com.smartsheet.api.internal.AbstractResources.java Source code

Java tutorial

Introduction

Here is the source code for com.smartsheet.api.internal.AbstractResources.java

Source

package com.smartsheet.api.internal;

/*
 * #[license]
 * Smartsheet SDK for Java
 * %%
 * Copyright (C) 2018 Smartsheet
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * %[license]
 */

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.smartsheet.api.AuthorizationException;
import com.smartsheet.api.InvalidRequestException;
import com.smartsheet.api.ResourceNotFoundException;
import com.smartsheet.api.ServiceUnavailableException;
import com.smartsheet.api.SmartsheetException;
import com.smartsheet.api.SmartsheetRestException;
import com.smartsheet.api.internal.http.HttpEntity;
import com.smartsheet.api.internal.http.HttpMethod;
import com.smartsheet.api.internal.http.HttpRequest;
import com.smartsheet.api.internal.http.HttpResponse;
import com.smartsheet.api.internal.json.JSONSerializerException;
import com.smartsheet.api.internal.util.StreamUtil;
import com.smartsheet.api.internal.util.Util;
import com.smartsheet.api.models.Attachment;
import com.smartsheet.api.models.CopyOrMoveRowDirective;
import com.smartsheet.api.models.CopyOrMoveRowResult;
import com.smartsheet.api.models.PagedResult;
import com.smartsheet.api.models.Result;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This is the base class of the Smartsheet REST API resources.
 * 
 * Thread Safety: This class is thread safe because it is immutable and the underlying SmartsheetImpl is thread safe.
 */
public abstract class AbstractResources {
    /** this system property is used to control the number of characters logged from an API response in logs */
    public static final String PROPERTY_RESPONSE_LOG_CHARS = "Smartsheet.responseLogChars";

    private static final Logger log = LoggerFactory.getLogger(AbstractResources.class);

    /** The Constant BUFFER_SIZE. */
    private final static int BUFFER_SIZE = 4098;

    /**
     * The Enum ErrorCode.
     */
    public enum ErrorCode {
        BAD_REQUEST(400, InvalidRequestException.class), NOT_AUTHORIZED(401,
                AuthorizationException.class), FORBIDDEN(403, AuthorizationException.class), NOT_FOUND(404,
                        ResourceNotFoundException.class), METHOD_NOT_SUPPORTED(405,
                                InvalidRequestException.class), INTERNAL_SERVER_ERROR(500,
                                        InvalidRequestException.class), SERVICE_UNAVAILABLE(503,
                                                ServiceUnavailableException.class);

        /** The error code. */
        int errorCode;

        /** The Exception class. */
        Class<? extends SmartsheetRestException> exceptionClass;

        /**
         * Instantiates a new error code.
         *
         * @param errorCode the error code
         * @param exceptionClass the Exception class
         */
        ErrorCode(int errorCode, Class<? extends SmartsheetRestException> exceptionClass) {
            this.errorCode = errorCode;
            this.exceptionClass = exceptionClass;
        }

        /**
         * Gets the error code.
         *
         * @param errorNumber the error number
         * @return the error code
         */
        public static ErrorCode getErrorCode(int errorNumber) {
            for (ErrorCode code : ErrorCode.values()) {
                if (code.errorCode == errorNumber) {
                    return code;
                }
            }

            return null;
        }

        /**
         * Gets the exception.
         *
         * @return the exception
         * @throws InstantiationException the instantiation exception
         * @throws IllegalAccessException the illegal access exception
         */
        public SmartsheetRestException getException() throws InstantiationException, IllegalAccessException {
            return exceptionClass.newInstance();
        }

        /**
         * Gets the exception.
         *
         * @param error the error
         * @return the exception
         * @throws SmartsheetException the smartsheet exception
         */
        public SmartsheetRestException getException(com.smartsheet.api.models.Error error)
                throws SmartsheetException {

            try {
                return exceptionClass.getConstructor(com.smartsheet.api.models.Error.class).newInstance(error);
            } catch (IllegalArgumentException e) {
                throw new SmartsheetException(e);
            } catch (SecurityException e) {
                throw new SmartsheetException(e);
            } catch (InstantiationException e) {
                throw new SmartsheetException(e);
            } catch (IllegalAccessException e) {
                throw new SmartsheetException(e);
            } catch (InvocationTargetException e) {
                throw new SmartsheetException(e);
            } catch (NoSuchMethodException e) {
                throw new SmartsheetException(e);
            }
        }
    }

