org.mule.modules.box.BoxConnector.java Source code

Java tutorial

Introduction

Here is the source code for org.mule.modules.box.BoxConnector.java

Source

/**
 * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
 * <p/>
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.md file.
 * <p/>
 * This file was automatically generated by the Mule Development Kit
 * <p/>
 * This file was automatically generated by the Mule Development Kit
 */

/**
 * This file was automatically generated by the Mule Development Kit
 */
package org.mule.modules.box;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.WebResource.Builder;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.core.impl.provider.entity.FormMultivaluedMapProvider;
import com.sun.jersey.core.impl.provider.entity.FormProvider;
import com.sun.jersey.core.impl.provider.entity.InputStreamProvider;
import com.sun.jersey.core.impl.provider.entity.MimeMultipartProvider;
import com.sun.jersey.multipart.FormDataMultiPart;
import com.sun.jersey.multipart.file.StreamDataBodyPart;
import com.sun.jersey.multipart.impl.MultiPartWriter;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.mule.MessageExchangePattern;
import org.mule.RequestContext;
import org.mule.api.MuleException;
import org.mule.api.annotations.*;
import org.mule.api.annotations.lifecycle.Start;
import org.mule.api.annotations.oauth.*;
import org.mule.api.annotations.param.Default;
import org.mule.api.annotations.param.Optional;
import org.mule.api.callback.SourceCallback;
import org.mule.api.callback.StopSourceCallback;
import org.mule.commons.jersey.JerseyUtil;
import org.mule.commons.jersey.provider.GsonProvider;
import org.mule.modules.box.exception.BoxException;
import org.mule.modules.box.exception.BoxTokenExpiredException;
import org.mule.modules.box.jersey.AuthBuilderBehaviour;
import org.mule.modules.box.jersey.BoxResponseHandler;
import org.mule.modules.box.jersey.GzipBehaviour;
import org.mule.modules.box.jersey.MediaTypesBuilderBehaviour;
import org.mule.modules.box.jersey.json.GsonFactory;
import org.mule.modules.box.lp.LongPollingClient;
import org.mule.modules.box.model.*;
import org.mule.modules.box.model.request.*;
import org.mule.modules.box.model.response.*;
import org.mule.streaming.PagingConfiguration;
import org.mule.streaming.ProviderAwarePagingDelegate;

import javax.ws.rs.core.MediaType;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * Box Cloud Connector for API V2.
 *
 * @author mariano.gonzalez@mulesoft.com
 */
@Connector(name = "box", schemaVersion = "2.0", friendlyName = "Box", minMuleVersion = "3.5")
@OAuth2(authorizationUrl = "https://www.box.com/api/oauth2/authorize", accessTokenUrl = "https://www.box.com/api/oauth2/token", accessTokenRegex = "\"access_token\"[ ]*:[ ]*\"([^\\\"]*)\"", expirationRegex = "\"expires_in\"[ ]*:[ ]*([\\d]*)", refreshTokenRegex = "\"refresh_token\"[ ]*:[ ]*\"([^\\\"]*)\"")
public class BoxConnector {

    private Client client;

    private Client uploadClient;

    /**
     * The api's base url
     */
    @Configurable
    @Optional
    @Default("https://api.box.com/2.0/")
    private String baseUrl;

    /**
     * The url of the endpoints dedicated to file uploading operations
     */
    @Configurable
    @Optional
    @Default("https://upload.box.com/api/2.0/files")
    private String uploadUrl;

    /**
     * The OAuth2 client id
     */
    @Configurable
    @OAuthConsumerKey
    private String clientId;

    /**
     * The OAuth2 client secret
     */
    @Configurable
    @OAuthConsumerSecret
    private String clientSecret;

    @OAuthAccessToken
    private String accessToken;

    /**
     * If set to true, Box will be asked to gzip all its responses
     */
    @Configurable
    @Optional
    @Default("false")
    private boolean useGzip = false;

    private LongPollingClient longPollingClient = null;

    /**
     * This list holds the source callbacks for long polling that can't yet be
     * served because authorization hasn't happened
     */
    private static List<SourceCallback> pendingSubscriptions = new ArrayList<SourceCallback>();

    private String accessTokenIdentifier;

    private JerseyUtil jerseyUtil;

    private WebResource apiResource;

    private WebResource uploadResource;

    /**
     * This method initiaes the box client and the auth callback. Also, it
     * fetches the save/restore flows (if specified). If those are specified but
     * don't exist in the registry, then IllegalArgumentException is thrown
     *
     * @throws MuleException
     * @throws IllegalArgumentException
     *             if restore/save token flows are specified but don't exist
     */
    @Start
    public void init() throws MuleException {
        ClientConfig clientConfig = new DefaultClientConfig();
        clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
        clientConfig.getClasses().add(MultiPartWriter.class);
        clientConfig.getClasses().add(MimeMultipartProvider.class);
        clientConfig.getClasses().add(InputStreamProvider.class);
        clientConfig.getClasses().add(FormProvider.class);
        clientConfig.getClasses().add(FormMultivaluedMapProvider.class);
        clientConfig.getSingletons().add(new GsonProvider(GsonFactory.get()));

        this.client = Client.create(clientConfig);
        this.uploadClient = Client.create(clientConfig);
        this.uploadClient.setChunkedEncodingSize(512);

        this.initJerseyUtil();
        this.longPollingClient = new LongPollingClient(this.jerseyUtil);

        this.apiResource = this.client.resource(this.baseUrl);
        this.uploadResource = this.uploadClient.resource(this.uploadUrl);

    }

    private void initJerseyUtil() {
        JerseyUtil.Builder builder = JerseyUtil.builder().addRequestBehaviour(MediaTypesBuilderBehaviour.INSTANCE)
                .addRequestBehaviour(new AuthBuilderBehaviour(this))
                .setResponseHandler(BoxResponseHandler.INSTANCE);

        if (this.useGzip) {
            builder.addRequestBehaviour(GzipBehaviour.INSTANCE);
        }

        this.jerseyUtil = builder.build();
    }

    @OAuthPostAuthorization
    public void postAuthorize() {
        //REMOVE THIS: this hack needs to be removed once the connector returns the remote user id
        // by itself in a right way after authorize
        RequestContext.getEvent().setFlowVariable("remoteUserId", this.getUser().getLogin());

        for (SourceCallback callback : pendingSubscriptions) {
            this.subscribe(callback);
        }
    }

