org.alfresco.repo.transfer.HttpClientTransmitterImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.transfer.HttpClientTransmitterImpl.java

Source

/*
 * #%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 <http://www.gnu.org/licenses/>.
 * #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<String, Protocol> protocolMap = null;
    private HttpMethodFactory httpMethodFactory = null;
    private JsonSerializer<Throwable, JSONObject> jsonErrorSerializer;

    private ContentService contentService;

    private NodeService nodeService;
    private boolean isAuthenticationPreemptive = false;

    public HttpClientTransmitterImpl() {
        protocolMap = new TreeMap<String, Protocol>();
        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);
        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.
     * @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
     * @param httpClient HttpClient
     */
    public void setHttpClient(HttpClient httpClient) {
        this.httpClient = httpClient;
    }

    /**
     * Whether httpClient will use preemptive authentication or not.
     * @param isAuthenticationPreemptive boolean
     */
    public void setIsAuthenticationPreemptive(boolean isAuthenticationPreemptive) {
        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();
        }
    }

    /**
     *
     * @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
     * @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;
    }

    /**
     * @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<NameValuePair> nameValuePairArrayList = new ArrayList<NameValuePair>(
                            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<ContentData> 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<Throwable, JSONObject> 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<AssociationRef> assocs = nodeService.getTargetAssocs(transferNodeRef,
                TransferModel.ASSOC_ROOT_FILE_TRANSFER);
        if (assocs.size() == 0 || assocs.size() > 1)
            return null;

        return assocs.get(0).getTargetRef();
    }

}