    /**
     * Represents the SmartsheetImpl.
     *
     * It will be initialized in constructor and will not change afterwards.
     */
    protected final SmartsheetImpl smartsheet;

    /**
     * Constructor.
     *
     * @param smartsheet the smartsheet
     */
    protected AbstractResources(SmartsheetImpl smartsheet) {
        Util.throwIfNull(smartsheet);

        this.smartsheet = smartsheet;
    }

    /**
     * Get a resource from Smartsheet REST API.
     *
     * Parameters: - path : the relative path of the resource - objectClass : the resource object class
     *
     * Returns: the resource (note that if there is no such resource, this method will throw ResourceNotFoundException
     * rather than returning null).
     *
     * Exceptions: -
     *   InvalidRequestException : if there is any problem with the REST API request
     *   AuthorizationException : if there is any problem with the REST API authorization(access token)
     *   ResourceNotFoundException : if the resource can not be found
     *   ServiceUnavailableException : if the REST API service is not available (possibly due to rate limiting)
     *   SmartsheetRestException : if there is any other REST API related error occurred during the operation
     *   SmartsheetException : if there is any other error occurred during the operation
     *
     * @param <T> the generic type
     * @param path the relative path of the resource.
     * @param objectClass the object class
     * @return the resource
     * @throws SmartsheetException the smartsheet exception
     */
    protected <T> T getResource(String path, Class<T> objectClass) throws SmartsheetException {
        Util.throwIfNull(path, objectClass);

        if (path.isEmpty()) {
            com.smartsheet.api.models.Error error = new com.smartsheet.api.models.Error();
            error.setMessage("An empty path was provided.");
            throw new ResourceNotFoundException(error);
        }

        HttpRequest request = createHttpRequest(smartsheet.getBaseURI().resolve(path), HttpMethod.GET);

        T obj = null;
        String content = null;
        try {
            HttpResponse response = this.smartsheet.getHttpClient().request(request);
            InputStream inputStream = response.getEntity().getContent();
            switch (response.getStatusCode()) {
            case 200:
                try {
                    if (log.isInfoEnabled()) {
                        ByteArrayOutputStream contentCopyStream = new ByteArrayOutputStream();
                        inputStream = StreamUtil.cloneContent(inputStream, response.getEntity().getContentLength(),
                                contentCopyStream);
                        content = StreamUtil.toUtf8StringOrHex(contentCopyStream, getResponseLogLength());
                    }
                    obj = this.smartsheet.getJsonSerializer().deserialize(objectClass, inputStream);
                } catch (JsonParseException e) {
                    log.info("failure parsing '{}'", content, e);
                    throw new SmartsheetException(e);
                } catch (JsonMappingException e) {
                    log.info("failure mapping '{}'", content, e);
                    throw new SmartsheetException(e);
                } catch (IOException e) {
                    log.info("failure loading '{}'", content, e);
                    throw new SmartsheetException(e);
                }
                break;
            default:
                handleError(response);
            }
        } catch (JSONSerializerException jsx) {
            log.info("failed to parse '{}'", content, jsx);
            throw jsx;
        } finally {
            smartsheet.getHttpClient().releaseConnection();
        }
        return obj;
    }