    /**
     * Retrieves information about a given folder. If the folderId parameter is
     * not provided or equals 0, then the root folder will be returned.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-folder}
     *
     * @param folderId
     *            the id of the folder you want to get. 0 means root
     * @param getAllAttributes
     *            option to retrieve all folder attributes or just the default ones
     * @return an instance of {@link org.mule.modules.box.model.Folder}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Folder getFolder(@Optional @Default("0") String folderId,
            @Optional @Default("false") boolean getAllAttributes) {
        WebResource resource = this.apiResource.path("folders").path(folderId);

        if (getAllAttributes) {
            resource = resource.queryParam("fields", this.getAllFolderFieldsAsString());
        }

        return this.jerseyUtil.get(resource, Folder.class, 200);
    }

    /**
     * Creates a new folder and returns a folder object with all its associated
     * information
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:create-folder}
     *
     * @param parentId
     *            the id of the parent folder. If not provided then the root
     *            will be used
     * @param folderName
     *            the name of the folder
     * @return an instance of {@link org.mule.modules.box.model.Folder}
     *         representing the newly created folder
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Folder createFolder(@Optional @Default("0") String parentId, String folderName) {
        return this.jerseyUtil.post(
                this.apiResource.path("folders").entity(new CreateFolderRequest(folderName, parentId)),
                Folder.class, 200, 201);
    }

    /**
     * Used to update information about the folder. To move a folder, update the
     * ID of its parent. To enable an email address that can be used to upload
     * files to this folder, update the folderUploadEmail attribute. An optional
     * If-Match header can be included to ensure that client only updates the
     * folder if it knows about the latest version by setting the etag
     * attribute.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:update-folder}
     *
     * @param request
     *            an instance of
     *            {@link org.mule.modules.box.model.request.UpdateItemRequest}
     *            with the attributes you want to change
     * @param folderId
     *            the id of the folder to be modified
     * @param etag
     *            if provided, it will be used to verify that no newer version
     *            of the file is available at box
     * @return an instance of {@link org.mule.modules.box.model.Folder} that
     *         represents the updated folder
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Folder updateFolder(@Optional @Default("#[payload]") UpdateItemRequest request, String folderId,
            @Optional String etag) {
        WebResource resource = this.apiResource.path("folders").path(folderId);
        return this.jerseyUtil.put(this.maybeAddIfMatch(resource, etag).entity(request), Folder.class, 200, 201);
    }

    /**
     * Retrieves the discussions on a particular folder, if any exist.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:get-folder-discussions}
     *
     * @param folderId
     *            the id of the folder which discussions you want
     * @return an instance of {@link org.mule.modules.box.model.Entries}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Entries getFolderDiscussions(String folderId) {
        return this.jerseyUtil.get(this.apiResource.path("folders").path(folderId).path("discussions"),
                Entries.class, 200, 204);
    }

    /**
     * Get the folders in the Trash. Retrieves the files and/or folders that
     * have been moved to the trash using the mini format. Paginated results can
     * be retrieved using the limit and offset parameters.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-trashed-items}
     *
     * @param limit
     *            the maximum amount of items to be returned (default=100,
     *            max=1000)
     * @param offset
     *            pagination offset (default=0)
     * @param pagingConfiguration pagingConfiguration object
     * @return an auto paginated iterator
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    @Paged
    public ProviderAwarePagingDelegate<Item, BoxConnector> getTrashedItems(@Optional @Default("100") Long limit,
            @Optional @Default("0") Long offset, PagingConfiguration pagingConfiguration) {

        return this.getFolderItems("trash", limit, offset, pagingConfiguration);
    }

    /**
     * Restores an item that has been moved to the trash. Default behavior is to
     * restore the item to the folder it was in before it was moved to the
     * trash. If that parent folder no longer exists or if there is now an item
     * with the same name in that parent folder, the new parent folder and/or
     * new name will need to be included in the request.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:restore-trashed-folder}
     *
     * @param folderId
     *            the id of the trashed folder being restored
     * @param request
     *            an instance of
     *            {@link org.mule.modules.box.model.request.RestoreTrashedItemRequest}
     *            with the request parameters
     * @return an instance of {@link org.mule.modules.box.model.Folder} with the
     *         restored folder new state
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Folder restoreTrashedFolder(String folderId,
            @Optional @Default("#[payload]") RestoreTrashedItemRequest request) {
        return this.jerseyUtil.post(this.apiResource.path("folders").path(folderId).entity(request), Folder.class,
                201);
    }

    /**
     * Permanently deletes an item that is in the trash. The item will no longer
     * exist in Box. This action cannot be undone.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:perm-delete-folder}
     *
     * @param folderId
     *            the id of the folder to be permanently deleted
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public void permDeleteFolder(String folderId) {
        this.jerseyUtil.delete(this.apiResource.path("folders").path(folderId).path("trash"), String.class, 204);
    }

    /**
     * Retrieves a folder that has been moved to the trash.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:get-trashed-folder}
     *
     * @param folderId
     *            the id of the folder you want
     * @return an instance of {@link org.mule.modules.box.model.Folder}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Folder getTrashedFolder(String folderId) {
        return this.jerseyUtil.get(this.apiResource.path("folders").path(folderId).path("trash"), Folder.class,
                200);
    }

    /**
     * Used to create a shared link for this particular folder
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:share-folder}
     *
     * @param folderId
     *            the id of the folder you want to share
     * @param sharedLink
     *            an instance of {@link org.mule.modules.box.model.SharedLink}
     *            with the information about the share
     * @return an instance of {@link org.mule.modules.box.model.Folder}
     *         representing the shared folder
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Folder shareFolder(String folderId, @Optional @Default("#[payload]") SharedLink sharedLink) {
        CreateSharedLinkRequest request = new CreateSharedLinkRequest();
        request.setSharedLink(sharedLink);

        return this.jerseyUtil.put(this.apiResource.path("folders").path(folderId).entity(request), Folder.class,
                200, 201);
    }

    /**
     * Deletes the shared link associated to a folder
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:unshare-folder}
     *
     * @param folderId
     *            the id of the folder you want to unshare
     * @return an instance of {@link org.mule.modules.box.model.Folder}
     *         representing the unshared folder
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Folder unshareFolder(String folderId) {
        return this.shareFolder(folderId, null);
    }

    /**
     * Used to create a shared link for this particular folder
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:share-folder}
     *
     * @param targetFolderId
     *            the id of the parent folder that will hold the copy. If not
     *            provided then the root will be used
     * @param folderId
     *            the if od the folder being copied
     * @return an instance of {@link org.mule.modules.box.model.Folder}
     *         representing the copy
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Folder copyFolder(@Optional @Default("0") String targetFolderId, String folderId) {
        Item parent = new Item();
        parent.setId(targetFolderId);
        CopyItemRequest request = new CopyItemRequest();
        request.setParent(parent);

        return this.jerseyUtil.post(this.apiResource.path("folders").path(folderId).path("copy").entity(request),
                Folder.class, 200, 201);
    }

    /**
     * Retrieves the files and/or folders contained within this folder without
     * any other metadata about the folder in the mini format is returned for
     * each item by default. Paginated results can be retrieved using the limit
     * and offset parameters.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-folder-items}
     *
     * @param folderId
     *            the id of the folder you want to inspect. If not provided then
     *            the root folder is assumed
     * @param limit
     *            the maximum amount of items to be returned (default=100,
     *            max=1000)
     * @param offset
     *            pagination offset (default=0)
     * @param pagingConfiguration pagingConfiguration object
     * @return an auto paginated iterator
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    @Paged
    public ProviderAwarePagingDelegate<Item, BoxConnector> getFolderItems(
            final @Optional @Default("0") String folderId, final @Optional Long limit,
            final @Optional @Default("0") Long offset, final PagingConfiguration pagingConfiguration) {

        if (limit != null && limit < 0) {
            throw new IllegalArgumentException("The limit parameter cannot be a negative number.");
        }

        if (offset != null && offset < 0) {
            throw new IllegalArgumentException("The offset parameter cannot to be a negative number.");
        }

        if (pagingConfiguration == null) {
            throw new IllegalArgumentException("The pagingConfiguration parameter cannot be null.");
        } else if (pagingConfiguration.getFetchSize() <= 0) {
            throw new IllegalArgumentException(
                    "The fetchSize value of pagingConfiguration needs to be a positive number.");
        }

        return new ProviderAwarePagingDelegate<Item, BoxConnector>() {
            private Long itemsReturned = 0L;
            private Long initialOffset = offset == null ? 0L : offset;
            private String folderIdValue = StringUtils.isEmpty(folderId) ? "0" : folderId;

            public BoxConnector connector;

            @Override
            public List<Item> getPage(BoxConnector boxConnector) {
                if (this.isLimitReached()) {
                    return new ArrayList<Item>();
                }

                List<Item> items = this.query(boxConnector).getEntries();
                itemsReturned += items.size();

                this.connector = boxConnector;
                return items;
            }

            private Boolean isLimitReached() {
                return limit != null && itemsReturned >= limit;
            }

            private GetItemsResponse query(BoxConnector boxConnector) {
                WebResource resource = apiResource.path("folders").path(folderIdValue).path("items");

                resource = resource.queryParam("offset", this.defineQueryOffset().toString());
                resource = resource.queryParam("limit", this.defineQueryLimit().toString());

                return boxConnector.getJerseyUtil().get(resource, GetItemsResponse.class, 200);
            }

            private Long defineQueryOffset() {
                return initialOffset + itemsReturned;
            }

            private Long defineQueryLimit() {
                Long queryLimit = new Long(pagingConfiguration.getFetchSize());

                if (limit != null) {
                    queryLimit = Math.min(queryLimit, limit - itemsReturned);
                }

                return queryLimit;
            }

            @Override
            public void close() throws MuleException {
            }

            @Override
            public int getTotalResults(BoxConnector boxConnector) throws Exception {
                return -1;
            }
        };

    }

    /**
     * Traverses a given folder looking for a resource (file or folder) of a
     * given name.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-folder-item}
     *
     * @param folderId
     *            the id of the folder you want to inspect. If not provided then
     *            the root folder is assumed
     * @param resourceName
     *            the name you want to test
     * @return an instance of {@link org.mule.modules.box.model.Item} with that
     *         about the found item. {@code null} if the item is not found
     * @throws Exception if case of error
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Item getFolderItem(@Optional @Default("0") String folderId, String resourceName) throws Exception {
        ProviderAwarePagingDelegate<Item, BoxConnector> delegate = this.getFolderItems(folderId, null, null,
                new PagingConfiguration(100));

        try {

            List<Item> items = delegate.getPage(this);
            while (!CollectionUtils.isEmpty(items)) {
                for (Item item : items) {
                    if (resourceName.equals(item.getName())) {
                        return item;
                    }
                }
                items = delegate.getPage(this);
            }
        } finally {
            delegate.close();
        }
        return null;
    }

    /**
     * Returns the item information for a given path.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-item-by-path}
     *
     * @param resourcePath
     *            the resource to retrieve from Box
     * @return an instance of {@link org.mule.modules.box.model.Item} with that
     *         about the found item. {@code null} if the item is not found
     * @throws Exception if case of error
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Item getItemByPath(String resourcePath) throws Exception {
        String parentId = "0";
        String[] itemNames = StringUtils.removeStart(resourcePath, "/").split("/");

        Item boxItem = null;

        for (int i = 0; i < itemNames.length; i++) {
            boxItem = this.getFolderItem(parentId, itemNames[i]);

            if (boxItem == null) {
                return null;
            }

            parentId = boxItem.getId();
        }

        return boxItem;
    }

    /**
     * Returns the folder information for a given path
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:get-folder-by-path}
     *
     * @param resourcePath
     *            the resource to retrieve from Box
     * @param getAllAttributes
     *            option to retrieve all folder attributes or just the default ones
     *
     * @return an instance of {@link org.mule.modules.box.model.Folder} with
     *         that about the found Folder. {@code null} if the folder is not
     *         found
     * @throws Exception if case of error         
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Folder getFolderByPath(String resourcePath, @Optional @Default("false") boolean getAllAttributes)
            throws Exception {
        String parentId = "0";
        String[] itemNames = StringUtils.removeStart(resourcePath, "/").split("/");

        for (int i = 0; i < itemNames.length; i++) {
            if (StringUtils.isBlank(itemNames[i])) {
                continue;
            }

            Item boxItem = this.getFolderItem(parentId, itemNames[i]);

            if (boxItem == null) {
                return null;
            }

            parentId = boxItem.getId();
        }

        return this.getFolder(parentId, getAllAttributes);
    }

    /**
     * Deletes a folder
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:delete-folder}
     *
     * @param folderId
     *            the id of the folder to be deleted
     * @param recursive
     *            Whether to delete this folder if it has items inside of it
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public void deleteFolder(String folderId, @Optional @Default("true") Boolean recursive) {
        this.jerseyUtil.delete(
                this.apiResource.path("folders").path(folderId).queryParam("recursive", recursive.toString()),
                String.class, 200, 204);

    }

    /**
     * Creates a new file with the contents of a {@link java.io.InputStream}.
     * You need to take in count that since this is a stream, using the option
     * of including a verification hash will cause the contents of the input
     * stream to be fully read and loaded in memory.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:upload-stream}
     *
     * @param folderId
     *            the id of the target folder.
     * @param filename
     *            the name you want the file to have at box.
     * @param content
     *            a {@link java.io.InputStream} with the contents of the file.
     *            This stream will leave this processor in a closed state
     * @param includeHash
     *            if true a sha1 hash of the file will be calculated prior to
     *            upload. Box will use that hash to verify that the content's
     *            hasn't been corrupted.
     * @param contentCreatedAt
     *            The time this file was created on the users machine. An
     *            example of a valid date is 2012-12-12T10:55:30-08:00
     * @param contentModifiedAt
     *            The time this file was modified on the users machine. An
     *            example of a valid date is 2012-12-12T10:55:30-08:00
     * @return an instance of {@link org.mule.modules.box.model.File} with the
     *         information of the created file
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public File uploadStream(@Optional @Default("0") String folderId, String filename,
            @Optional @Default("#[payload]") InputStream content, @Optional @Default("false") boolean includeHash,
            @Optional String contentCreatedAt, @Optional String contentModifiedAt) {

        WebResource.Builder resource = this.uploadResource.path("content").type(MediaType.MULTIPART_FORM_DATA);

        if (includeHash) {
            resource.header("Content-MD5", this.hash(content));
        }

        FormDataMultiPart form = new FormDataMultiPart();
        form.field("parent_id", folderId);

        if (!StringUtils.isBlank(contentCreatedAt)) {
            form.field("content_created_at", contentCreatedAt);
        }

        if (!StringUtils.isBlank(contentModifiedAt)) {
            form.field("content_modified_at", contentModifiedAt);
        }

        form.bodyPart(new StreamDataBodyPart(filename, content));

        resource.entity(form);

        UploadFileResponse response = this.jerseyUtil.post(resource, UploadFileResponse.class, 200, 201);
        return response.getEntries().get(0);
    }

    /**
     * Uploads a new version of a file from an input stream
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:upload-new-version-stream}
     *
     * @param content
     *            a {@link java.io.InputStream} with the contents of the file.
     *            This stream will leave this processor in a closed state
     * @param fileId
     *            the id of the file to be updated
     * @param etag
     *            if provided, it will be used to verify that no newer version
     *            of the file is available at box
     * @param filename
     *            new name for the file
     * @param contentModifiedAt
     *            The time this file was modified on the users machine. An
     *            example of a valid date is 2012-12-12T10:55:30-08:00
     * @return an instance of {@link org.mule.modules.box.model.File} with the
     *         information of the updated file
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public File uploadNewVersionStream(@Optional @Default("#[payload]") InputStream content, String fileId,
            @Optional String etag, String filename, @Optional String contentModifiedAt) {

        WebResource.Builder resource = this.maybeAddIfMatch(this.uploadResource.path(fileId).path("content"), etag)
                .type(MediaType.MULTIPART_FORM_DATA);

        FormDataMultiPart form = new FormDataMultiPart();

        if (!StringUtils.isBlank(contentModifiedAt)) {
            form.field("content_modified_at", contentModifiedAt);
        }

        form.bodyPart(new StreamDataBodyPart(filename, content));

        resource.entity(form);

        UploadFileResponse response = this.jerseyUtil.post(resource, UploadFileResponse.class, 200, 201);
        return response.getEntries().get(0);
    }

    /**
     * Uploads a new version of a file by reading the contents from a path in
     * local storage
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:upload-new-version-stream}
     *
     * @param path
     *            the path of the file in local storage
     * @param fileId
     *            the id of the file to be updated
     * @param filename
     *            new name for the file
     * @param etag
     *            if provided, it will be used to verify that no newer version
     *            of the file is available at box
     * @param contentModifiedAt
     *            The time this file was modified on the users machine. An
     *            example of a valid date is 2012-12-12T10:55:30-08:00
     * @return an instance of {@link org.mule.modules.box.model.File} with the
     *         information of the updated file
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public File uploadNewVersionPath(String path, String fileId, String filename, @Optional String etag,
            @Optional String contentModifiedAt) {

        return this.uploadNewVersionStream(this.readLocalFile(path), fileId, etag, filename, contentModifiedAt);
    }

    /**
     * Downloads a file
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:download}
     *
     * @param fileId
     *            the id of the file you want
     * @param version
     *            The ID specific version of this file to download.
     * @return an input stream with the contents of the file
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public InputStream download(String fileId, @Optional String version) {
        WebResource resource = this.apiResource.path("files").path(fileId).path("content");

        if (version != null) {
            resource.queryParam("version", version);
        }

        return this.jerseyUtil.get(resource, InputStream.class, 200);
    }

    /**
     * Used to retrieve the metadata about a file.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-file-metadata}
     *
     * @param fileId
     *            the id of the file you want to inspect
     * @param getAllAttributes
     *            option to retrieve all file attributes or just the default ones
     * @return an instance of {@link org.mule.modules.box.model.File}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public File getFileMetadata(String fileId, @Optional @Default("false") boolean getAllAttributes) {
        WebResource resource = this.apiResource.path("files").path(fileId);

        if (getAllAttributes) {
            resource = resource.queryParam("fields", this.getAllFileFieldsAsString());
        }

        return this.jerseyUtil.get(resource, File.class, 200);
    }

    /**
     * If there are previous versions of this file, this method can be used to
     * retrieve metadata about the older versions. Alert: Versions are only
     * tracked for Box users with premium accounts.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-file-metadata}
     *
     * @param fileId
     *            the id of the file which versions you want to pull
     * @return an instance of
     *         {@link org.mule.modules.box.model.response.FileVersionResponse}
     *         with the metadata about the versions
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public FileVersionResponse getVersionsMetadata(String fileId) {
        return this.jerseyUtil.get(this.apiResource.path("files").path(fileId).path("versions"),
                FileVersionResponse.class, 200, 201);
    }

    /**
     * Receives the path of a file in local storage and uploads its content
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:upload-path}
     *
     * @param path
     *            the path of the file in local storage
     * @param folderId
     *            the id of the target folder.
     * @param filename
     *            the name you want the file to have at box. If not provided,
     *            the name on current storage will be used
     * @param includeHash
     *            if true a sha1 hash of the file will be calculated prior to
     *            upload. Box will use that hash to verify that the content's
     *            hasn't been corrupted.
     * @param contentCreatedAt
     *            The time this file was created on the users machine. An
     *            example of a valid date is 2012-12-12T10:55:30-08:00
     * @param contentModifiedAt
     *            The time this file was modified on the users machine. An
     *            example of a valid date is 2012-12-12T10:55:30-08:00
     *
     * @return an instance of {@link org.mule.modules.box.model.File} with the
     *         information of the created file
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public File uploadPath(String path, @Optional @Default("0") String folderId, String filename,
            @Optional @Default("false") boolean includeHash, @Optional String contentCreatedAt,
            @Optional String contentModifiedAt) {

        return this.uploadStream(folderId, filename, this.readLocalFile(path), includeHash, contentCreatedAt,
                contentModifiedAt);
    }

    /**
     * Discards a file to the trash. The etag of the file can be included as an
     * If-Match header to prevent race conditions. Depending on the enterprise
     * settings for this user, the item will either be actually deleted from Box
     * or moved to the trash.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:delete-file}
     *
     * @param fileId
     *            the id of the file to be deleted
     * @param etag
     *            if provided, it will be used to verify that no newer version
     *            of the file is available at box
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public void deleteFile(String fileId, @Optional String etag) {
        WebResource resource = this.apiResource.path("files").path(fileId);
        this.jerseyUtil.delete(this.maybeAddIfMatch(resource, etag), String.class, 200, 204);
    }

    /**
     * Update a files information. Used to update individual or multiple fields
     * in the file object, including renaming the file, changing its
     * description, and creating a shared link for the file. To move a file,
     * change the ID of its parent folder. An optional etag can be provided to
     * ensure that client only updates the file if it knows about the latest
     * version
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:update-file}
     *
     * @param fileId
     *            the id of the file which metadata you want to update
     * @param request
     *            an instance of
     *            {@link org.mule.modules.box.model.request.UpdateItemRequest}
     *            carrying the update parameters
     * @param etag
     *            if provided, it will be used to verify that no newer version
     *            of the file is available at box
     * @return an instance of {@link org.mule.modules.box.model.File} with the
     *         updated state of the file
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public File updateFile(String fileId, @Optional @Default("#[payload]") UpdateItemRequest request,
            @Optional String etag) {
        WebResource resource = this.apiResource.path("files").path(fileId);
        return this.jerseyUtil.put(this.maybeAddIfMatch(resource, etag).entity(request), File.class, 200, 201);
    }

    /**
     * Used to create a copy of a file in another folder. The original version
     * of the file will not be altered.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:copy-file}
     *
     * @param fileId
     *            the id of the file you want to copy
     * @param targetFolderId
     *            the if of the target folder. Defaults to the root folder
     * @return and instance of {@link org.mule.modules.box.model.File}
     *         representing the copy of the file
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public File copyFile(@Optional @Default("0") String targetFolderId, String fileId) {
        Item parent = new Item();
        parent.setId(targetFolderId);
        CopyItemRequest request = new CopyItemRequest();
        request.setParent(parent);

        return this.jerseyUtil.post(this.apiResource.path("files").path(fileId).path("copy").entity(request),
                File.class, 200, 201);
    }

    /**
     * Used to create a shared link for this particular file.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:share-file}
     *
     * @param fileId
     *            the id of the file you want to share
     * @param sharedLink
     *            an instance of {@link org.mule.modules.box.model.SharedLink}
     *            with the information about the share
     * @return an instance of {@link org.mule.modules.box.model.File}
     *         representing the shared folder
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public File shareFile(String fileId, @Optional @Default("#[payload]") SharedLink sharedLink) {
        CreateSharedLinkRequest request = new CreateSharedLinkRequest();
        request.setSharedLink(sharedLink);

        return this.jerseyUtil.put(this.apiResource.path("files").path(fileId).entity(request), File.class, 200,
                201);
    }

    /**
     * Deletes the shared link associated to a file
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:unshare-file}
     *
     * @param fileId
     *            the id of the file you want to unshare
     * @return an instance of {@link org.mule.modules.box.model.File}
     *         representing the unshared folder
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public File unshareFile(String fileId) {
        return this.shareFile(fileId, null);
    }

    /**
     * Retrieves the comments on a particular file, if any exist.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-file-comments}
     *
     * @param fileId
     *            the id of the while which comments you want
     * @return an instance of
     *         {@link org.mule.modules.box.model.response.GetCommentsResponse}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public GetCommentsResponse getFileComments(String fileId) {
        return this.jerseyUtil.get(this.apiResource.path("files").path(fileId).path("comments"),
                GetCommentsResponse.class, 200);
    }

    /**
     * Retrieves a thumbnail, or smaller image representation, of this file.
     * Sizes of 32x32, 64x64, 128x128, and 256x256 can be returned. Currently
     * thumbnails are only available in .png format and will only be generated
     * for image file formats.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:get-file-thumbnail}
     *
     * @param fileId
     *            the id of the file which thumb you want
     * @param minSize
     *            the minimum size you're interested in
     * @param maxSize
     *            the maximum size you're interested in
     * @return an InputStream with the content of the thumb. Remember to close
     *         it!
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public InputStream getFileThumbnail(String fileId, @Optional ThumbnailSize minSize,
            @Optional ThumbnailSize maxSize) {
        WebResource resource = this.apiResource.path("files").path(fileId).path("thumbnail.extension");

        if (minSize != null) {
            resource = minSize.setAsMin(resource);
        }

        if (maxSize != null) {
            resource = maxSize.setAsMax(resource);
        }

        return this.jerseyUtil.get(resource, InputStream.class, 200, 202);
    }

    /**
     * Retrieves the metadata of a trashed file
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-trashed-file}
     *
     * @param fileId
     *            the id of the trashed file you want
     * @return an instance of {@link org.mule.modules.box.model.File}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public File getTrashedFile(String fileId) {
        return this.jerseyUtil.get(this.apiResource.path("files").path(fileId).path("trash"), File.class, 200);
    }

    /**
     * Restores a file that has been moved to the trash. Default behavior is to
     * restore the item to the folder it was in before it was moved to the
     * trash. If that parent folder no longer exists or if there is now an item
     * with the same name in that parent folder, the new parent folder and/or
     * new name will need to be included in the request.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:restore-trashed-file}
     *
     * @param fileId
     *            the id of the trashed file being restored
     * @param request
     *            an instance of
     *            {@link org.mule.modules.box.model.request.RestoreTrashedItemRequest}
     *            with the request parameters
     * @return an instance of {@link org.mule.modules.box.model.Folder} with the
     *         restored folder new state
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public File restoreTrashedFile(String fileId,
            @Optional @Default("#[payload]") RestoreTrashedItemRequest request) {
        return this.jerseyUtil.post(this.apiResource.path("files").path(fileId).entity(request), File.class, 201);
    }

    /**
     * Permanently deletes an item that is in the trash. The item will no longer
     * exist in Box. This action cannot be undone.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:perm-delete-file}
     *
     * @param fileId
     *            the id of the file to be permanently deleted
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public void permDeleteFile(String fileId) {
        this.jerseyUtil.delete(this.apiResource.path("files").path(fileId).path("trash"), String.class, 204);
    }

    /**
     * Used to retrieve the message and metadata about a specific comment.
     * Information about the user who created the comment is also included.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-comment}
     *
     * @param commentId
     *            the id of the comment you want
     * @return an instance of {@link org.mule.modules.box.model.Comment}
     *         representing the message
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Comment getComment(String commentId) {
        return this.jerseyUtil.get(this.apiResource.path("comments").path(commentId), Comment.class, 200);
    }

    /**
     * Used to add a comment by the user to a specific file
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:comment-file}
     *
     * @param fileId
     *            the id of the file to be commented on
     * @param message
     *            text of the comment to be posted
     * @return an instance of {@link org.mule.modules.box.model.Comment}
     *         representing the created message
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Comment commentFile(String fileId, String message) {
        Map<String, String> entity = new HashMap<String, String>();
        entity.put("message", message);

        return this.jerseyUtil.post(this.apiResource.path("files").path(fileId).path("comments").entity(entity),
                Comment.class, 200, 201);
    }

    /**
     * Used to update the message of the comment.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:update-comment}
     *
     * @param commentId
     *            the id of the comment to be updated
     * @param newMessage
     *            the new message
     * @return an instance of {@link org.mule.modules.box.model.Comment}
     *         representing the updated message
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Comment updateComment(String commentId, String newMessage) {
        Map<String, String> entity = new HashMap<String, String>();
        entity.put("message", newMessage);

        return this.jerseyUtil.put(this.apiResource.path("comments").path(commentId).entity(entity), Comment.class,
                200, 201);
    }

    /**
     * Delets a comment.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:delete-comment}
     *
     * @param commentId
     *            the id of the comment to be deleted
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public void deleteComment(String commentId) {
        this.jerseyUtil.delete(this.apiResource.path("comments").path(commentId), Comment.class, 200, 204);
    }

    /**
     * Used to create the metadata for a new discussion for a particular folder.
     * The parent, id and name attributes of the request object are required
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:create-discussion}
     *
     * @param discussion
     *            the discussion object to be created
     * @return an instance of {@link org.mule.modules.box.model.Discussion} with
     *         the metadata of the created discussion
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Discussion createDiscussion(@Optional @Default("#[payload]") Discussion discussion) {
        return this.jerseyUtil.post(this.apiResource.path("discussions").entity(discussion), Discussion.class, 200,
                201);
    }

    /**
     * Used to add a comment to a discussion.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:comment-discussion}
     *
     * @param discussionId
     *            the id of the discussion to comment on
     * @param message
     *            the text of the comment to be posted
     * @return an instance of {@link org.mule.modules.box.model.Comment}
     *         representing the created message
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Comment commentDiscussion(String discussionId, String message) {
        Map<String, String> entity = new HashMap<String, String>();
        entity.put("message", message);

        return this.jerseyUtil.post(
                this.apiResource.path("discussions").path(discussionId).path("comments").entity(entity),
                Comment.class, 200, 201);
    }

    /**
     * Used to retrieve the metadata about a specific discussion. Information
     * about the user who created the discussion is also included.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-discussion}
     *
     * @param discussionId
     *            the id of the discussion you wnat
     * @return an instance of {@link org.mule.modules.box.model.Discussion} with
     *         the discussion metadata
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Discussion getDiscussion(String discussionId) {
        return this.jerseyUtil.get(this.apiResource.path("discussions").path(discussionId), Discussion.class, 200);
    }

    /**
     * Used to update the metadata for an existing discussion.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:update-discussion}
     *
     * @param discussion
     *            discussion object carrying the new state
     * @param discussionId
     *            the id of the discussion to be updated
     * @return a new instance of {@link org.mule.modules.box.model.Discussion}
     *         carrying the updated state
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Discussion updateDiscussion(@Optional @Default("#[payload]") Discussion discussion,
            String discussionId) {
        return this.jerseyUtil.put(this.apiResource.path("discussions").path(discussionId).entity(discussion),
                Discussion.class, 200, 201);
    }

    /**
     * Used to retrieve all comments for a given discussion.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:get-discussion-comments}
     *
     * @param discussionId
     *            the id of the discussions which comments you want
     * @return an instance of
     *         {@link org.mule.modules.box.model.response.GetCommentsResponse}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public GetCommentsResponse getDiscussionComments(String discussionId) {
        return this.jerseyUtil.get(this.apiResource.path("discussions").path(discussionId).path("comments"),
                GetCommentsResponse.class, 200);
    }

    /**
     * Used to add a collaboration for a single user to a folder. Either an
     * email address or a user ID can be used to create the collaboration.
     *
     * Transferring ownership: To transfer ownership of a folder (as the current
     * owner of the folder), first create a collaboration for the new user with
     * any role. Then update that collaboration with a role of owner.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:create-collaboration}
     *
     * @param collaboration
     *            object representing the collaboration to be created
     * @return a new instance of
     *         {@link org.mule.modules.box.model.Collaboration} with the state
     *         of the newly created collab
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Collaboration createCollaboration(@Optional @Default("#[payload]") Collaboration collaboration) {
        return this.jerseyUtil.post(this.apiResource.path("collaborations").entity(collaboration),
                Collaboration.class, 200, 201);
    }

    /**
     * Used to update an existing collaboration.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:update-collaboration}
     *
     * @param collaboration
     *            object holding the new state for the collaboration
     * @param collaborationId
     *            the id of the collaboration to be updated
     * @return a new instance of
     *         {@link org.mule.modules.box.model.Collaboration} with the state
     *         of the collab
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Collaboration updateCollaboration(@Optional @Default("#[payload]") Collaboration collaboration,
            String collaborationId) {
        return this.jerseyUtil.put(
                this.apiResource.path("collaborations").path(collaborationId).entity(collaboration),
                Collaboration.class, 200, 201);
    }

    /**
     * Used to delete a single collaboration.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:delete-collaboration}
     *
     * @param collaborationId
     *            the id of the collaboration to be deleted
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public void deleteCollaboration(String collaborationId) {
        this.jerseyUtil.delete(this.apiResource.path("collaborations").path(collaborationId), String.class, 200,
                204);
    }

    /**
     * Used to get information about a single collaboration.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-collaboration}
     *
     * @param collaborationId
     *            the id of the collaboration you want
     * @return an instance of {@link org.mule.modules.box.model.Collaboration}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Collaboration getCollaboration(String collaborationId) {
        return this.jerseyUtil.get(this.apiResource.path("collaborations").path(collaborationId),
                Collaboration.class, 200);
    }

    /**
     * Used to retrieve all pending collaboration invites for this user.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-collaboration}
     *
     * @return an instance of
     *         {@link org.mule.modules.box.model.response.GetCollaborationsResponse}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public GetCollaborationsResponse getPendingCollaborations() {
        return this.jerseyUtil.get(this.apiResource.path("collaborations").queryParam("status", "pending"),
                GetCollaborationsResponse.class, 200);
    }

    /**
     * Retrieves information about the user who is currently logged in i.e. the
     * user for whom this auth token was generated.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-user}
     *
     * @return an instance of {@link org.mule.modules.box.model.User}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public User getUser() {
        return this.jerseyUtil.get(this.apiResource.path("users/me"), User.class, 200);
    }

    /**
     * Returns a list of all users for the Enterprise
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-users}
     *
     * @param filterTerm
     *            A string used to filter the results to only users starting
     *            with the filter_term in either the name or the login
     * @param limit
     *            The number of records to return.
     * @param offset
     *            The record at which to start
     * @return an instance of
     *         {@link org.mule.modules.box.model.response.GetUsersResponse}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public GetUsersResponse getUsers(@Optional String filterTerm, @Optional Long limit, @Optional Long offset) {
        WebResource resource = this.apiResource.path("users");

        if (filterTerm != null) {
            resource = resource.queryParam("filter_term", filterTerm);
        }

        if (limit != null) {
            resource = resource.queryParam("limit", limit.toString());
        }

        if (offset != null) {
            resource = resource.queryParam("offset", offset.toString());
        }

        return this.jerseyUtil.get(this.apiResource.path("users"), GetUsersResponse.class, 200);
    }

    /**
     * Used to edit the settings and information about a user. This method only
     * works for enterprise admins. To roll a user out of the enterprise (and
     * convert them to a standalone free user), update the special enterprise
     * attribute to be null
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:update-user}
     *
     * @param userId
     *            the id of the user you want to update
     * @param user
     *            the user object with the updated state
     * @param notify
     *            Whether the user should receive an email when they are rolled
     *            out of an enterprise
     * @return an instance of {@link org.mule.modules.box.model.User} with the
     *         updated user state
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public User updateUser(String userId, @Optional @Default("#[payload]") User user,
            @Optional @Default("true") Boolean notify) {

        return this.jerseyUtil.put(
                this.apiResource.path("users").path(userId).queryParam("notify", notify.toString()).entity(user),
                User.class, 200);
    }

    /**
     * Used to provision a new user in an enterprise. This method only works for
     * enterprise admins.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:create-user}
     *
     * @param user
     *            an instance of {@link org.mule.modules.box.model.User}
     *            representing the user to be created
     * @return a new instance of {@link org.mule.modules.box.model.User} with
     *         the final state of the created object
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public User createUser(@Optional @Default("#[payload]") User user) {
        return this.jerseyUtil.post(this.apiResource.path("users").entity(user), User.class, 200, 201);
    }

    /**
     * Moves all of the content from within one users folder into a new folder
     * in another users account. You can move folders across users as long as
     * the you have administrative permissions. To move everything from the root
     * folder, use 0 (zero) which always represents the root folder of a Box
     * account
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:move-folder-to-user}
     *
     * @param targetUser
     *            an instance of {@link org.mule.modules.box.model.User}
     *            representing the user that will receive the folder
     * @param folderId
     *            the id of the folder to be moved
     * @param notify
     *            Determines if the destination user should receive email
     *            notification of the transfer.
     * @return an instance of {@link org.mule.modules.box.model.Folder}
     *         representing the moved folder
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public Folder moveFolderToUser(@Optional @Default("#[paylaod]") User targetUser,
            @Optional @Default("0") String folderId, @Optional @Default("true") Boolean notify) {

        Map<String, Object> entity = new HashMap<String, Object>();
        entity.put("owned_by", targetUser);
        entity.put("id", targetUser.getId());

        return this.jerseyUtil.put(this.apiResource.path("users").path(targetUser.getId()).path("folders")
                .path(folderId).queryParam("notify", notify.toString()).entity(entity), Folder.class, 200, 201);
    }

    /**
     * Deletes a user in an enterprise account.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:delete-user}
     *
     * @param userId
     *            the id of the user being delete
     * @param notify
     *            Determines if the destination user should receive email
     *            notification of the transfer.
     * @param force
     *            Whether or not the user should be deleted even if this user
     *            still own files.
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public void deleteUser(String userId, @Optional @Default("true") Boolean notify,
            @Optional @Default("false") Boolean force) {

        this.jerseyUtil.delete(this.apiResource.path("users").path(userId).queryParam("notify", notify.toString())
                .queryParam("force", force.toString()), String.class, 200, 204);
    }

    /**
     * Adds a new email alias to the given users account.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:create-email-alias}
     *
     * @param userId
     *            the id of the user getting the alias
     * @param email
     *            the new email alias
     * @return an instance of {@link org.mule.modules.box.model.EmailAlias}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public EmailAlias createEmailAlias(String userId, String email) {
        Map<String, String> entity = new HashMap<String, String>();
        entity.put("email", email);

        return this.jerseyUtil.post(this.apiResource.path("users").path(userId).path("email_aliases"),
                EmailAlias.class, 200, 201);
    }

    /**
     * Removes an email alias from a user.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:delete-email-alias}
     *
     * @param userId
     *            the id of the user owning the alias
     * @param emailAliasId
     *            the id of the alias being deleted
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public void deleteEmailAlias(String userId, String emailAliasId) {
        this.jerseyUtil.delete(this.apiResource.path("users").path(userId).path("email_aliases").path(emailAliasId),
                String.class, 200, 204);
    }

    /**
     * Used to convert one of the users confirmed email aliases into the users
     * primary login.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:change-primary-login}
     *
     * @param userId
     *            the id of the user being updated
     * @param login
     *            the new login
     * @return an instance of {@link org.mule.modules.box.model.User} with the
     *         modified state
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public User changePrimaryLogin(String userId, String login) {
        Map<String, String> entity = new HashMap<String, String>();
        entity.put("login", login);

        return this.jerseyUtil.put(this.apiResource.path("users").path(userId).entity(entity), User.class, 200,
                204);
    }

    /**
     * Retrieves all email aliases for this user. The collection of email
     * aliases does not include the primary login for the user
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-email-aliases}
     *
     * @param userId
     *            the id of the user whose aliases you want
     * @return an instance of
     *         {@link org.mule.modules.box.model.response.GetEmailAliasResponse}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public GetEmailAliasResponse getEmailAliases(String userId) {
        return this.jerseyUtil.get(this.apiResource.path("users").path(userId).path("email_aliases"),
                GetEmailAliasResponse.class, 200);
    }

    /**
     *
     * Use this to get events for a given user. A chunk of event objects is
     * returned for the user based on the parameters passed in. Parameters
     * indicating how many chunks are left as well as the next streamPosition
     * are also returned.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-events}
     *
     * @param streamPosition
     *            The location in the event stream at which you want to start
     *            receiving events. Can specify special case now to get 0
     *            events and the latest stream position for initialization.
     * @param streamType
     *            Limits the type of events returned
     * @param limit
     *            Limits the number of events returned
     * @return an instance of
     *         {@link org.mule.modules.box.model.response.GetEventsResponse}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public GetEventsResponse getEvents(String streamPosition, @Optional @Default("all") StreamType streamType,
            @Optional @Default("100") Long limit) {
        return this.jerseyUtil.get(
                this.apiResource.path("events").queryParam("stream_position", streamPosition.toString())
                        .queryParam("stream_type", streamType.name()).queryParam("limit", limit.toString()),
                GetEventsResponse.class, 200, 204);
    }

    /**
     * Retrieves events for all users in an enterprise. Upper and lower bounds
     * as well as filters can be applied to the results.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:get-events}
     *
     * @param createdAfter
     *            A lower bound on the timestamp of the events returned
     * @param createdBefore
     *            An upper bound on the timestamp of the events returned
     * @param eventTypes
     *            list of events to filter by
     * @param limit
     *            Limits the number of events returned
     * @param offset
     *            The item at which to start
     * @return an instance of
     *         {@link org.mule.modules.box.model.response.GetEventsResponse}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public GetEventsResponse getEnterpriseEvents(@Optional String createdAfter, @Optional String createdBefore,
            @Optional List<String> eventTypes, @Optional @Default("100") Long limit,
            @Optional @Default("0") Long offset) {

        WebResource resource = this.apiResource.path("events").queryParam("stream_type", "admin_logs")
                .queryParam("limit", limit.toString()).queryParam("offset", offset.toString());

        if (createdAfter != null) {
            resource = resource.queryParam("created_after", createdAfter);
        }

        if (createdBefore != null) {
            resource = resource.queryParam("created_before", createdBefore.toString());
        }

        if (!CollectionUtils.isEmpty(eventTypes)) {
            StringBuilder builder = new StringBuilder();
            boolean isFirst = true;
            for (String value : eventTypes) {

                if (isFirst) {
                    isFirst = false;
                } else {
                    builder.append(',');
                }

                builder.append(value);
            }

            resource = resource.queryParam("event_type", builder.toString());
        }

        return this.jerseyUtil.get(resource, GetEventsResponse.class, 200);
    }

    /**
     * Message source that subscribes to the events long polling server and will
     * trigger a new message each time an event is generated.
     *
     * Such message will have an instance of @{link
     * org.mule.modules.box.model.PollingEvent} as payload and an inbound
     * property called 'boxAccessTokenId' that will carry the accessTokenId of
     * the user that owns the event.
     *
     * This source will not provide the events that are generated, it's just a
     * notification that there're new events available. You'll need to use the
     * get-events processor to actually get them. Managing the stream position
     * while doing so is up to you.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:listen-events}
     *
     * @param callback
     *            callback to be invoked when a message arribes
     * @return an instance of {@link org.mule.api.callback.StopSourceCallback}
     *         that unsubscribes the long polling server when the app is
     *         stopped.
     */
    @Source(primaryNodeOnly = true)
    public synchronized StopSourceCallback listenEvents(final SourceCallback callback) {
        if (this.accessToken == null || this.accessTokenIdentifier == null) {
            pendingSubscriptions.add(callback);
        } else {
            this.subscribe(callback);
        }

        return new StopSourceCallback() {

            @Override
            public void stop() throws Exception {
                longPollingClient.unsubscribe();
            }
        };
    }

