diff --git a/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorServiceImpl.java b/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorServiceImpl.java index 7bc43de5f4..9539d97e20 100644 --- a/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorServiceImpl.java +++ b/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorServiceImpl.java @@ -1,547 +1,469 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - * #L% - */ -package org.alfresco.repo.remoteconnector; - -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.LinkedList; -import java.util.List; -import java.util.StringTokenizer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.security.authentication.AuthenticationException; -import org.alfresco.service.cmr.remoteconnector.RemoteConnectorClientException; -import org.alfresco.service.cmr.remoteconnector.RemoteConnectorRequest; -import org.alfresco.service.cmr.remoteconnector.RemoteConnectorResponse; -import org.alfresco.service.cmr.remoteconnector.RemoteConnectorServerException; -import org.alfresco.service.cmr.remoteconnector.RemoteConnectorService; -import org.alfresco.util.HttpClientHelper; -import org.apache.commons.httpclient.Credentials; -import org.apache.commons.httpclient.Header; -import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.HttpMethodBase; -import org.apache.commons.httpclient.ProxyHost; -import org.apache.commons.httpclient.UsernamePasswordCredentials; -import org.apache.commons.httpclient.auth.AuthScope; -import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; -import org.apache.commons.httpclient.methods.EntityEnclosingMethod; -import org.apache.commons.httpclient.methods.StringRequestEntity; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; -import org.springframework.extensions.webscripts.Status; - -import static org.alfresco.service.cmr.favourites.FavouritesService.SortFields.username; - -/** - * HttpClient powered implementation of {@link RemoteConnectorService}, which - * performs requests to remote HTTP servers. - * - * Note - this class assumes direct connectivity is available to the destination - * system, and does not support proxies. - * - * @author Nick Burch - * @since 4.0.2 - */ -public class RemoteConnectorServiceImpl implements RemoteConnectorService -{ - /** - * The logger - */ - private static Log logger = LogFactory.getLog(RemoteConnectorServiceImpl.class); - private static final long MAX_BUFFER_RESPONSE_SIZE = 10*1024*1024; - - private static ProxyHost httpProxyHost; - private static ProxyHost httpsProxyHost; - private static Credentials httpProxyCredentials; - private static Credentials httpsProxyCredentials; - private static AuthScope httpAuthScope; - private static AuthScope httpsAuthScope; - - /** - * Initialise the HTTP Proxy Hosts and Params Factory - */ - static - { - // Create an HTTP Proxy Host if appropriate system property set - httpProxyHost = createProxyHost("http.proxyHost", "http.proxyPort", 80); - httpProxyCredentials = createProxyCredentials("http.proxyUser", "http.proxyPassword"); - httpAuthScope = createProxyAuthScope(httpProxyHost); - - // Create an HTTPS Proxy Host if appropriate system property set - httpsProxyHost = createProxyHost("https.proxyHost", "https.proxyPort", 443); - httpsProxyCredentials = createProxyCredentials("https.proxyUser", "https.proxyPassword"); - httpsAuthScope = createProxyAuthScope(httpsProxyHost); - - - } - - public RemoteConnectorServiceImpl() - {} - - /** - * Builds a new Request object - */ - public RemoteConnectorRequest buildRequest(String url, String method) - { - return new RemoteConnectorRequestImpl(url, method); - } - - /** - * Builds a new Request object, using HttpClient method descriptions - */ - public RemoteConnectorRequest buildRequest(String url, Class method) - { - return new RemoteConnectorRequestImpl(url, method); - } - - /** - * Executes the specified request, and return the response - */ - public RemoteConnectorResponse executeRequest(RemoteConnectorRequest request) throws IOException, AuthenticationException, - RemoteConnectorClientException, RemoteConnectorServerException - { - RemoteConnectorRequestImpl reqImpl = (RemoteConnectorRequestImpl)request; - HttpMethodBase httpRequest = reqImpl.getMethodInstance(); - - // Attach the headers to the request - for (Header hdr : request.getRequestHeaders()) - { - httpRequest.addRequestHeader(hdr); - } - - // Attach the body, if possible - if (httpRequest instanceof EntityEnclosingMethod) - { - if (request.getRequestBody() != null) - { - ((EntityEnclosingMethod)httpRequest).setRequestEntity( reqImpl.getRequestBody() ); - } - } - - // Grab our thread local HttpClient instance - // Remember - we must then clean it up! - HttpClient httpClient = HttpClientHelper.getHttpClient(); - - // The url should already be vetted by the RemoteConnectorRequest - URL url = new URL(request.getURL()); - - // Use the appropriate Proxy Host if required - if (httpProxyHost != null && url.getProtocol().equals("http") && requiresProxy(url.getHost())) - { - httpClient.getHostConfiguration().setProxyHost(httpProxyHost); - if (logger.isDebugEnabled()) - logger.debug(" - using HTTP proxy host for: " + url); - if (httpProxyCredentials != null) - { - httpClient.getState().setProxyCredentials(httpAuthScope, httpProxyCredentials); - if (logger.isDebugEnabled()) - logger.debug(" - using HTTP proxy credentials for proxy: " + httpProxyHost.getHostName()); - } - } - else if (httpsProxyHost != null && url.getProtocol().equals("https") && requiresProxy(url.getHost())) - { - httpClient.getHostConfiguration().setProxyHost(httpsProxyHost); - if (logger.isDebugEnabled()) - logger.debug(" - using HTTPS proxy host for: " + url); - if (httpsProxyCredentials != null) - { - httpClient.getState().setProxyCredentials(httpsAuthScope, httpsProxyCredentials); - if (logger.isDebugEnabled()) - logger.debug(" - using HTTPS proxy credentials for proxy: " + httpsProxyHost.getHostName()); - } - } - else - { - //host should not be proxied remove any configured proxies - httpClient.getHostConfiguration().setProxyHost(null); - httpClient.getState().clearProxyCredentials(); - } - - // Log what we're doing - if (logger.isDebugEnabled()) { - logger.debug("Performing " + request.getMethod() + " request to " + request.getURL()); - for (Header hdr : request.getRequestHeaders()) - { - logger.debug("Header: " + hdr ); - } - Object requestBody = null; - if (request != null) - { - requestBody = request.getRequestBody(); - } - if (requestBody != null && requestBody instanceof StringRequestEntity) - { - StringRequestEntity re = (StringRequestEntity)request.getRequestBody(); - // remove credentials from logs, such as "username":"John.Doe@test.com" and "password":"123456abc" - // the strings can include double quotes, therefore we should check for proper end, it can be either - // a comma "," or "}" - // REPO-1471 - String payload = re.getContent(); // returns a new string, should be safe to modify - String usernameString = "\"username\""; - String passwordString = "\"password\""; - String hiddenString = "\"\""; - List matches = new LinkedList<>(); - Matcher m = Pattern.compile(usernameString + ":\"(.+)\",|" + - usernameString + ":\"(.+)\"}|" + - passwordString + ":\"(.+)\",|" + - passwordString + ":\"(.+)\"}").matcher(payload); - while(m.find()) - { - matches.add(m.group()); - } - for (String match: matches) - { - if (match.contains(usernameString)) - { - payload = payload.replace( - match.substring( - match.indexOf(usernameString) + usernameString.length() + 1, // 1 is semicolon - match.lastIndexOf("\"") + 1), // 1 is a double quote - hiddenString); - } - else if (match.contains(passwordString)) - { - payload = payload.replace( - match.substring( - match.indexOf(passwordString) + passwordString.length() + 1, // 1 is semicolon - match.lastIndexOf("\"") + 1), // 1 is a double quote - hiddenString); - } - - } - - logger.debug("Payload (string): " + payload); - } - else if (requestBody != null && requestBody instanceof ByteArrayRequestEntity) - { - ByteArrayRequestEntity re = (ByteArrayRequestEntity)request.getRequestBody(); - logger.debug("Payload (byte array): " + re.getContent().toString()); - } - else - { - logger.debug("Payload is not of a readable type."); - } - } - - // Perform the request, and wrap the response - int status = -1; - String statusText = null; - RemoteConnectorResponse response = null; - try - { - status = httpClient.executeMethod(httpRequest); - statusText = httpRequest.getStatusText(); - - Header[] responseHdrs = httpRequest.getResponseHeaders(); - Header responseContentTypeH = httpRequest.getResponseHeader(RemoteConnectorRequestImpl.HEADER_CONTENT_TYPE); - String responseCharSet = httpRequest.getResponseCharSet(); - String responseContentType = (responseContentTypeH != null ? responseContentTypeH.getValue() : null); - - if(logger.isDebugEnabled()) - { - logger.debug("response url=" + request.getURL() + ", length =" + httpRequest.getResponseContentLength() + ", responceContentType " + responseContentType + ", statusText =" + statusText ); - } - - // Decide on how best to handle the response, based on the size - // Ideally, we want to close the HttpClient resources immediately, but - // that isn't possible for very large responses - // If we can close immediately, it makes cleanup simpler and fool-proof - if (httpRequest.getResponseContentLength() > MAX_BUFFER_RESPONSE_SIZE || httpRequest.getResponseContentLength() == -1 ) - { - if(logger.isTraceEnabled()) - { - logger.trace("large response (or don't know length) url=" + request.getURL()); - } - - // Need to wrap the InputStream in something that'll close - InputStream wrappedStream = new HttpClientReleasingInputStream(httpRequest); - httpRequest = null; - - // Now build the response - response = new RemoteConnectorResponseImpl(request, responseContentType, responseCharSet, - status, responseHdrs, wrappedStream); - } - else - { - if(logger.isTraceEnabled()) - { - logger.debug("small response for url=" + request.getURL()); - } - // Fairly small response, just keep the bytes and make life simple - response = new RemoteConnectorResponseImpl(request, responseContentType, responseCharSet, - status, responseHdrs, httpRequest.getResponseBody()); - - // Now we have the bytes, we can close the HttpClient resources - httpRequest.releaseConnection(); - httpRequest = null; - } - } - finally - { - // Make sure, problems or not, we always tidy up (if not large stream based) - // This is important because we use a thread local HttpClient instance - if (httpRequest != null) - { - httpRequest.releaseConnection(); - httpRequest = null; - } - } - - - // Log the response - if (logger.isDebugEnabled()) - logger.debug("Response was " + status + " " + statusText); - - // Decide if we should throw an exception - if (status >= 300) - { - // Tidy if needed - if (httpRequest != null) - httpRequest.releaseConnection(); - - // Specific exceptions - if (status == Status.STATUS_FORBIDDEN || - status == Status.STATUS_UNAUTHORIZED) - { - // TODO Forbidden may need to be handled differently. - // TODO Need to get error message into the AuthenticationException - throw new AuthenticationException(statusText); - } - - // Server side exceptions - if (status >= 500 && status <= 599) - { - logger.error("executeRequest: remote connector server exception: ["+status+"] "+statusText); - throw new RemoteConnectorServerException(status, statusText); - } - if(status == Status.STATUS_PRECONDITION_FAILED) - { - logger.error("executeRequest: remote connector client exception: ["+status+"] "+statusText); - throw new RemoteConnectorClientException(status, statusText, response); - } - else - { - // Client request exceptions - if (httpRequest != null) - { - // Response wasn't too big and is available, supply it - logger.error("executeRequest: remote connector client exception: ["+status+"] "+statusText); - throw new RemoteConnectorClientException(status, statusText, response); - } - else - { - // Response was too large, report without it - logger.error("executeRequest: remote connector client exception: ["+status+"] "+statusText); - throw new RemoteConnectorClientException(status, statusText, null); - } - } - } - - // If we get here, then the request/response was all fine - // So, return our created response - return response; - } - - /** - * Executes the given request, requesting a JSON response, and - * returns the parsed JSON received back - * - * @throws ParseException If the response is not valid JSON - */ - public JSONObject executeJSONRequest(RemoteConnectorRequest request) throws ParseException, IOException, AuthenticationException - { - return doExecuteJSONRequest(request, this); - } - - public static JSONObject doExecuteJSONRequest(RemoteConnectorRequest request, RemoteConnectorService service) throws ParseException, IOException, AuthenticationException - { - // Set as JSON - request.setContentType(MimetypeMap.MIMETYPE_JSON); - - // Perform the request - RemoteConnectorResponse response = service.executeRequest(request); - - // Parse this as JSON - JSONParser parser = new JSONParser(); - String jsonText = response.getResponseBodyAsString(); - Object json = parser.parse(jsonText); - - // Check it's the right type and return - if (json instanceof JSONObject) - { - return (JSONObject)json; - } - else - { - throw new ParseException(0, json); - } - } - - private static class HttpClientReleasingInputStream extends FilterInputStream - { - private HttpMethodBase httpRequest; - private HttpClientReleasingInputStream(HttpMethodBase httpRequest) throws IOException - { - super(httpRequest.getResponseBodyAsStream()); - this.httpRequest = httpRequest; - } - - @Override - public void close() throws IOException - { - // Tidy the main stream - super.close(); - - // Now release the underlying resources - if (httpRequest != null) - { - httpRequest.releaseConnection(); - httpRequest = null; - } - } - - /** - * In case the caller has neglected to close the Stream, warn - * (as this will break things for other users!) and then close - */ - @Override - protected void finalize() throws Throwable - { - if (httpRequest != null) - { - logger.warn("RemoteConnector response InputStream wasn't closed but must be! This can cause issues for " + - "other requests in this Thread!"); - - httpRequest.releaseConnection(); - httpRequest = null; - } - - // Let the InputStream tidy up if it wants to too - super.finalize(); - } - } - - /** - * Create proxy host for the given system host and port properties. - * If the properties are not set, no proxy will be created. - * - * @param hostProperty String - * @param portProperty String - * @param defaultPort int - * - * @return ProxyHost if appropriate properties have been set, null otherwise - */ - private static ProxyHost createProxyHost(final String hostProperty, final String portProperty, final int defaultPort) - { - final String proxyHost = System.getProperty(hostProperty); - ProxyHost proxy = null; - if (proxyHost != null && proxyHost.length() != 0) - { - final String strProxyPort = System.getProperty(portProperty); - if (strProxyPort == null || strProxyPort.length() == 0) - { - proxy = new ProxyHost(proxyHost, defaultPort); - } - else - { - proxy = new ProxyHost(proxyHost, Integer.parseInt(strProxyPort)); - } - if (logger.isDebugEnabled()) - logger.debug("ProxyHost: " + proxy.toString()); - } - return proxy; - } - - /** - * Create the proxy credentials for the given proxy user and password properties. - * If the properties are not set, not credentials will be created. - * @param proxyUserProperty String - * @param proxyPasswordProperty String - * @return Credentials if appropriate properties have been set, null otherwise - */ - private static Credentials createProxyCredentials(final String proxyUserProperty, final String proxyPasswordProperty) - { - final String proxyUser = System.getProperty(proxyUserProperty); - final String proxyPassword = System.getProperty(proxyPasswordProperty); - Credentials creds = null; - if (StringUtils.isNotBlank(proxyUser)) - { - creds = new UsernamePasswordCredentials(proxyUser, proxyPassword); - } - return creds; - } - - /** - * Create suitable AuthScope for ProxyHost. - * If the ProxyHost is null, no AuthsScope will be created. - * @param proxyHost ProxyHost - * @return Authscope for provided ProxyHost, null otherwise. - */ - private static AuthScope createProxyAuthScope(final ProxyHost proxyHost) - { - AuthScope authScope = null; - if (proxyHost != null) - { - authScope = new AuthScope(proxyHost.getHostName(), proxyHost.getPort()); - } - return authScope; - } - - /** - * Return true unless the given target host is specified in the http.nonProxyHosts system property. - * See http://download.oracle.com/javase/1.4.2/docs/guide/net/properties.html - * @param targetHost Non-null host name to test - * @return true if not specified in list, false if it is specifed and therefore should be excluded from proxy - */ - private boolean requiresProxy(final String targetHost) - { - boolean requiresProxy = true; - final String nonProxyHosts = System.getProperty("http.nonProxyHosts"); - if (nonProxyHosts != null) - { - StringTokenizer tokenizer = new StringTokenizer(nonProxyHosts, "|"); - while (tokenizer.hasMoreTokens()) - { - String pattern = tokenizer.nextToken(); - pattern = pattern.replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*"); - if (targetHost.matches(pattern)) - { - requiresProxy = false; - break; - } - } - } - return requiresProxy; - } -} +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2017 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.repo.remoteconnector; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.service.cmr.remoteconnector.RemoteConnectorClientException; +import org.alfresco.service.cmr.remoteconnector.RemoteConnectorRequest; +import org.alfresco.service.cmr.remoteconnector.RemoteConnectorResponse; +import org.alfresco.service.cmr.remoteconnector.RemoteConnectorServerException; +import org.alfresco.service.cmr.remoteconnector.RemoteConnectorService; +import org.alfresco.util.HttpClientHelper; +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethodBase; +import org.apache.commons.httpclient.ProxyHost; +import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; +import org.apache.commons.httpclient.methods.EntityEnclosingMethod; +import org.apache.commons.httpclient.methods.StringRequestEntity; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.springframework.extensions.webscripts.Status; + +/** + * HttpClient powered implementation of {@link RemoteConnectorService}, which performs requests to remote HTTP servers. + * + * @author Nick Burch + * @since 4.0.2 + */ +public class RemoteConnectorServiceImpl implements RemoteConnectorService +{ + /** + * The logger + */ + private static Log logger = LogFactory.getLog(RemoteConnectorServiceImpl.class); + private static final long MAX_BUFFER_RESPONSE_SIZE = 10*1024*1024; + + private static ProxyHost httpProxyHost; + private static ProxyHost httpsProxyHost; + private static Credentials httpProxyCredentials; + private static Credentials httpsProxyCredentials; + private static AuthScope httpAuthScope; + private static AuthScope httpsAuthScope; + + /** + * Initialise the HTTP Proxy Hosts and Params Factory + */ + static + { + // Create an HTTP Proxy Host if appropriate system property set + httpProxyHost = HttpClientHelper.createProxyHost("http.proxyHost", "http.proxyPort", 80); + httpProxyCredentials = HttpClientHelper.createProxyCredentials("http.proxyUser", "http.proxyPassword"); + httpAuthScope = createProxyAuthScope(httpProxyHost); + + // Create an HTTPS Proxy Host if appropriate system property set + httpsProxyHost = HttpClientHelper.createProxyHost("https.proxyHost", "https.proxyPort", 443); + httpsProxyCredentials = HttpClientHelper.createProxyCredentials("https.proxyUser", "https.proxyPassword"); + httpsAuthScope = createProxyAuthScope(httpsProxyHost); + } + + public RemoteConnectorServiceImpl() + {} + + /** + * Builds a new Request object + */ + public RemoteConnectorRequest buildRequest(String url, String method) + { + return new RemoteConnectorRequestImpl(url, method); + } + + /** + * Builds a new Request object, using HttpClient method descriptions + */ + public RemoteConnectorRequest buildRequest(String url, Class method) + { + return new RemoteConnectorRequestImpl(url, method); + } + + /** + * Executes the specified request, and return the response + */ + public RemoteConnectorResponse executeRequest(RemoteConnectorRequest request) throws IOException, AuthenticationException, + RemoteConnectorClientException, RemoteConnectorServerException + { + RemoteConnectorRequestImpl reqImpl = (RemoteConnectorRequestImpl)request; + HttpMethodBase httpRequest = reqImpl.getMethodInstance(); + + // Attach the headers to the request + for (Header hdr : request.getRequestHeaders()) + { + httpRequest.addRequestHeader(hdr); + } + + // Attach the body, if possible + if (httpRequest instanceof EntityEnclosingMethod) + { + if (request.getRequestBody() != null) + { + ((EntityEnclosingMethod)httpRequest).setRequestEntity( reqImpl.getRequestBody() ); + } + } + + // Grab our thread local HttpClient instance + // Remember - we must then clean it up! + HttpClient httpClient = HttpClientHelper.getHttpClient(); + + // The url should already be vetted by the RemoteConnectorRequest + URL url = new URL(request.getURL()); + + // Use the appropriate Proxy Host if required + if (httpProxyHost != null && url.getProtocol().equals("http") && HttpClientHelper.requiresProxy(url.getHost())) + { + httpClient.getHostConfiguration().setProxyHost(httpProxyHost); + if (logger.isDebugEnabled()) + { + logger.debug(" - using HTTP proxy host for: " + url); + } + if (httpProxyCredentials != null) + { + httpClient.getState().setProxyCredentials(httpAuthScope, httpProxyCredentials); + if (logger.isDebugEnabled()) + { + logger.debug(" - using HTTP proxy credentials for proxy: " + httpProxyHost.getHostName()); + } + } + } + else if (httpsProxyHost != null && url.getProtocol().equals("https") && HttpClientHelper.requiresProxy(url.getHost())) + { + httpClient.getHostConfiguration().setProxyHost(httpsProxyHost); + if (logger.isDebugEnabled()) + { + logger.debug(" - using HTTPS proxy host for: " + url); + } + if (httpsProxyCredentials != null) + { + httpClient.getState().setProxyCredentials(httpsAuthScope, httpsProxyCredentials); + if (logger.isDebugEnabled()) + { + logger.debug(" - using HTTPS proxy credentials for proxy: " + httpsProxyHost.getHostName()); + } + } + } + else + { + //host should not be proxied remove any configured proxies + httpClient.getHostConfiguration().setProxyHost(null); + httpClient.getState().clearProxyCredentials(); + } + + // Log what we're doing + if (logger.isDebugEnabled()) + { + logger.debug("Performing " + request.getMethod() + " request to " + request.getURL()); + for (Header hdr : request.getRequestHeaders()) + { + logger.debug("Header: " + hdr ); + } + Object requestBody = null; + if (request != null) + { + requestBody = request.getRequestBody(); + } + if (requestBody != null && requestBody instanceof StringRequestEntity) + { + StringRequestEntity re = (StringRequestEntity)request.getRequestBody(); + // remove credentials from logs, such as "username":"John.Doe@test.com" and "password":"123456abc" + // the strings can include double quotes, therefore we should check for proper end, it can be either + // a comma "," or "}" + // REPO-1471 + String payload = re.getContent(); // returns a new string, should be safe to modify + String usernameString = "\"username\""; + String passwordString = "\"password\""; + String hiddenString = "\"\""; + List matches = new LinkedList<>(); + Matcher m = Pattern.compile(usernameString + ":\"(.+)\",|" + + usernameString + ":\"(.+)\"}|" + + passwordString + ":\"(.+)\",|" + + passwordString + ":\"(.+)\"}").matcher(payload); + while(m.find()) + { + matches.add(m.group()); + } + for (String match: matches) + { + if (match.contains(usernameString)) + { + payload = payload.replace( + match.substring( + match.indexOf(usernameString) + usernameString.length() + 1, // 1 is semicolon + match.lastIndexOf("\"") + 1), // 1 is a double quote + hiddenString); + } + else if (match.contains(passwordString)) + { + payload = payload.replace( + match.substring( + match.indexOf(passwordString) + passwordString.length() + 1, // 1 is semicolon + match.lastIndexOf("\"") + 1), // 1 is a double quote + hiddenString); + } + + } + + logger.debug("Payload (string): " + payload); + } + else if (requestBody != null && requestBody instanceof ByteArrayRequestEntity) + { + ByteArrayRequestEntity re = (ByteArrayRequestEntity)request.getRequestBody(); + logger.debug("Payload (byte array): " + re.getContent().toString()); + } + else + { + logger.debug("Payload is not of a readable type."); + } + } + + // Perform the request, and wrap the response + int status = -1; + String statusText = null; + RemoteConnectorResponse response = null; + try + { + status = httpClient.executeMethod(httpRequest); + statusText = httpRequest.getStatusText(); + + Header[] responseHdrs = httpRequest.getResponseHeaders(); + Header responseContentTypeH = httpRequest.getResponseHeader(RemoteConnectorRequestImpl.HEADER_CONTENT_TYPE); + String responseCharSet = httpRequest.getResponseCharSet(); + String responseContentType = (responseContentTypeH != null ? responseContentTypeH.getValue() : null); + + if(logger.isDebugEnabled()) + { + logger.debug("response url=" + request.getURL() + ", length =" + httpRequest.getResponseContentLength() + ", responceContentType " + responseContentType + ", statusText =" + statusText ); + } + + // Decide on how best to handle the response, based on the size + // Ideally, we want to close the HttpClient resources immediately, but + // that isn't possible for very large responses + // If we can close immediately, it makes cleanup simpler and fool-proof + if (httpRequest.getResponseContentLength() > MAX_BUFFER_RESPONSE_SIZE || httpRequest.getResponseContentLength() == -1 ) + { + if(logger.isTraceEnabled()) + { + logger.trace("large response (or don't know length) url=" + request.getURL()); + } + + // Need to wrap the InputStream in something that'll close + InputStream wrappedStream = new HttpClientReleasingInputStream(httpRequest); + httpRequest = null; + + // Now build the response + response = new RemoteConnectorResponseImpl(request, responseContentType, responseCharSet, + status, responseHdrs, wrappedStream); + } + else + { + if(logger.isTraceEnabled()) + { + logger.debug("small response for url=" + request.getURL()); + } + // Fairly small response, just keep the bytes and make life simple + response = new RemoteConnectorResponseImpl(request, responseContentType, responseCharSet, + status, responseHdrs, httpRequest.getResponseBody()); + + // Now we have the bytes, we can close the HttpClient resources + httpRequest.releaseConnection(); + httpRequest = null; + } + } + finally + { + // Make sure, problems or not, we always tidy up (if not large stream based) + // This is important because we use a thread local HttpClient instance + if (httpRequest != null) + { + httpRequest.releaseConnection(); + httpRequest = null; + } + } + + + // Log the response + if (logger.isDebugEnabled()) + logger.debug("Response was " + status + " " + statusText); + + // Decide if we should throw an exception + if (status >= 300) + { + // Tidy if needed + if (httpRequest != null) + httpRequest.releaseConnection(); + + // Specific exceptions + if (status == Status.STATUS_FORBIDDEN || + status == Status.STATUS_UNAUTHORIZED) + { + // TODO Forbidden may need to be handled differently. + // TODO Need to get error message into the AuthenticationException + throw new AuthenticationException(statusText); + } + + // Server side exceptions + if (status >= 500 && status <= 599) + { + logger.error("executeRequest: remote connector server exception: ["+status+"] "+statusText); + throw new RemoteConnectorServerException(status, statusText); + } + if(status == Status.STATUS_PRECONDITION_FAILED) + { + logger.error("executeRequest: remote connector client exception: ["+status+"] "+statusText); + throw new RemoteConnectorClientException(status, statusText, response); + } + else + { + // Client request exceptions + if (httpRequest != null) + { + // Response wasn't too big and is available, supply it + logger.error("executeRequest: remote connector client exception: ["+status+"] "+statusText); + throw new RemoteConnectorClientException(status, statusText, response); + } + else + { + // Response was too large, report without it + logger.error("executeRequest: remote connector client exception: ["+status+"] "+statusText); + throw new RemoteConnectorClientException(status, statusText, null); + } + } + } + + // If we get here, then the request/response was all fine + // So, return our created response + return response; + } + + /** + * Executes the given request, requesting a JSON response, and + * returns the parsed JSON received back + * + * @throws ParseException If the response is not valid JSON + */ + public JSONObject executeJSONRequest(RemoteConnectorRequest request) throws ParseException, IOException, AuthenticationException + { + return doExecuteJSONRequest(request, this); + } + + public static JSONObject doExecuteJSONRequest(RemoteConnectorRequest request, RemoteConnectorService service) throws ParseException, IOException, AuthenticationException + { + // Set as JSON + request.setContentType(MimetypeMap.MIMETYPE_JSON); + + // Perform the request + RemoteConnectorResponse response = service.executeRequest(request); + + // Parse this as JSON + JSONParser parser = new JSONParser(); + String jsonText = response.getResponseBodyAsString(); + Object json = parser.parse(jsonText); + + // Check it's the right type and return + if (json instanceof JSONObject) + { + return (JSONObject)json; + } + else + { + throw new ParseException(0, json); + } + } + + private static class HttpClientReleasingInputStream extends FilterInputStream + { + private HttpMethodBase httpRequest; + private HttpClientReleasingInputStream(HttpMethodBase httpRequest) throws IOException + { + super(httpRequest.getResponseBodyAsStream()); + this.httpRequest = httpRequest; + } + + @Override + public void close() throws IOException + { + // Tidy the main stream + super.close(); + + // Now release the underlying resources + if (httpRequest != null) + { + httpRequest.releaseConnection(); + httpRequest = null; + } + } + + /** + * In case the caller has neglected to close the Stream, warn + * (as this will break things for other users!) and then close + */ + @Override + protected void finalize() throws Throwable + { + if (httpRequest != null) + { + logger.warn("RemoteConnector response InputStream wasn't closed but must be! This can cause issues for " + + "other requests in this Thread!"); + + httpRequest.releaseConnection(); + httpRequest = null; + } + + // Let the InputStream tidy up if it wants to too + super.finalize(); + } + } + + /** + * Create suitable AuthScope for ProxyHost. + * If the ProxyHost is null, no AuthsScope will be created. + * @param proxyHost ProxyHost + * @return Authscope for provided ProxyHost, null otherwise. + */ + private static AuthScope createProxyAuthScope(final ProxyHost proxyHost) + { + AuthScope authScope = null; + if (proxyHost != null) + { + authScope = new AuthScope(proxyHost.getHostName(), proxyHost.getPort()); + } + return authScope; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java b/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java index 74d7b5ef41..5ddbe41b5d 100644 --- a/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java +++ b/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2017 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -23,128 +23,147 @@ * along with Alfresco. If not, see . * #L% */ - -package org.alfresco.repo.transfer; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; -import java.nio.channels.WritableByteChannel; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -import org.alfresco.service.cmr.repository.AssociationRef; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.transfer.TransferException; -import org.alfresco.service.cmr.transfer.TransferProgress; -import org.alfresco.service.cmr.transfer.TransferTarget; -import org.alfresco.service.cmr.transfer.TransferVersion; -import org.alfresco.util.PropertyCheck; -import org.alfresco.util.json.ExceptionJsonSerializer; -import org.alfresco.util.json.JsonSerializer; -import org.apache.commons.httpclient.HostConfiguration; -import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.HttpMethod; -import org.apache.commons.httpclient.HttpState; -import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; -import org.apache.commons.httpclient.NameValuePair; -import org.apache.commons.httpclient.UsernamePasswordCredentials; -import org.apache.commons.httpclient.auth.AuthScope; -import org.apache.commons.httpclient.methods.PostMethod; -import org.apache.commons.httpclient.methods.multipart.FilePart; -import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; -import org.apache.commons.httpclient.methods.multipart.Part; -import org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory; -import org.apache.commons.httpclient.protocol.Protocol; -import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; -import org.apache.commons.httpclient.protocol.SSLProtocolSocketFactory; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * HTTP implementation of TransferTransmitter. - * - * Sends data via HTTP to the server. - * - * @author brian - */ -public class HttpClientTransmitterImpl implements TransferTransmitter -{ - private static final Log log = LogFactory.getLog(HttpClientTransmitterImpl.class); - - private static final String MSG_UNSUPPORTED_PROTOCOL = "transfer_service.comms.unsupported_protocol"; - private static final String MSG_UNSUCCESSFUL_RESPONSE = "transfer_service.comms.unsuccessful_response"; - private static final String MSG_HTTP_REQUEST_FAILED = "transfer_service.comms.http_request_failed"; - - private static final int DEFAULT_HTTP_PORT = 80; - private static final int DEFAULT_HTTPS_PORT = 443; - private static final String HTTP_SCHEME_NAME = "http"; // lowercase is important - private static final String HTTPS_SCHEME_NAME = "https"; // lowercase is important - - private HttpClient httpClient = null; - private Protocol httpProtocol = new Protocol(HTTP_SCHEME_NAME, new DefaultProtocolSocketFactory(), DEFAULT_HTTP_PORT); - private Protocol httpsProtocol = new Protocol(HTTPS_SCHEME_NAME, (ProtocolSocketFactory) new SSLProtocolSocketFactory(), DEFAULT_HTTPS_PORT); - private Map protocolMap = null; - private HttpMethodFactory httpMethodFactory = null; - private JsonSerializer jsonErrorSerializer; - - private ContentService contentService; - - private NodeService nodeService; + +package org.alfresco.repo.transfer; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.transfer.TransferException; +import org.alfresco.service.cmr.transfer.TransferProgress; +import org.alfresco.service.cmr.transfer.TransferTarget; +import org.alfresco.service.cmr.transfer.TransferVersion; +import org.alfresco.util.HttpClientHelper; +import org.alfresco.util.PropertyCheck; +import org.alfresco.util.json.ExceptionJsonSerializer; +import org.alfresco.util.json.JsonSerializer; +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.HostConfiguration; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.HttpState; +import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.commons.httpclient.NameValuePair; +import org.apache.commons.httpclient.ProxyHost; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.multipart.FilePart; +import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; +import org.apache.commons.httpclient.methods.multipart.Part; +import org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory; +import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; +import org.apache.commons.httpclient.protocol.SSLProtocolSocketFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONObject; + +/** + * HTTP implementation of TransferTransmitter. + * + * Sends data via HTTP to the server. + * + * @author brian + */ +public class HttpClientTransmitterImpl implements TransferTransmitter +{ + private static final Log log = LogFactory.getLog(HttpClientTransmitterImpl.class); + + private static final String MSG_UNSUPPORTED_PROTOCOL = "transfer_service.comms.unsupported_protocol"; + private static final String MSG_UNSUCCESSFUL_RESPONSE = "transfer_service.comms.unsuccessful_response"; + private static final String MSG_HTTP_REQUEST_FAILED = "transfer_service.comms.http_request_failed"; + + private static final int DEFAULT_HTTP_PORT = 80; + private static final int DEFAULT_HTTPS_PORT = 443; + private static final String HTTP_SCHEME_NAME = "http"; // lowercase is important + private static final String HTTPS_SCHEME_NAME = "https"; // lowercase is important + + private HttpClient httpClient = null; + private Protocol httpProtocol = new Protocol(HTTP_SCHEME_NAME, new DefaultProtocolSocketFactory(), DEFAULT_HTTP_PORT); + private Protocol httpsProtocol = new Protocol(HTTPS_SCHEME_NAME, (ProtocolSocketFactory) new SSLProtocolSocketFactory(), DEFAULT_HTTPS_PORT); + private Map protocolMap = null; + private HttpMethodFactory httpMethodFactory = null; + private JsonSerializer jsonErrorSerializer; + + private ContentService contentService; + + private NodeService nodeService; private boolean isAuthenticationPreemptive = false; - - public HttpClientTransmitterImpl() - { - protocolMap = new TreeMap(); - protocolMap.put(HTTP_SCHEME_NAME, httpProtocol); - protocolMap.put(HTTPS_SCHEME_NAME, httpsProtocol); - - httpClient = new HttpClient(); - httpClient.setHttpConnectionManager(new MultiThreadedHttpConnectionManager()); - httpMethodFactory = new StandardHttpMethodFactoryImpl(); - jsonErrorSerializer = new ExceptionJsonSerializer(); - } - - public void init() - { - PropertyCheck.mandatory(this, "contentService", contentService); + + private ProxyHost httpProxyHost; + private ProxyHost httpsProxyHost; + private Credentials httpProxyCredentials; + private Credentials httpsProxyCredentials; + private AuthScope httpAuthScope; + private AuthScope httpsAuthScope; + + public HttpClientTransmitterImpl() + { + protocolMap = new TreeMap(); + protocolMap.put(HTTP_SCHEME_NAME, httpProtocol); + protocolMap.put(HTTPS_SCHEME_NAME, httpsProtocol); + + httpClient = new HttpClient(); + httpClient.setHttpConnectionManager(new MultiThreadedHttpConnectionManager()); + httpMethodFactory = new StandardHttpMethodFactoryImpl(); + jsonErrorSerializer = new ExceptionJsonSerializer(); + + // Create an HTTP Proxy Host if appropriate system properties are set + httpProxyHost = HttpClientHelper.createProxyHost("http.proxyHost", "http.proxyPort", DEFAULT_HTTP_PORT); + httpProxyCredentials = HttpClientHelper.createProxyCredentials("http.proxyUser", "http.proxyPassword"); + httpAuthScope = createProxyAuthScope(httpProxyHost); + + // Create an HTTPS Proxy Host if appropriate system properties are set + httpsProxyHost = HttpClientHelper.createProxyHost("https.proxyHost", "https.proxyPort", DEFAULT_HTTPS_PORT); + httpsProxyCredentials = HttpClientHelper.createProxyCredentials("https.proxyUser", "https.proxyPassword"); + httpsAuthScope = createProxyAuthScope(httpsProxyHost); + } + + public void init() + { + PropertyCheck.mandatory(this, "contentService", contentService); httpClient.getParams().setAuthenticationPreemptive(isAuthenticationPreemptive); - } - - /** - * By default this class uses the standard SSLProtocolSocketFactory, but this method allows this to be overridden. - * Useful if, for example, one wishes to permit support of self-signed certificates on the target. + } + + /** + * By default this class uses the standard SSLProtocolSocketFactory, but this method allows this to be overridden. + * Useful if, for example, one wishes to permit support of self-signed certificates on the target. * @param socketFactory ProtocolSocketFactory - */ - public void setHttpsSocketFactory(ProtocolSocketFactory socketFactory) - { - protocolMap.put(HTTPS_SCHEME_NAME, new Protocol(HTTPS_SCHEME_NAME, socketFactory, DEFAULT_HTTPS_PORT)); - } - - /** - * By default, this class uses a plain HttpClient instance with the only non-default - * option being the multi-threaded connection manager. - * Use this method to replace this with your own HttpClient instance configured how you wish + */ + public void setHttpsSocketFactory(ProtocolSocketFactory socketFactory) + { + protocolMap.put(HTTPS_SCHEME_NAME, new Protocol(HTTPS_SCHEME_NAME, socketFactory, DEFAULT_HTTPS_PORT)); + } + + /** + * By default, this class uses a plain HttpClient instance with the only non-default + * option being the multi-threaded connection manager. + * Use this method to replace this with your own HttpClient instance configured how you wish * @param httpClient HttpClient - */ - public void setHttpClient(HttpClient httpClient) - { - this.httpClient = httpClient; - } - + */ + public void setHttpClient(HttpClient httpClient) + { + this.httpClient = httpClient; + } + /** * Whether httpClient will use preemptive authentication or not. * @param isAuthenticationPreemptive boolean @@ -154,649 +173,725 @@ public class HttpClientTransmitterImpl implements TransferTransmitter this.isAuthenticationPreemptive = isAuthenticationPreemptive; } - /* (non-Javadoc) - * @see org.alfresco.repo.transfer.Transmitter#verifyTarget(org.alfresco.service.cmr.transfer.TransferTarget) - */ - public void verifyTarget(TransferTarget target) throws TransferException - { - HttpMethod verifyRequest = getPostMethod(); - try - { - HostConfiguration hostConfig = getHostConfig(target); - HttpState httpState = getHttpState(target); - - verifyRequest.setPath(target.getEndpointPath() + "/test"); - try - { - int response = httpClient.executeMethod(hostConfig, verifyRequest, httpState); - checkResponseStatus("verifyTarget", response, verifyRequest); - } - catch (RuntimeException e) - { - throw e; - } - catch (Exception e) - { - String error = "Failed to execute HTTP request to target"; - log.debug(error, e); - throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"verifyTraget", target.toString(), e.toString()}, e); - } - } - finally - { - verifyRequest.releaseConnection(); - } - } - - /** + /* (non-Javadoc) + * @see org.alfresco.repo.transfer.Transmitter#verifyTarget(org.alfresco.service.cmr.transfer.TransferTarget) + */ + public void verifyTarget(TransferTarget target) throws TransferException + { + HttpMethod verifyRequest = getPostMethod(); + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + verifyRequest.setPath(target.getEndpointPath() + "/test"); + try + { + int response = httpClient.executeMethod(hostConfig, verifyRequest, httpState); + checkResponseStatus("verifyTarget", response, verifyRequest); + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.debug(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"verifyTraget", target.toString(), e.toString()}, e); + } + } + finally + { + verifyRequest.releaseConnection(); + } + } + + /** * * @param methodName String * @param response int * @param method HttpMethod - */ - private void checkResponseStatus(String methodName, int response, HttpMethod method) - { - if (response != 200) - { - Throwable error = null; - try - { - log.error("Received \"unsuccessful\" response code from target server: " + response); - String errorPayload = method.getResponseBodyAsString(); - JSONObject errorObj = new JSONObject(errorPayload); - error = rehydrateError(errorObj); - } - catch (Exception ex) - { - throw new TransferException(MSG_UNSUCCESSFUL_RESPONSE, new Object[] {methodName, response}); - } - if ((error != null) && TransferException.class.isAssignableFrom(error.getClass())) - { - throw (TransferException)error; - } - else - { - throw new TransferException(MSG_UNSUCCESSFUL_RESPONSE, new Object[] {methodName, response}); - } - } - } - - /** - * Get the HTTPState for a transfer target + */ + private void checkResponseStatus(String methodName, int response, HttpMethod method) + { + if (response != 200) + { + Throwable error = null; + try + { + log.error("Received \"unsuccessful\" response code from target server: " + response); + String errorPayload = method.getResponseBodyAsString(); + JSONObject errorObj = new JSONObject(errorPayload); + error = rehydrateError(errorObj); + } + catch (Exception ex) + { + throw new TransferException(MSG_UNSUCCESSFUL_RESPONSE, new Object[] {methodName, response}); + } + if ((error != null) && TransferException.class.isAssignableFrom(error.getClass())) + { + throw (TransferException)error; + } + else + { + throw new TransferException(MSG_UNSUCCESSFUL_RESPONSE, new Object[] {methodName, response}); + } + } + } + + /** + * Get the HTTPState for a transfer target * @param target TransferTarget * @return HttpState - */ - protected HttpState getHttpState(TransferTarget target) - { - HttpState httpState = new HttpState(); - httpState.setCredentials(new AuthScope(target.getEndpointHost(), target.getEndpointPort(), - AuthScope.ANY_REALM), - new UsernamePasswordCredentials(target.getUsername(), new String(target.getPassword()))); - return httpState; - } - - /** + */ + private HttpState getHttpState(TransferTarget target) + { + HttpState httpState = new HttpState(); + httpState.setCredentials(new AuthScope(target.getEndpointHost(), target.getEndpointPort(), + AuthScope.ANY_REALM), + new UsernamePasswordCredentials(target.getUsername(), new String(target.getPassword()))); + + String requiredProtocol = target.getEndpointProtocol(); + if (requiredProtocol == null) + { + throw new TransferException(MSG_UNSUPPORTED_PROTOCOL, new Object[] {requiredProtocol}); + } + + Protocol protocol = protocolMap.get(requiredProtocol.toLowerCase().trim()); + if (protocol == null) + { + log.error("Unsupported protocol: " + requiredProtocol); + throw new TransferException(MSG_UNSUPPORTED_PROTOCOL, new Object[] {requiredProtocol}); + } + + // Use the appropriate Proxy credentials if required + if (httpProxyHost != null && HTTP_SCHEME_NAME.equals(protocol.getScheme()) && HttpClientHelper.requiresProxy(target.getEndpointHost())) + { + if (httpProxyCredentials != null) + { + httpState.setProxyCredentials(httpAuthScope, httpProxyCredentials); + + if (log.isDebugEnabled()) + { + log.debug("Using HTTP proxy credentials for proxy: " + httpProxyHost.getHostName()); + } + } + } + else if (httpsProxyHost != null && HTTPS_SCHEME_NAME.equals(protocol.getScheme()) && HttpClientHelper.requiresProxy(target.getEndpointHost())) + { + if (httpsProxyCredentials != null) + { + httpState.setProxyCredentials(httpsAuthScope, httpsProxyCredentials); + + if (log.isDebugEnabled()) + { + log.debug("Using HTTPS proxy credentials for proxy: " + httpsProxyHost.getHostName()); + } + } + } + + return httpState; + } + + /** * @param target TransferTarget * @return HostConfiguration - */ - private HostConfiguration getHostConfig(TransferTarget target) - { - String requiredProtocol = target.getEndpointProtocol(); - if (requiredProtocol == null) - { - throw new TransferException(MSG_UNSUPPORTED_PROTOCOL, new Object[] {target.getEndpointProtocol()}); - } - - Protocol protocol = protocolMap.get(requiredProtocol.toLowerCase().trim()); - if (protocol == null) { - log.error("Unsupported protocol: " + target.getEndpointProtocol()); - throw new TransferException(MSG_UNSUPPORTED_PROTOCOL, new Object[] {target.getEndpointProtocol()}); - } - - HostConfiguration hostConfig = new HostConfiguration(); - hostConfig.setHost(target.getEndpointHost(), target.getEndpointPort(), protocol); - return hostConfig; - } - - public Transfer begin(TransferTarget target, String fromRepositoryId, TransferVersion fromVersion) throws TransferException - { - PostMethod beginRequest = getPostMethod(); - try - { - HostConfiguration hostConfig = getHostConfig(target); - HttpState httpState = getHttpState(target); - - beginRequest.setPath(target.getEndpointPath() + "/begin"); - try - { - NameValuePair[] nameValuePair = new NameValuePair[] { - new NameValuePair(TransferCommons.PARAM_FROM_REPOSITORYID, fromRepositoryId), - new NameValuePair(TransferCommons.PARAM_ALLOW_TRANSFER_TO_SELF, "false"), - new NameValuePair(TransferCommons.PARAM_VERSION_EDITION, fromVersion.getEdition()), - new NameValuePair(TransferCommons.PARAM_VERSION_MAJOR, fromVersion.getVersionMajor()), - new NameValuePair(TransferCommons.PARAM_VERSION_MINOR, fromVersion.getVersionMinor()), - new NameValuePair(TransferCommons.PARAM_VERSION_REVISION, fromVersion.getVersionRevision()) - }; - - //add the parameter defining the root of the transfer on the file system if exist - NodeRef transferRootNode = this.getFileTransferRootNodeRef(target.getNodeRef()); - if (transferRootNode != null) - { - //add the parameter - ArrayList nameValuePairArrayList= new ArrayList(nameValuePair.length + 1); - Collections.addAll(nameValuePairArrayList,nameValuePair); - nameValuePairArrayList.add(new NameValuePair(TransferCommons.PARAM_ROOT_FILE_TRANSFER, transferRootNode.toString())); - nameValuePair = nameValuePairArrayList.toArray(new NameValuePair[0]); - } - - beginRequest.setRequestBody(nameValuePair); - - int responseStatus = httpClient.executeMethod(hostConfig, beginRequest, httpState); - - checkResponseStatus("begin", responseStatus, beginRequest); - //If we get here then we've received a 200 response - //We're expecting the transfer id encoded in a JSON object... - JSONObject response = new JSONObject(beginRequest.getResponseBodyAsString()); - - Transfer transfer = new Transfer(); - transfer.setTransferTarget(target); - - String transferId = response.getString(TransferCommons.PARAM_TRANSFER_ID); - transfer.setTransferId(transferId); - - if(response.has(TransferCommons.PARAM_VERSION_MAJOR)) - { - String versionMajor = response.getString(TransferCommons.PARAM_VERSION_MAJOR); - String versionMinor = response.getString(TransferCommons.PARAM_VERSION_MINOR); - String versionRevision = response.getString(TransferCommons.PARAM_VERSION_REVISION); - String edition = response.getString(TransferCommons.PARAM_VERSION_EDITION); - TransferVersion version = new TransferVersionImpl(versionMajor, versionMinor, versionRevision, edition); - transfer.setToVersion(version); - } - else - { - TransferVersion version = new TransferVersionImpl("0", "0", "0", "Unknown"); - transfer.setToVersion(version); - } - - if(log.isDebugEnabled()) - { - log.debug("begin transfer transferId:" + transferId +", target:" + target); - } - - return transfer; - } - catch (RuntimeException e) - { - log.debug("unexpected exception", e); - throw e; - } - catch (Exception e) - { - String error = "Failed to execute HTTP request to target"; - log.debug(error, e); - throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[] {"begin", target.toString(), e.toString()}, e); - } - } - finally - { - log.debug("releasing connection"); - beginRequest.releaseConnection(); - } - } - - public void sendManifest(Transfer transfer, File manifest, OutputStream result) throws TransferException - { - TransferTarget target = transfer.getTransferTarget(); - PostMethod postSnapshotRequest = getPostMethod(); - MultipartRequestEntity requestEntity; - - if(log.isDebugEnabled()) - { - log.debug("does manifest exist? " + manifest.exists()); - log.debug("sendManifest file : " + manifest.getAbsoluteFile()); - } - - - try - { - HostConfiguration hostConfig = getHostConfig(target); - HttpState httpState = getHttpState(target); - - try - { - postSnapshotRequest.setPath(target.getEndpointPath() + "/post-snapshot"); - - //Put the transferId on the query string - postSnapshotRequest.setQueryString( - new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); - - //TODO encapsulate the name of the manifest part - //And add the manifest file as a "part" - Part file = new FilePart(TransferCommons.PART_NAME_MANIFEST, manifest); - requestEntity = new MultipartRequestEntity(new Part[] {file}, postSnapshotRequest.getParams()); - postSnapshotRequest.setRequestEntity(requestEntity); - - int responseStatus = httpClient.executeMethod(hostConfig, postSnapshotRequest, httpState); - checkResponseStatus("sendManifest", responseStatus, postSnapshotRequest); - - InputStream is = postSnapshotRequest.getResponseBodyAsStream(); - - final ReadableByteChannel inputChannel = Channels.newChannel(is); - final WritableByteChannel outputChannel = Channels.newChannel(result); - try - { - // copy the channels - channelCopy(inputChannel, outputChannel); - } - finally - { - inputChannel.close(); - outputChannel.close(); - } - - return; - } - catch (RuntimeException e) - { - throw e; - } - catch (Exception e) - { - String error = "Failed to execute HTTP request to target"; - log.debug(error, e); - throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"sendManifest", target.toString(), e.toString()}, e); - } - } - finally - { - postSnapshotRequest.releaseConnection(); - } - } - - public void abort(Transfer transfer) throws TransferException - { - TransferTarget target = transfer.getTransferTarget(); - HttpMethod abortRequest = getPostMethod(); - try - { - HostConfiguration hostConfig = getHostConfig(target); - HttpState httpState = getHttpState(target); - - abortRequest.setPath(target.getEndpointPath() + "/abort"); - //Put the transferId on the query string - abortRequest.setQueryString( - new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); - - try - { - int responseStatus = httpClient.executeMethod(hostConfig, abortRequest, httpState); - checkResponseStatus("abort", responseStatus, abortRequest); - //If we get here then we've received a 200 response - //We're expecting the transfer id encoded in a JSON object... - } - catch (RuntimeException e) - { - throw e; - } - catch (Exception e) - { - String error = "Failed to execute HTTP request to target"; - log.debug(error, e); - throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"abort", target.toString(), e.toString()}, e); - } - } - finally - { - abortRequest.releaseConnection(); - } - } - - public void commit(Transfer transfer) throws TransferException - { - TransferTarget target = transfer.getTransferTarget(); - HttpMethod commitRequest = getPostMethod(); - try - { - HostConfiguration hostConfig = getHostConfig(target); - HttpState httpState = getHttpState(target); - - commitRequest.setPath(target.getEndpointPath() + "/commit"); - //Put the transferId on the query string - commitRequest.setQueryString( - new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); - try - { - int responseStatus = httpClient.executeMethod(hostConfig, commitRequest, httpState); - checkResponseStatus("commit", responseStatus, commitRequest); - //If we get here then we've received a 200 response - //We're expecting the transfer id encoded in a JSON object... - } - catch (RuntimeException e) - { - throw e; - } - catch (Exception e) - { - String error = "Failed to execute HTTP request to target"; - log.error(error, e); - throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"commit", target.toString(), e.toString()}, e); - } - } - finally - { - commitRequest.releaseConnection(); - } - } - - public void prepare(Transfer transfer) throws TransferException - { - TransferTarget target = transfer.getTransferTarget(); - HttpMethod prepareRequest = getPostMethod(); - try - { - HostConfiguration hostConfig = getHostConfig(target); - HttpState httpState = getHttpState(target); - - prepareRequest.setPath(target.getEndpointPath() + "/prepare"); - //Put the transferId on the query string - prepareRequest.setQueryString( - new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); - try - { - int responseStatus = httpClient.executeMethod(hostConfig, prepareRequest, httpState); - checkResponseStatus("prepare", responseStatus, prepareRequest); - //If we get here then we've received a 200 response - //We're expecting the transfer id encoded in a JSON object... - } - catch (RuntimeException e) - { - throw e; - } - catch (Exception e) - { - String error = "Failed to execute HTTP request to target"; - log.debug(error, e); - throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"prepare", target.toString(), e.toString()}, e); - } - } - finally - { - prepareRequest.releaseConnection(); - } - } - - /** - * - */ - public void sendContent(Transfer transfer, Set data) throws TransferException - { - if(log.isDebugEnabled()) - { - log.debug("send content to transfer:" + transfer); - } - - TransferTarget target = transfer.getTransferTarget(); - PostMethod postContentRequest = getPostMethod(); - - try - { - HostConfiguration hostConfig = getHostConfig(target); - HttpState httpState = getHttpState(target); - - try - { - postContentRequest.setPath(target.getEndpointPath() + "/post-content"); - //Put the transferId on the query string - postContentRequest.setQueryString( - new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); - - //Put the transferId on the query string - postContentRequest.setQueryString( - new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); - - Part[] parts = new Part[data.size()]; - - int index = 0; - for(ContentData content : data) - { - String contentUrl = content.getContentUrl(); - String fileName = TransferCommons.URLToPartName(contentUrl); - log.debug("content partName: " + fileName); - - parts[index++] = new ContentDataPart(getContentService(), fileName, content); - } - - MultipartRequestEntity requestEntity = new MultipartRequestEntity(parts, postContentRequest.getParams()); - postContentRequest.setRequestEntity(requestEntity); - - int responseStatus = httpClient.executeMethod(hostConfig, postContentRequest, httpState); - checkResponseStatus("sendContent", responseStatus, postContentRequest); - - if(log.isDebugEnabled()) - { - log.debug("sent content"); - } - - } - catch (RuntimeException e) - { - throw e; - } - catch (Exception e) - { - String error = "Failed to execute HTTP request to target"; - log.debug(error, e); - throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"sendContent", target.toString(), e.toString()}, e); - } - } - finally - { - postContentRequest.releaseConnection(); - } - } // end of sendContent - - /** - * - */ - public TransferProgress getStatus(Transfer transfer) throws TransferException - { - TransferTarget target = transfer.getTransferTarget(); - HttpMethod statusRequest = getPostMethod(); - try - { - HostConfiguration hostConfig = getHostConfig(target); - HttpState httpState = getHttpState(target); - - statusRequest.setPath(target.getEndpointPath() + "/status"); - //Put the transferId on the query string - statusRequest.setQueryString( - new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); - - try - { - int responseStatus = httpClient.executeMethod(hostConfig, statusRequest, httpState); - checkResponseStatus("status", responseStatus, statusRequest); - //If we get here then we've received a 200 response - String statusPayload = statusRequest.getResponseBodyAsString(); - JSONObject statusObj = new JSONObject(statusPayload); - //We're expecting the transfer progress encoded in a JSON object... - int currentPosition = statusObj.getInt("currentPosition"); - int endPosition = statusObj.getInt("endPosition"); - String statusStr= statusObj.getString("status"); - - TransferProgress p = new TransferProgress(); - - if(statusObj.has("error")) - { - JSONObject errorJSON = statusObj.getJSONObject("error"); - Throwable throwable = rehydrateError(errorJSON); - p.setError(throwable); - } - - p.setStatus(TransferProgress.Status.valueOf(statusStr)); - p.setCurrentPosition(currentPosition); - p.setEndPosition(endPosition); - - return p; - } - catch (RuntimeException e) - { - throw e; - } - catch (Exception e) - { - String error = "Failed to execute HTTP request to target"; - log.debug(error, e); - throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"status", target.toString(), e.toString()}, e); - } - } - finally - { - statusRequest.releaseConnection(); - } - } - - /** - * - */ - public void getTransferReport(Transfer transfer, OutputStream result) - { - TransferTarget target = transfer.getTransferTarget(); - PostMethod getReportRequest = getPostMethod(); - try - { - HostConfiguration hostConfig = getHostConfig(target); - HttpState httpState = getHttpState(target); - - try - { - getReportRequest.setPath(target.getEndpointPath() + "/report"); - - //Put the transferId on the query string - getReportRequest.setQueryString( - new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); - - int responseStatus = httpClient.executeMethod(hostConfig, getReportRequest, httpState); - checkResponseStatus("getReport", responseStatus, getReportRequest); - - InputStream is = getReportRequest.getResponseBodyAsStream(); - - // Now copy the response input stream to result. - final ReadableByteChannel inputChannel = Channels.newChannel(is); - final WritableByteChannel outputChannel = Channels.newChannel(result); - try - { - // copy the channels - channelCopy(inputChannel, outputChannel); - } - finally - { - // closing the channels - inputChannel.close(); - outputChannel.close(); - } - - return; - } - catch (RuntimeException e) - { - throw e; - } - catch (Exception e) - { - String error = "Failed to execute HTTP request to target"; - log.debug(error, e); - throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"getTransferReport", target.toString(), e.toString()}, e); - } - } - finally - { - getReportRequest.releaseConnection(); - } - } - - private static void channelCopy(final ReadableByteChannel src, final WritableByteChannel dest) throws IOException - { - final ByteBuffer buffer = ByteBuffer.allocateDirect(2 * 1024); - while (src.read(buffer) != -1) - { - // prepare the buffer to be drained - buffer.flip(); - // write to the channel, may block - dest.write(buffer); - - // If partial transfer, shift remainder down - // If buffer is empty, same as doing clear() - buffer.compact(); - } - - // EOF will leave buffer in fill state - buffer.flip(); - - // make sure the buffer is fully drained. - while (buffer.hasRemaining()) - { - dest.write(buffer); - } - } - - protected PostMethod getPostMethod() - { - return httpMethodFactory.createPostMethod(); - } - - /** - * - * @param errorJSON A JSON object expected to hold the name of the error class ("errorType"), - * the error message ("errorMessage"), and, optionally, the Alfresco message id ("alfrescoErrorId") - * and Alfresco message parameters ("alfrescoErrorParams"). - * @return The rehydrated error object, or null if errorJSON is null. + */ + private HostConfiguration getHostConfig(TransferTarget target) + { + String requiredProtocol = target.getEndpointProtocol(); + if (requiredProtocol == null) + { + throw new TransferException(MSG_UNSUPPORTED_PROTOCOL, new Object[] {requiredProtocol}); + } + + Protocol protocol = protocolMap.get(requiredProtocol.toLowerCase().trim()); + if (protocol == null) + { + log.error("Unsupported protocol: " + target.getEndpointProtocol()); + throw new TransferException(MSG_UNSUPPORTED_PROTOCOL, new Object[] {requiredProtocol}); + } + + HostConfiguration hostConfig = new HostConfiguration(); + hostConfig.setHost(target.getEndpointHost(), target.getEndpointPort(), protocol); + + // Use the appropriate Proxy Host if required + if (httpProxyHost != null && HTTP_SCHEME_NAME.equals(protocol.getScheme()) && HttpClientHelper.requiresProxy(target.getEndpointHost())) + { + hostConfig.setProxyHost(httpProxyHost); + + if (log.isDebugEnabled()) + { + log.debug("Using HTTP proxy host for: " + target.getEndpointHost()); + } + } + else if (httpsProxyHost != null && HTTPS_SCHEME_NAME.equals(protocol.getScheme()) && HttpClientHelper.requiresProxy(target.getEndpointHost())) + { + hostConfig.setProxyHost(httpsProxyHost); + + if (log.isDebugEnabled()) + { + log.debug("Using HTTPS proxy host for: " + target.getEndpointHost()); + } + } + return hostConfig; + } + + /** + * Create suitable AuthScope for ProxyHost. If the ProxyHost is null, no AuthsScope will be created. + * @param proxyHost ProxyHost + * @return AuthScope for provided ProxyHost, null otherwise. + */ + private AuthScope createProxyAuthScope(final ProxyHost proxyHost) + { + AuthScope authScope = null; + if (proxyHost != null) + { + authScope = new AuthScope(proxyHost.getHostName(), proxyHost.getPort()); + } + return authScope; + } + + public Transfer begin(TransferTarget target, String fromRepositoryId, TransferVersion fromVersion) throws TransferException + { + PostMethod beginRequest = getPostMethod(); + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + beginRequest.setPath(target.getEndpointPath() + "/begin"); + try + { + NameValuePair[] nameValuePair = new NameValuePair[] { + new NameValuePair(TransferCommons.PARAM_FROM_REPOSITORYID, fromRepositoryId), + new NameValuePair(TransferCommons.PARAM_ALLOW_TRANSFER_TO_SELF, "false"), + new NameValuePair(TransferCommons.PARAM_VERSION_EDITION, fromVersion.getEdition()), + new NameValuePair(TransferCommons.PARAM_VERSION_MAJOR, fromVersion.getVersionMajor()), + new NameValuePair(TransferCommons.PARAM_VERSION_MINOR, fromVersion.getVersionMinor()), + new NameValuePair(TransferCommons.PARAM_VERSION_REVISION, fromVersion.getVersionRevision()) + }; + + //add the parameter defining the root of the transfer on the file system if exist + NodeRef transferRootNode = this.getFileTransferRootNodeRef(target.getNodeRef()); + if (transferRootNode != null) + { + //add the parameter + ArrayList nameValuePairArrayList= new ArrayList(nameValuePair.length + 1); + Collections.addAll(nameValuePairArrayList,nameValuePair); + nameValuePairArrayList.add(new NameValuePair(TransferCommons.PARAM_ROOT_FILE_TRANSFER, transferRootNode.toString())); + nameValuePair = nameValuePairArrayList.toArray(new NameValuePair[0]); + } + + beginRequest.setRequestBody(nameValuePair); + + int responseStatus = httpClient.executeMethod(hostConfig, beginRequest, httpState); + + checkResponseStatus("begin", responseStatus, beginRequest); + //If we get here then we've received a 200 response + //We're expecting the transfer id encoded in a JSON object... + JSONObject response = new JSONObject(beginRequest.getResponseBodyAsString()); + + Transfer transfer = new Transfer(); + transfer.setTransferTarget(target); + + String transferId = response.getString(TransferCommons.PARAM_TRANSFER_ID); + transfer.setTransferId(transferId); + + if(response.has(TransferCommons.PARAM_VERSION_MAJOR)) + { + String versionMajor = response.getString(TransferCommons.PARAM_VERSION_MAJOR); + String versionMinor = response.getString(TransferCommons.PARAM_VERSION_MINOR); + String versionRevision = response.getString(TransferCommons.PARAM_VERSION_REVISION); + String edition = response.getString(TransferCommons.PARAM_VERSION_EDITION); + TransferVersion version = new TransferVersionImpl(versionMajor, versionMinor, versionRevision, edition); + transfer.setToVersion(version); + } + else + { + TransferVersion version = new TransferVersionImpl("0", "0", "0", "Unknown"); + transfer.setToVersion(version); + } + + if(log.isDebugEnabled()) + { + log.debug("begin transfer transferId:" + transferId +", target:" + target); + } + + return transfer; + } + catch (RuntimeException e) + { + log.debug("unexpected exception", e); + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.debug(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[] {"begin", target.toString(), e.toString()}, e); + } + } + finally + { + log.debug("releasing connection"); + beginRequest.releaseConnection(); + } + } + + public void sendManifest(Transfer transfer, File manifest, OutputStream result) throws TransferException + { + TransferTarget target = transfer.getTransferTarget(); + PostMethod postSnapshotRequest = getPostMethod(); + MultipartRequestEntity requestEntity; + + if(log.isDebugEnabled()) + { + log.debug("does manifest exist? " + manifest.exists()); + log.debug("sendManifest file : " + manifest.getAbsoluteFile()); + } + + + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + try + { + postSnapshotRequest.setPath(target.getEndpointPath() + "/post-snapshot"); + + //Put the transferId on the query string + postSnapshotRequest.setQueryString( + new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); + + //TODO encapsulate the name of the manifest part + //And add the manifest file as a "part" + Part file = new FilePart(TransferCommons.PART_NAME_MANIFEST, manifest); + requestEntity = new MultipartRequestEntity(new Part[] {file}, postSnapshotRequest.getParams()); + postSnapshotRequest.setRequestEntity(requestEntity); + + int responseStatus = httpClient.executeMethod(hostConfig, postSnapshotRequest, httpState); + checkResponseStatus("sendManifest", responseStatus, postSnapshotRequest); + + InputStream is = postSnapshotRequest.getResponseBodyAsStream(); + + final ReadableByteChannel inputChannel = Channels.newChannel(is); + final WritableByteChannel outputChannel = Channels.newChannel(result); + try + { + // copy the channels + channelCopy(inputChannel, outputChannel); + } + finally + { + inputChannel.close(); + outputChannel.close(); + } + + return; + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.debug(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"sendManifest", target.toString(), e.toString()}, e); + } + } + finally + { + postSnapshotRequest.releaseConnection(); + } + } + + public void abort(Transfer transfer) throws TransferException + { + TransferTarget target = transfer.getTransferTarget(); + HttpMethod abortRequest = getPostMethod(); + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + abortRequest.setPath(target.getEndpointPath() + "/abort"); + //Put the transferId on the query string + abortRequest.setQueryString( + new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); + + try + { + int responseStatus = httpClient.executeMethod(hostConfig, abortRequest, httpState); + checkResponseStatus("abort", responseStatus, abortRequest); + //If we get here then we've received a 200 response + //We're expecting the transfer id encoded in a JSON object... + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.debug(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"abort", target.toString(), e.toString()}, e); + } + } + finally + { + abortRequest.releaseConnection(); + } + } + + public void commit(Transfer transfer) throws TransferException + { + TransferTarget target = transfer.getTransferTarget(); + HttpMethod commitRequest = getPostMethod(); + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + commitRequest.setPath(target.getEndpointPath() + "/commit"); + //Put the transferId on the query string + commitRequest.setQueryString( + new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); + try + { + int responseStatus = httpClient.executeMethod(hostConfig, commitRequest, httpState); + checkResponseStatus("commit", responseStatus, commitRequest); + //If we get here then we've received a 200 response + //We're expecting the transfer id encoded in a JSON object... + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.error(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"commit", target.toString(), e.toString()}, e); + } + } + finally + { + commitRequest.releaseConnection(); + } + } + + public void prepare(Transfer transfer) throws TransferException + { + TransferTarget target = transfer.getTransferTarget(); + HttpMethod prepareRequest = getPostMethod(); + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + prepareRequest.setPath(target.getEndpointPath() + "/prepare"); + //Put the transferId on the query string + prepareRequest.setQueryString( + new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); + try + { + int responseStatus = httpClient.executeMethod(hostConfig, prepareRequest, httpState); + checkResponseStatus("prepare", responseStatus, prepareRequest); + //If we get here then we've received a 200 response + //We're expecting the transfer id encoded in a JSON object... + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.debug(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"prepare", target.toString(), e.toString()}, e); + } + } + finally + { + prepareRequest.releaseConnection(); + } + } + + /** + * + */ + public void sendContent(Transfer transfer, Set data) throws TransferException + { + if(log.isDebugEnabled()) + { + log.debug("send content to transfer:" + transfer); + } + + TransferTarget target = transfer.getTransferTarget(); + PostMethod postContentRequest = getPostMethod(); + + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + try + { + postContentRequest.setPath(target.getEndpointPath() + "/post-content"); + //Put the transferId on the query string + postContentRequest.setQueryString( + new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); + + //Put the transferId on the query string + postContentRequest.setQueryString( + new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); + + Part[] parts = new Part[data.size()]; + + int index = 0; + for(ContentData content : data) + { + String contentUrl = content.getContentUrl(); + String fileName = TransferCommons.URLToPartName(contentUrl); + log.debug("content partName: " + fileName); + + parts[index++] = new ContentDataPart(getContentService(), fileName, content); + } + + MultipartRequestEntity requestEntity = new MultipartRequestEntity(parts, postContentRequest.getParams()); + postContentRequest.setRequestEntity(requestEntity); + + int responseStatus = httpClient.executeMethod(hostConfig, postContentRequest, httpState); + checkResponseStatus("sendContent", responseStatus, postContentRequest); + + if(log.isDebugEnabled()) + { + log.debug("sent content"); + } + + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.debug(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"sendContent", target.toString(), e.toString()}, e); + } + } + finally + { + postContentRequest.releaseConnection(); + } + } // end of sendContent + + /** + * + */ + public TransferProgress getStatus(Transfer transfer) throws TransferException + { + TransferTarget target = transfer.getTransferTarget(); + HttpMethod statusRequest = getPostMethod(); + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + statusRequest.setPath(target.getEndpointPath() + "/status"); + //Put the transferId on the query string + statusRequest.setQueryString( + new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); + + try + { + int responseStatus = httpClient.executeMethod(hostConfig, statusRequest, httpState); + checkResponseStatus("status", responseStatus, statusRequest); + //If we get here then we've received a 200 response + String statusPayload = statusRequest.getResponseBodyAsString(); + JSONObject statusObj = new JSONObject(statusPayload); + //We're expecting the transfer progress encoded in a JSON object... + int currentPosition = statusObj.getInt("currentPosition"); + int endPosition = statusObj.getInt("endPosition"); + String statusStr= statusObj.getString("status"); + + TransferProgress p = new TransferProgress(); + + if(statusObj.has("error")) + { + JSONObject errorJSON = statusObj.getJSONObject("error"); + Throwable throwable = rehydrateError(errorJSON); + p.setError(throwable); + } + + p.setStatus(TransferProgress.Status.valueOf(statusStr)); + p.setCurrentPosition(currentPosition); + p.setEndPosition(endPosition); + + return p; + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.debug(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"status", target.toString(), e.toString()}, e); + } + } + finally + { + statusRequest.releaseConnection(); + } + } + + /** + * + */ + public void getTransferReport(Transfer transfer, OutputStream result) + { + TransferTarget target = transfer.getTransferTarget(); + PostMethod getReportRequest = getPostMethod(); + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + try + { + getReportRequest.setPath(target.getEndpointPath() + "/report"); + + //Put the transferId on the query string + getReportRequest.setQueryString( + new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); + + int responseStatus = httpClient.executeMethod(hostConfig, getReportRequest, httpState); + checkResponseStatus("getReport", responseStatus, getReportRequest); + + InputStream is = getReportRequest.getResponseBodyAsStream(); + + // Now copy the response input stream to result. + final ReadableByteChannel inputChannel = Channels.newChannel(is); + final WritableByteChannel outputChannel = Channels.newChannel(result); + try + { + // copy the channels + channelCopy(inputChannel, outputChannel); + } + finally + { + // closing the channels + inputChannel.close(); + outputChannel.close(); + } + + return; + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.debug(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"getTransferReport", target.toString(), e.toString()}, e); + } + } + finally + { + getReportRequest.releaseConnection(); + } + } + + private static void channelCopy(final ReadableByteChannel src, final WritableByteChannel dest) throws IOException + { + final ByteBuffer buffer = ByteBuffer.allocateDirect(2 * 1024); + while (src.read(buffer) != -1) + { + // prepare the buffer to be drained + buffer.flip(); + // write to the channel, may block + dest.write(buffer); + + // If partial transfer, shift remainder down + // If buffer is empty, same as doing clear() + buffer.compact(); + } + + // EOF will leave buffer in fill state + buffer.flip(); + + // make sure the buffer is fully drained. + while (buffer.hasRemaining()) + { + dest.write(buffer); + } + } + + protected PostMethod getPostMethod() + { + return httpMethodFactory.createPostMethod(); + } + + /** + * + * @param errorJSON A JSON object expected to hold the name of the error class ("errorType"), + * the error message ("errorMessage"), and, optionally, the Alfresco message id ("alfrescoErrorId") + * and Alfresco message parameters ("alfrescoErrorParams"). + * @return The rehydrated error object, or null if errorJSON is null. * Throws {@code JSONException} if an error occurs while parsing the supplied JSON object - */ - private Throwable rehydrateError(JSONObject errorJSON) - { - return jsonErrorSerializer.deserialize(errorJSON); - } - - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - public ContentService getContentService() - { - return contentService; - } - - public void setHttpMethodFactory(HttpMethodFactory httpMethodFactory) - { - this.httpMethodFactory = httpMethodFactory; - } - - public void setJsonErrorSerializer(JsonSerializer jsonErrorSerializer) - { - this.jsonErrorSerializer = jsonErrorSerializer; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - private NodeRef getFileTransferRootNodeRef(NodeRef transferNodeRef) - { - //testing if transferring to file system - if(!TransferModel.TYPE_FILE_TRANSFER_TARGET.equals(nodeService.getType(transferNodeRef))) - return null; - - //get association - List assocs = nodeService.getTargetAssocs(transferNodeRef, TransferModel.ASSOC_ROOT_FILE_TRANSFER); - if(assocs.size() == 0 || assocs.size() > 1) - return null; - - return assocs.get(0).getTargetRef(); - } - - -} + */ + private Throwable rehydrateError(JSONObject errorJSON) + { + return jsonErrorSerializer.deserialize(errorJSON); + } + + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + public ContentService getContentService() + { + return contentService; + } + + public void setHttpMethodFactory(HttpMethodFactory httpMethodFactory) + { + this.httpMethodFactory = httpMethodFactory; + } + + public void setJsonErrorSerializer(JsonSerializer jsonErrorSerializer) + { + this.jsonErrorSerializer = jsonErrorSerializer; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + private NodeRef getFileTransferRootNodeRef(NodeRef transferNodeRef) + { + //testing if transferring to file system + if(!TransferModel.TYPE_FILE_TRANSFER_TARGET.equals(nodeService.getType(transferNodeRef))) + return null; + + //get association + List assocs = nodeService.getTargetAssocs(transferNodeRef, TransferModel.ASSOC_ROOT_FILE_TRANSFER); + if(assocs.size() == 0 || assocs.size() > 1) + return null; + + return assocs.get(0).getTargetRef(); + } + + +} diff --git a/source/java/org/alfresco/util/HttpClientHelper.java b/source/java/org/alfresco/util/HttpClientHelper.java index 885395658e..47a3da69b9 100644 --- a/source/java/org/alfresco/util/HttpClientHelper.java +++ b/source/java/org/alfresco/util/HttpClientHelper.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2017 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -25,10 +25,16 @@ */ package org.alfresco.util; -import org.alfresco.httpclient.HttpClientFactory; -import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import java.util.StringTokenizer; + +import org.alfresco.httpclient.HttpClientFactory; +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.ProxyHost; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.extensions.webscripts.connector.RemoteClient; /** @@ -39,7 +45,20 @@ import org.springframework.extensions.webscripts.connector.RemoteClient; * otherwise things will break for the next request in this thread! * * TODO Merge me back to Spring Surf, which is where this code has been - * pulled out from (was in {@link RemoteClient} but not available externally) + * pulled out from (was in {@link RemoteClient} but not available externally) + * + * This class also provides support for creating proxy configurations, + * taking into account System properties like: + *