    /**
     * Create a resource using Smartsheet REST API.
     *
     * Exceptions:
     *   IllegalArgumentException : if any argument is null, or path is empty string
     *   InvalidRequestException : if there is any problem with the REST API request
     *   AuthorizationException : if there is any problem with the REST API authorization(access token)
     *   ServiceUnavailableException : if the REST API service is not available (possibly due to rate limiting)
     *   SmartsheetRestException : if there is any other REST API related error occurred during the operation
     *   SmartsheetException : if there is any other error occurred during the operation
     *
     * @param <T> the generic type of object to return/deserialize
     * @param <S> the generic type of object to serialize
     * @param path the relative path of the resource collections
     * @param objectClass the resource object class
     * @param object the object to create
     * @return the created resource
     * @throws SmartsheetException the smartsheet exception
     */
    protected <T, S> T createResource(String path, Class<T> objectClass, S object) throws SmartsheetException {
        Util.throwIfNull(path, object, objectClass);
        Util.throwIfEmpty(path);

        HttpRequest request = createHttpRequest(smartsheet.getBaseURI().resolve(path), HttpMethod.POST);

        ByteArrayOutputStream objectBytesStream = new ByteArrayOutputStream();
        this.smartsheet.getJsonSerializer().serialize(object, objectBytesStream);
        HttpEntity entity = new HttpEntity();
        entity.setContentType("application/json");
        entity.setContent(new ByteArrayInputStream(objectBytesStream.toByteArray()));
        entity.setContentLength(objectBytesStream.size());
        request.setEntity(entity);

        T obj = null;
        try {
            HttpResponse response = this.smartsheet.getHttpClient().request(request);
            switch (response.getStatusCode()) {
            case 200: {
                InputStream inputStream = response.getEntity().getContent();
                String content = null;
                try {
                    if (log.isInfoEnabled()) {
                        ByteArrayOutputStream contentCopyStream = new ByteArrayOutputStream();
                        inputStream = StreamUtil.cloneContent(inputStream, response.getEntity().getContentLength(),
                                contentCopyStream);
                        content = StreamUtil.toUtf8StringOrHex(contentCopyStream, getResponseLogLength());
                    }
                    obj = this.smartsheet.getJsonSerializer().deserializeResult(objectClass, inputStream)
                            .getResult();
                } catch (JSONSerializerException e) {
                    log.info("failure parsing '{}'", content, e);
                    throw new SmartsheetException(e);
                } catch (IOException e) {
                    log.info("failure cloning content from inputStream '{}'", inputStream, e);
                    throw new SmartsheetException(e);
                }
                break;
            }
            default:
                handleError(response);
            }
        } finally {
            smartsheet.getHttpClient().releaseConnection();
        }

        return obj;
    }