    /**
     * Requests access to a long polling server that notifies about events in
     * real time.
     *
     * This is just a request for connection details. A subscription to such
     * topic is not made
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample
     * box:get-events-long-polling-server}
     *
     * @return an instance of
     *         {@link org.mule.modules.box.model.LongPollingServer}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public LongPollingServer getEventsLongPollingServer() {
        LongPollingServerResponse response = this.jerseyUtil.options(this.apiResource.path("events"),
                LongPollingServerResponse.class, 200);

        if (CollectionUtils.isEmpty(response.getEntries())) {
            throw new BoxException("Box did not returned a long polling server back");
        }

        return response.getEntries().get(0);
    }

    /**
     * The search endpoint provides a simple way of finding items that are
     * accessible in a given users Box account.
     *
     * {@sample.xml ../../../doc/box-connector.xml.sample box:search}
     *
     * @param query
     *            The string to search for; can be matched against item names,
     *            descriptions, text content of a file, and other fields of the
     *            different item types.
     * @param limit
     *            Number of search results to return
     * @param offset
     *            The search result at which to start the response
     * @return an instance of
     *         {@link org.mule.modules.box.model.response.SearchResponse}
     */
    @Processor
    @OAuthProtected
    @OAuthInvalidateAccessTokenOn(exception = BoxTokenExpiredException.class)
    public SearchResponse search(String query, @Optional @Default("30") Long limit,
            @Optional @Default("0") Long offset) {
        return this.jerseyUtil.get(this.apiResource.path("search").queryParam("query", query)
                .queryParam("limit", limit.toString()).queryParam("offset", offset.toString()),
                SearchResponse.class, 200);
    }