+ *
    + *
  • http.proxyHost
  • + *
  • http.proxyPort
  • + *
  • http.nonProxyHosts
  • + *
  • http.proxyUser
  • + *
  • http.proxyPassword
  • + *
+ *

+ * */ public class HttpClientHelper { @@ -63,5 +82,84 @@ public class HttpClientHelper public static HttpClient getHttpClient() { return httpClient.get(); + } + + /** + * Create proxy host for the given system host and port properties. + * If the properties are not set, no proxy will be created. + * + * @param hostProperty the name of the system property for the proxy server (http.proxyHost or https.proxyHost) + * @param portProperty the name of the system property for the proxy server port (http.proxyPort) + * @param defaultPort + * + * @return ProxyHost if appropriate properties have been set, null otherwise + */ + public static ProxyHost createProxyHost(final String hostProperty, final String portProperty, final int defaultPort) + { + final String proxyHost = System.getProperty(hostProperty); + ProxyHost proxy = null; + if (proxyHost != null && proxyHost.length() != 0) + { + final String strProxyPort = System.getProperty(portProperty); + if (strProxyPort == null || strProxyPort.length() == 0) + { + proxy = new ProxyHost(proxyHost, defaultPort); + } + else + { + proxy = new ProxyHost(proxyHost, Integer.parseInt(strProxyPort)); + } + if (logger.isDebugEnabled()) + { + logger.debug("ProxyHost: " + proxy.toString()); + } + } + return proxy; + } + + /** + * Create the proxy credentials for the given proxy user and password properties. + * If the properties are not set, not credentials will be created. + * @param proxyUserProperty the name of the system property for the proxy user + * @param proxyPasswordProperty the name of the system property for the proxy password + * @return Credentials if appropriate properties have been set, null otherwise + */ + public static Credentials createProxyCredentials(final String proxyUserProperty, final String proxyPasswordProperty) + { + final String proxyUser = System.getProperty(proxyUserProperty); + final String proxyPassword = System.getProperty(proxyPasswordProperty); + Credentials credentials = null; + if (StringUtils.isNotBlank(proxyUser)) + { + credentials = new UsernamePasswordCredentials(proxyUser, proxyPassword); + } + return credentials; + } + + /** + * Return true unless the given target host is specified in the http.nonProxyHosts system property. + * See http://docs.oracle.com/javase/8/docs/api/java/net/doc-files/net-properties.html + * @param targetHost Non-null host name to verify + * @return true if not specified in the list, false if it is specified and therefore should be excluded from proxy + */ + public static boolean requiresProxy(final String targetHost) + { + boolean requiresProxy = true; + final String nonProxyHosts = System.getProperty("http.nonProxyHosts"); + if (nonProxyHosts != null) + { + StringTokenizer tokenizer = new StringTokenizer(nonProxyHosts, "|"); + while (tokenizer.hasMoreTokens()) + { + String pattern = tokenizer.nextToken(); + pattern = pattern.replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*"); + if (targetHost.matches(pattern)) + { + requiresProxy = false; + break; + } + } + } + return requiresProxy; } }