    /**
     * Create a resource using Smartsheet REST API.
     *
     * Exceptions:
     *   IllegalArgumentException : if any argument is null, or path is empty string
     *   InvalidRequestException : if there is any problem with the REST API request
     *   AuthorizationException : if there is any problem with the REST API authorization(access token)
     *   ServiceUnavailableException : if the REST API service is not available (possibly due to rate limiting)
     *   SmartsheetRestException : if there is any other REST API related error occurred during the operation
     *   SmartsheetException : if there is any other error occurred during the operation
     *
     * @param <T> the generic type
     * @param path the relative path of the resource collections
     * @param objectClass the resource object class
     * @param object the object to create
     * @return the created resource
     * @throws SmartsheetException the smartsheet exception
     */
    protected <T> T createResourceWithAttachment(String path, Class<T> objectClass, T object, String partName,
            InputStream inputStream, String contentType, String attachmentName) throws SmartsheetException {
        Util.throwIfNull(path, object);
        Util.throwIfEmpty(path);

        HttpRequest request;
        final String boundary = "----" + System.currentTimeMillis();
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost uploadFile = createHttpPost(this.getSmartsheet().getBaseURI().resolve(path));

        try {
            uploadFile.setHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        builder.setBoundary(boundary);
        builder.addTextBody(partName, this.getSmartsheet().getJsonSerializer().serialize(object),
                ContentType.APPLICATION_JSON);
        builder.addBinaryBody("file", inputStream, ContentType.create(contentType), attachmentName);
        org.apache.http.HttpEntity multipart = builder.build();

        uploadFile.setEntity(multipart);

        T obj = null;
        //implement switch case
        try {
            CloseableHttpResponse response = httpClient.execute(uploadFile);
            org.apache.http.HttpEntity responseEntity = response.getEntity();
            obj = this.getSmartsheet().getJsonSerializer()
                    .deserializeResult(objectClass, responseEntity.getContent()).getResult();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return obj;
    }

    /**
     * Update a resource using Smartsheet REST API.
     *
     * Exceptions:
     *   IllegalArgumentException : if any argument is null, or path is empty string
     *   InvalidRequestException : if there is any problem with the REST API request
     *   AuthorizationException : if there is any problem with the REST API authorization(access token)
     *   ResourceNotFoundException : if the resource can not be found
     *   ServiceUnavailableException : if the REST API service is not available (possibly due to rate limiting)
     *   SmartsheetRestException : if there is any other REST API related error occurred during the operation
     *   SmartsheetException : if there is any other error occurred during the operation
     *
     * @param <T> the generic type
     * @param path the relative path of the resource
     * @param objectClass the resource object class
     * @param object the object to create
     * @return the updated resource
     * @throws SmartsheetException the smartsheet exception
     */
    protected <T> T updateResource(String path, Class<T> objectClass, T object) throws SmartsheetException {
        Util.throwIfNull(path, object);
        Util.throwIfEmpty(path);

        HttpRequest request;
        request = createHttpRequest(smartsheet.getBaseURI().resolve(path), HttpMethod.PUT);

        ByteArrayOutputStream objectBytesStream = new ByteArrayOutputStream();
        this.smartsheet.getJsonSerializer().serialize(object, objectBytesStream);
        HttpEntity entity = new HttpEntity();
        entity.setContentType("application/json");
        entity.setContent(new ByteArrayInputStream(objectBytesStream.toByteArray()));
        entity.setContentLength(objectBytesStream.size());
        request.setEntity(entity);

        T obj = null;
        try {
            HttpResponse response = this.smartsheet.getHttpClient().request(request);
            switch (response.getStatusCode()) {
            case 200:
                obj = this.smartsheet.getJsonSerializer()
                        .deserializeResult(objectClass, response.getEntity().getContent()).getResult();
                break;
            default:
                handleError(response);
            }
        } finally {
            smartsheet.getHttpClient().releaseConnection();
        }

        return obj;
    }

    /**
     * List resources using Smartsheet REST API.
     *
     * Exceptions:
     *   IllegalArgumentException : if any argument is null, or path is empty string
     *   InvalidRequestException : if there is any problem with the REST API request
     *   AuthorizationException : if there is any problem with the REST API authorization(access token)
     *   ServiceUnavailableException : if the REST API service is not available (possibly due to rate limiting)
     *   SmartsheetRestException : if there is any other REST API related error occurred during the operation
     *   SmartsheetException : if there is any other error occurred during the operation
     *
     * @param <T> the generic type
     * @param path the relative path of the resource collections
     * @param objectClass the resource object class
     * @return the resources
     * @throws SmartsheetException if an error occurred during the operation
     */
    protected <T> List<T> listResources(String path, Class<T> objectClass) throws SmartsheetException {
        Util.throwIfNull(path, objectClass);
        Util.throwIfEmpty(path);

        HttpRequest request;
        request = createHttpRequest(smartsheet.getBaseURI().resolve(path), HttpMethod.GET);

        List<T> obj = null;
        try {
            HttpResponse response = this.smartsheet.getHttpClient().request(request);
            switch (response.getStatusCode()) {
            case 200:
                obj = this.smartsheet.getJsonSerializer().deserializeList(objectClass,
                        response.getEntity().getContent());
                break;
            default:
                handleError(response);
            }
        } finally {
            smartsheet.getHttpClient().releaseConnection();
        }

        return obj;
    }

    /**
     * List resources Wrapper (supports paging info) using Smartsheet REST API.
     *
     * Exceptions:
     *   IllegalArgumentException : if any argument is null, or path is empty string
     *   InvalidRequestException : if there is any problem with the REST API request
     *   AuthorizationException : if there is any problem with the REST API authorization(access token)
     *   ServiceUnavailableException : if the REST API service is not available (possibly due to rate limiting)
     *   SmartsheetRestException : if there is any other REST API related error occurred during the operation
     *   SmartsheetException : if there is any other error occurred during the operation
     * @param path
     * @param objectClass
     * @param <T>
     * @return
     * @throws SmartsheetException
     */
    protected <T> PagedResult<T> listResourcesWithWrapper(String path, Class<T> objectClass)
            throws SmartsheetException {
        Util.throwIfNull(path, objectClass);
        Util.throwIfEmpty(path);

        HttpRequest request;
        request = createHttpRequest(smartsheet.getBaseURI().resolve(path), HttpMethod.GET);

        PagedResult<T> obj = null;
        try {
            HttpResponse response = this.smartsheet.getHttpClient().request(request);
            switch (response.getStatusCode()) {
            case 200:
                obj = this.smartsheet.getJsonSerializer().deserializeDataWrapper(objectClass,
                        response.getEntity().getContent());
                break;
            default:
                handleError(response);
            }
        } finally {
            smartsheet.getHttpClient().releaseConnection();
        }

        return obj;
    }

    /**
     * Delete a resource from Smartsheet REST API.
     *
     * Exceptions:
     *   IllegalArgumentException : if any argument is null, or path is empty string
     *   InvalidRequestException : if there is any problem with the REST API request
     *   AuthorizationException : if there is any problem with the REST API authorization(access token)
     *   ResourceNotFoundException : if the resource can not be found
     *   ServiceUnavailableException : if the REST API service is not available (possibly due to rate limiting)
     *   SmartsheetRestException : if there is any other REST API related error occurred during the operation
     *   SmartsheetException : if there is any other error occurred during the operation
     *
     * @param <T> the generic type
     * @param path the relative path of the resource
     * @param objectClass the resource object class
     * @throws SmartsheetException the smartsheet exception
     */
    protected <T> void deleteResource(String path, Class<T> objectClass) throws SmartsheetException {
        Util.throwIfNull(path, objectClass);
        Util.throwIfEmpty(path);

        HttpRequest request;
        request = createHttpRequest(smartsheet.getBaseURI().resolve(path), HttpMethod.DELETE);

        try {
            HttpResponse response = this.smartsheet.getHttpClient().request(request);
            switch (response.getStatusCode()) {
            case 200:
                this.smartsheet.getJsonSerializer().deserializeResult(objectClass,
                        response.getEntity().getContent());
                break;
            default:
                handleError(response);
            }
        } finally {
            smartsheet.getHttpClient().releaseConnection();
        }
    }

    /**
     * Delete resources and return a list from Smartsheet REST API.
     *
     * Exceptions:
     *   IllegalArgumentException : if any argument is null, or path is empty string
     *   InvalidRequestException : if there is any problem with the REST API request
     *   AuthorizationException : if there is any problem with the REST API authorization(access token)
     *   ResourceNotFoundException : if the resource can not be found
     *   ServiceUnavailableException : if the REST API service is not available (possibly due to rate limiting)
     *   SmartsheetRestException : if there is any other REST API related error occurred during the operation
     *   SmartsheetException : if there is any other error occurred during the operation
     *
     * @param <T> the generic type
     * @param path the relative path of the resource
     * @param objectClass the resource object class
     * @return List of ids deleted
     * @throws SmartsheetException the smartsheet exception
     */
    protected <T> List<T> deleteListResources(String path, Class<T> objectClass) throws SmartsheetException {
        Util.throwIfNull(path, objectClass);
        Util.throwIfEmpty(path);

        Result<List<T>> obj = null;
        HttpRequest request;
        request = createHttpRequest(smartsheet.getBaseURI().resolve(path), HttpMethod.DELETE);
        try {
            HttpResponse response = this.smartsheet.getHttpClient().request(request);
            switch (response.getStatusCode()) {
            case 200:
                obj = this.smartsheet.getJsonSerializer().deserializeListResult(objectClass,
                        response.getEntity().getContent());
                break;
            default:
                handleError(response);
            }
        } finally {
            smartsheet.getHttpClient().releaseConnection();
        }
        return obj.getResult();
    }

    /**
     * Post an object to Smartsheet REST API and receive a list of objects from response.
     *
     * Parameters: - path : the relative path of the resource collections - objectToPost : the object to post -
     * objectClassToReceive : the resource object class to receive
     *
     * Returns: the object list
     *
     * Exceptions:
     *   IllegalArgumentException : if any argument is null, or path is empty string
     *   InvalidRequestException : if there is any problem with the REST API request
     *   AuthorizationException : if there is any problem with the REST API authorization(access token)
     *   ServiceUnavailableException : if the REST API service is not available (possibly due to rate limiting)
     *   SmartsheetRestException : if there is any other REST API related error occurred during the operation
     *   SmartsheetException : if there is any other error occurred during the operation
     *
     * @param <T> the generic type
     * @param <S> the generic type
     * @param path the path
     * @param objectToPost the object to post
     * @param objectClassToReceive the object class to receive
     * @return the list
     * @throws SmartsheetException the smartsheet exception
     */
    protected <T, S> List<S> postAndReceiveList(String path, T objectToPost, Class<S> objectClassToReceive)
            throws SmartsheetException {
        Util.throwIfNull(path, objectToPost, objectClassToReceive);
        Util.throwIfEmpty(path);

        HttpRequest request = createHttpRequest(smartsheet.getBaseURI().resolve(path), HttpMethod.POST);

        ByteArrayOutputStream objectBytesStream = new ByteArrayOutputStream();
        this.smartsheet.getJsonSerializer().serialize(objectToPost, objectBytesStream);
        HttpEntity entity = new HttpEntity();
        entity.setContentType("application/json");
        entity.setContent(new ByteArrayInputStream(objectBytesStream.toByteArray()));
        entity.setContentLength(objectBytesStream.size());
        request.setEntity(entity);

        List<S> obj = null;
        try {
            HttpResponse response = this.smartsheet.getHttpClient().request(request);
            switch (response.getStatusCode()) {
            case 200:
                obj = this.smartsheet.getJsonSerializer()
                        .deserializeListResult(objectClassToReceive, response.getEntity().getContent()).getResult();
                break;
            default:
                handleError(response);
            }
        } finally {
            smartsheet.getHttpClient().releaseConnection();
        }

        return obj;
    }

    /**
     * Post an object to Smartsheet REST API and receive a CopyOrMoveRowResult object from response.
     *
     * Parameters: - path : the relative path of the resource collections - objectToPost : the object to post -
     *
     * Returns: the object
     *
     * Exceptions:
     *   IllegalArgumentException : if any argument is null, or path is empty string
     *   InvalidRequestException : if there is any problem with the REST API request
     *   AuthorizationException : if there is any problem with the REST API authorization(access token)
     *   ServiceUnavailableException : if the REST API service is not available (possibly due to rate limiting)
     *   SmartsheetRestException : if there is any other REST API related error occurred during the operation
     *   SmartsheetException : if there is any other error occurred during the operation
     *
     * @param path the path
     * @param objectToPost the object to post
     * @return the result object
     * @throws SmartsheetException the smartsheet exception
     */
    protected CopyOrMoveRowResult postAndReceiveRowObject(String path, CopyOrMoveRowDirective objectToPost)
            throws SmartsheetException {
        Util.throwIfNull(path, objectToPost);
        Util.throwIfEmpty(path);

        HttpRequest request = createHttpRequest(smartsheet.getBaseURI().resolve(path), HttpMethod.POST);

        ByteArrayOutputStream objectBytesStream = new ByteArrayOutputStream();
        this.smartsheet.getJsonSerializer().serialize(objectToPost, objectBytesStream);
        HttpEntity entity = new HttpEntity();
        entity.setContentType("application/json");
        entity.setContent(new ByteArrayInputStream(objectBytesStream.toByteArray()));
        entity.setContentLength(objectBytesStream.size());
        request.setEntity(entity);

        CopyOrMoveRowResult obj = null;
        try {
            HttpResponse response = this.smartsheet.getHttpClient().request(request);
            switch (response.getStatusCode()) {
            case 200:
                obj = this.smartsheet.getJsonSerializer()
                        .deserializeCopyOrMoveRow(response.getEntity().getContent());
                break;
            default:
                handleError(response);
            }
        } finally {
            smartsheet.getHttpClient().releaseConnection();
        }

        return obj;
    }

    /**
     * Put an object to Smartsheet REST API and receive a list of objects from response.
     *
     * Exceptions:
     *   IllegalArgumentException : if any argument is null, or path is empty string
     *   InvalidRequestException : if there is any problem with the REST API request
     *   AuthorizationException : if there is any problem with the REST API authorization(access token)
     *   ServiceUnavailableException : if the REST API service is not available (possibly due to rate limiting)
     *   SmartsheetRestException : if there is any other REST API related error occurred during the operation
     *   SmartsheetException : if there is any other error occurred during the operation
     *
     * @param <T> the generic type
     * @param <S> the generic type
     * @param path the relative path of the resource collections
     * @param objectToPut the object to put
     * @param objectClassToReceive the resource object class to receive
     * @return the object list
     * @throws SmartsheetException the smartsheet exception
     */
    protected <T, S> List<S> putAndReceiveList(String path, T objectToPut, Class<S> objectClassToReceive)
            throws SmartsheetException {
        Util.throwIfNull(path, objectToPut, objectClassToReceive);
        Util.throwIfEmpty(path);

        HttpRequest request = createHttpRequest(smartsheet.getBaseURI().resolve(path), HttpMethod.PUT);

        ByteArrayOutputStream objectBytesStream = new ByteArrayOutputStream();
        this.smartsheet.getJsonSerializer().serialize(objectToPut, objectBytesStream);
        HttpEntity entity = new HttpEntity();
        entity.setContentType("application/json");
        entity.setContent(new ByteArrayInputStream(objectBytesStream.toByteArray()));
        entity.setContentLength(objectBytesStream.size());
        request.setEntity(entity);

        List<S> obj = null;
        try {
            HttpResponse response = this.smartsheet.getHttpClient().request(request);
            switch (response.getStatusCode()) {
            case 200:
                obj = this.smartsheet.getJsonSerializer()
                        .deserializeListResult(objectClassToReceive, response.getEntity().getContent()).getResult();
                break;
            default:
                handleError(response);
            }
        } finally {
            smartsheet.getHttpClient().releaseConnection();
        }

        return obj;
    }

    /**
     * Create an HttpRequest.
     * <p>
     * Exceptions: Any exception shall be propagated since it's a private method.
     *
     * @param uri    the URI
     * @param method the HttpMethod
     * @return the http request
     * @throws UnsupportedEncodingException the unsupported encoding exception
     */
    protected HttpRequest createHttpRequest(URI uri, HttpMethod method) {
        HttpRequest request = new HttpRequest();
        request.setUri(uri);
        request.setMethod(method);

        // Set authorization header
        request.setHeaders(createHeaders());

        return request;
    }

    protected HttpPost createHttpPost(URI uri) {
        HttpPost httpPost = new HttpPost(uri);
        Map<String, String> headers = createHeaders();
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            httpPost.addHeader(entry.getKey(), entry.getValue());
        }
        return httpPost;
    }

    public Attachment attachFile(String url, InputStream inputStream, String contentType, long contentLength,
            String attachmentName) throws SmartsheetException {
        Util.throwIfNull(inputStream, contentType);
        HttpRequest request = createHttpRequest(this.getSmartsheet().getBaseURI().resolve(url), HttpMethod.POST);
        try {
            request.getHeaders().put("Content-Disposition",
                    "attachment; filename=\"" + URLEncoder.encode(attachmentName, "UTF-8") + "\"");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        HttpEntity entity = new HttpEntity();
        entity.setContentType(contentType);
        entity.setContent(new LengthEnforcingInputStream(inputStream, contentLength));
        entity.setContentLength(contentLength);
        request.setEntity(entity);

        Attachment attachment = null;
        try {
            HttpResponse response = this.getSmartsheet().getHttpClient().request(request);
            switch (response.getStatusCode()) {
            case 200:
                attachment = this.getSmartsheet().getJsonSerializer()
                        .deserializeResult(Attachment.class, response.getEntity().getContent()).getResult();
                break;
            default:
                handleError(response);
            }
        } finally {
            this.getSmartsheet().getHttpClient().releaseConnection();
        }

        return attachment;
    }

    /**
     * Create a multipart upload request.
     *
     * @param url the url
     * @param t the object to create
     * @param partName the name of the part
     * @param inputstream the file inputstream
     * @param contentType the type of the file to be attached
     * @return the http request
     * @throws UnsupportedEncodingException the unsupported encoding exception
     */
    public <T> Attachment attachFile(String url, T t, String partName, InputStream inputstream, String contentType,
            String attachmentName) throws SmartsheetException {
        Util.throwIfNull(inputstream, contentType);
        Attachment attachment = null;
        final String boundary = "----" + System.currentTimeMillis();

        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost uploadFile = createHttpPost(this.getSmartsheet().getBaseURI().resolve(url));

        try {
            uploadFile.setHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        builder.setBoundary(boundary);
        builder.addTextBody(partName, this.getSmartsheet().getJsonSerializer().serialize(t),
                ContentType.APPLICATION_JSON);
        builder.addBinaryBody("file", inputstream, ContentType.create(contentType), attachmentName);
        org.apache.http.HttpEntity multipart = builder.build();

        uploadFile.setEntity(multipart);

        try {
            CloseableHttpResponse response = httpClient.execute(uploadFile);
            org.apache.http.HttpEntity responseEntity = response.getEntity();
            attachment = this.getSmartsheet().getJsonSerializer()
                    .deserializeResult(Attachment.class, responseEntity.getContent()).getResult();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return attachment;
    }

    /**
     * Handles an error HttpResponse (non-200) returned by Smartsheet REST API.
     *
     * Exceptions:
     *   SmartsheetRestException : the exception corresponding to the error
     *
     * @param response the HttpResponse
     * @throws SmartsheetException the smartsheet exception
     */
    protected void handleError(HttpResponse response) throws SmartsheetException {

        com.smartsheet.api.models.Error error;
        try {
            error = this.smartsheet.getJsonSerializer().deserialize(com.smartsheet.api.models.Error.class,
                    response.getEntity().getContent());
        } catch (JsonParseException e) {
            throw new SmartsheetException(e);
        } catch (JsonMappingException e) {
            throw new SmartsheetException(e);
        } catch (IOException e) {
            throw new SmartsheetException(e);
        }

        ErrorCode code = ErrorCode.getErrorCode(response.getStatusCode());

        if (code == null) {
            throw new SmartsheetRestException(error);
        }

        try {
            throw code.getException(error);
        } catch (IllegalArgumentException e) {
            throw new SmartsheetException(e);
        } catch (SecurityException e) {
            throw new SmartsheetException(e);
        }
    }

    /**
     * Gets the smartsheet.
     *
     * @return the smartsheet
     */
    public SmartsheetImpl getSmartsheet() {
        return smartsheet;
    }

    /**
     * Get a sheet as a file.
     *
     * Exceptions:
     *   - InvalidRequestException : if there is any problem with the REST API request
     *   - AuthorizationException : if there is any problem with the REST API authorization(access token)
     *   - ResourceNotFoundException : if the resource can not be found
     *   - ServiceUnavailableException : if the REST API service is not available (possibly due to rate limiting)
     *   - SmartsheetRestException : if there is any other REST API related error occurred during the operation
     *   - SmartsheetException : if there is any other error occurred during the operation
     *
     * @param path the path
     * @param fileType the output file type
     * @param outputStream the OutputStream to which the file will be written
     * @return the report as file
     * @throws SmartsheetException the smartsheet exception
     */
    public void getResourceAsFile(String path, String fileType, OutputStream outputStream)
            throws SmartsheetException {
        Util.throwIfNull(outputStream, fileType);

        HttpRequest request;
        request = createHttpRequest(this.getSmartsheet().getBaseURI().resolve(path), HttpMethod.GET);
        request.getHeaders().put("Accept", fileType);

        try {
            HttpResponse response = getSmartsheet().getHttpClient().request(request);

            switch (response.getStatusCode()) {
            case 200:
                try {
                    copyStream(response.getEntity().getContent(), outputStream);
                } catch (IOException e) {
                    throw new SmartsheetException(e);
                }
                break;
            default:
                handleError(response);
            }
        } finally {
            getSmartsheet().getHttpClient().releaseConnection();
        }
    }

    /*
     * Copy an input stream to an output stream.
     *
     * @param input The input stream to copy.
     *
     * @param output the output stream to write to.
     *
     * @throws IOException if there is trouble reading or writing to the streams.
     */
    /**
     * Copy stream.
     *
     * @param input the input
     * @param output the output
     * @throws IOException Signals that an I/O exception has occurred.
     */
    @Deprecated // replace with StreamUtil.copyContentIntoOutputStream()
    private static void copyStream(InputStream input, OutputStream output) throws IOException {
        byte[] buffer = new byte[BUFFER_SIZE];
        int len;
        while ((len = input.read(buffer)) != -1) {
            output.write(buffer, 0, len);
        }
    }

    /**
     * @return a map of headers to be used when making requests.
     */
    Map<String, String> createHeaders() {
        Map<String, String> headers = new HashMap<String, String>();
        headers.put("Authorization", "Bearer " + smartsheet.getAccessToken());
        headers.put("Content-Type", "application/json");

        // Set assumed user
        if (smartsheet.getAssumedUser() != null) {
            try {
                headers.put("Assume-User", URLEncoder.encode(smartsheet.getAssumedUser(), "utf-8"));
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(
                        "Unsupported encode. You must support utf-8 for the Smartsheet Java SDK to work", e);
            }
        }
        if (smartsheet.getChangeAgent() != null) {
            try {
                headers.put("Smartsheet-Change-Agent", URLEncoder.encode(smartsheet.getChangeAgent(), "utf-8"));
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(
                        "Unsupported encode. You must support utf-8 for the Smartsheet Java SDK to work", e);
            }
        }
        if (smartsheet.getUserAgent() != null) {
            headers.put("User-Agent", smartsheet.getUserAgent());
        }
        return headers;
    }

    int getResponseLogLength() {
        // not cached to allow for it to be changed dynamically by client code
        return Integer.getInteger(PROPERTY_RESPONSE_LOG_CHARS, 1024);
    }
}