    private void subscribe(final SourceCallback callback) {
        LongPollingServer server = this.getEventsLongPollingServer();
        WebResource pollingResource = this.client.resource(server.getHost()).path("subscribe")
                .queryParam("channel", server.getChannel()).queryParam("stream_type", "all");

        this.longPollingClient.subscribe(pollingResource, this.accessTokenIdentifier, callback);
    }

    private WebResource.Builder maybeAddIfMatch(WebResource resource, String etag) {
        Builder builder = resource.getRequestBuilder();

        if (!StringUtils.isBlank(etag)) {
            builder = builder.header("If-Match", etag);
        }

        return builder;
    }

    private InputStream readLocalFile(String path) {
        java.io.File file = new java.io.File(path);

        if (!file.exists()) {
            throw new IllegalArgumentException(String.format("File %s does not exist", path));
        }

        try {
            return new ByteArrayInputStream(FileUtils.readFileToByteArray(file));
        } catch (IOException e) {
            throw new RuntimeException(String.format("Error reading file at %s", path), e);
        }
    }

    private String hash(InputStream content) {
        byte[] bytes = null;
        try {
            bytes = IOUtils.toByteArray(content);
        } catch (IOException e) {
            throw new RuntimeException("Error generating sha1 for content", e);
        }

        Formatter formatter = new Formatter();
        try {
            for (byte b : bytes) {
                formatter.format("%02x", b);
            }
            return formatter.toString();
        } finally {
            formatter.close();
        }
    }

    private String getAllFolderFieldsAsString() {
        return new StringBuilder().append("sequence_id,").append("etag,").append("name,").append("created_at,")
                .append("modified_at,").append("description,").append("size,").append("path_collection,")
                .append("created_by,").append("modified_by,").append("trashed_at,").append("purged_at,")
                .append("content_created_at,").append("content_modified_at,").append("owned_by,")
                .append("shared_link,").append("folder_upload_email,").append("parent,").append("item_status,")
                .append("item_collection,").append("sync_state,").append("has_collaborations,")
                .append("permissions,").append("tags").toString();
    }

    private String getAllFileFieldsAsString() {
        return new StringBuilder().append("sequence_id,").append("etag,").append("sha1,").append("name,")
                .append("description,").append("size,").append("path_collection,").append("created_at,")
                .append("modified_at,").append("trashed_at,").append("purged_at,").append("content_created_at,")
                .append("content_modified_at,").append("created_by,").append("modified_by,").append("owned_by,")
                .append("shared_link,").append("parent,").append("version_number,").append("comment_count,")
                .append("permissions,").append("tags,").append("lock").toString();
    }

    public String getBaseUrl() {
        return baseUrl;
    }

    public void setBaseUrl(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    public String getUploadUrl() {
        return uploadUrl;
    }

    public void setUploadUrl(String uploadUrl) {
        this.uploadUrl = uploadUrl;
    }

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public String getClientSecret() {
        return clientSecret;
    }

    public void setClientSecret(String clientSecret) {
        this.clientSecret = clientSecret;
    }

    public String getAccessToken() {
        return accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public boolean getUseGzip() {
        return useGzip;
    }

    public void setUseGzip(boolean useGzip) {
        this.useGzip = useGzip;
    }

    public JerseyUtil getJerseyUtil() {
        return jerseyUtil;
    }
}