de.jlo.talendcomp.google.drive.DriveHelper.java Source code

Java tutorial

Introduction

Here is the source code for de.jlo.talendcomp.google.drive.DriveHelper.java

Source

/**
 * Copyright 2015 Jan Lolling jan.lolling@gmail.com
 * 
 * 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.
 */
package de.jlo.talendcomp.google.drive;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.media.MediaHttpDownloader;
import com.google.api.client.googleapis.media.MediaHttpDownloaderProgressListener;
import com.google.api.client.googleapis.media.MediaHttpUploader;
import com.google.api.client.googleapis.media.MediaHttpUploaderProgressListener;
import com.google.api.client.http.FileContent;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.Clock;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.Drive.Files.Get;
import com.google.api.services.drive.DriveRequest;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.FileList;
import com.google.api.services.drive.model.ParentReference;
import com.google.api.services.drive.model.Permission;
import com.google.api.services.drive.model.User;

public class DriveHelper {

    private Logger logger = null;
    private static final Map<String, DriveHelper> clientCache = new HashMap<String, DriveHelper>();
    private final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
    private final JsonFactory JSON_FACTORY = new JacksonFactory();
    private File keyFile; // *.p12 key file is needed
    private String clientSecretFile = null;
    private String accountEmail;
    private String applicationName = null;
    private boolean useServiceAccount = true;
    private boolean useApplicationClientID = false;
    private String credentialDataStoreDir = null;
    private long timeMillisOffsetToPast = 10000;
    private Drive driveService;
    private int timeoutInSeconds = 120;
    private static Map<String, String> mimeTypeMap = null;
    public static final String FOLDER_MIME_TYPE = "application/vnd.google-apps.folder";
    private String lastDownloadedFilePath = null;
    private long lastDownloadedFileSize = 0;
    private int maxRetriesInCaseOfErrors = 1;
    private int currentAttempt = 0;
    private long innerLoopWaitInterval = 100;
    private int errorCode = 0;
    private String errorMessage = null;

    public static void putIntoCache(String key, DriveHelper client) {
        clientCache.put(key, client);
    }

    public static DriveHelper getFromCache(String key) {
        return clientCache.get(key);
    }

    public void setKeyFile(String file) {
        keyFile = new File(file);
    }

    public void setAccountEmail(String email) {
        accountEmail = email;
    }

    public String getApplicationName() {
        return applicationName;
    }

    public void setApplicationName(String applicationName) {
        this.applicationName = applicationName;
    }

    private Credential authorizeWithServiceAccount() throws Exception {
        if (keyFile == null) {
            throw new Exception("KeyFile not set!");
        }
        if (keyFile.canRead() == false) {
            throw new IOException("keyFile:" + keyFile.getAbsolutePath() + " is not readable");
        }
        if (accountEmail == null || accountEmail.isEmpty()) {
            throw new Exception("account email cannot be null or empty");
        }
        // Authorization.
        return new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT).setJsonFactory(JSON_FACTORY)
                .setServiceAccountId(accountEmail)
                .setServiceAccountScopes(Arrays.asList(DriveScopes.DRIVE, DriveScopes.DRIVE_FILE))
                .setServiceAccountPrivateKeyFromP12File(keyFile).setClock(new Clock() {
                    @Override
                    public long currentTimeMillis() {
                        // we must be sure, that we are always in the past from Googles point of view
                        // otherwise we get an "invalid_grant" error
                        return System.currentTimeMillis() - timeMillisOffsetToPast;
                    }
                }).build();
    }

    private Credential authorizeWithClientSecret() throws Exception {
        if (clientSecretFile == null) {
            throw new IllegalStateException("client secret file is not set");
        }
        File secretFile = new File(clientSecretFile);
        if (secretFile.exists() == false) {
            throw new Exception(
                    "Client secret file:" + secretFile.getAbsolutePath() + " does not exists or is not readable.");
        }
        Reader reader = new FileReader(secretFile);
        // Load client secrets.
        GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, reader);
        try {
            reader.close();
        } catch (Throwable e) {
        }
        // Checks that the defaults have been replaced (Default =
        // "Enter X here").
        if (clientSecrets.getDetails().getClientId().startsWith("Enter")
                || clientSecrets.getDetails().getClientSecret().startsWith("Enter ")) {
            throw new Exception(
                    "The client secret file does not contains the credentials. At first you have to pass the web based authorization process!");
        }
        credentialDataStoreDir = secretFile.getParent() + "/" + clientSecrets.getDetails().getClientId() + "/";
        File credentialDataStoreDirFile = new File(credentialDataStoreDir);
        if (credentialDataStoreDirFile.exists() == false && credentialDataStoreDirFile.mkdirs() == false) {
            throw new Exception(
                    "Credentedial data dir does not exists or cannot created:" + credentialDataStoreDir);
        }
        FileDataStoreFactory fdsf = new FileDataStoreFactory(credentialDataStoreDirFile);
        // Set up authorization code flow.
        GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY,
                clientSecrets, Arrays.asList(DriveScopes.DRIVE, DriveScopes.DRIVE_FILE)).setDataStoreFactory(fdsf)
                        .setClock(new Clock() {
                            @Override
                            public long currentTimeMillis() {
                                // we must be sure, that we are always in the past from Googles point of view
                                // otherwise we get an "invalid_grant" error
                                return System.currentTimeMillis() - timeMillisOffsetToPast;
                            }
                        }).build();
        // Authorize.
        return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize(accountEmail);
    }

    /**
     * Initialize the Drive service client
     * Depending of the settings a service account or a client-Id for native applications will be used
     * @throws Exception
     */
    public void initializeClient() throws Exception {
        // Authorization.
        final Credential credential;
        if (useServiceAccount) {
            credential = authorizeWithServiceAccount();
        } else if (useApplicationClientID) {
            credential = authorizeWithClientSecret();
        } else {
            throw new IllegalStateException("No authorisation method set!");
        }
        driveService = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, new HttpRequestInitializer() {
            @Override
            public void initialize(final HttpRequest httpRequest) throws IOException {
                credential.initialize(httpRequest);
                httpRequest.setConnectTimeout(timeoutInSeconds * 1000);
                httpRequest.setReadTimeout(timeoutInSeconds * 1000);
            }
        }).setApplicationName(applicationName).build();
    }

    /**
     * Move a file to another folder on the Drive
     * @param driveFilePathToMove source file path
     * @param driveTargetFolderPath path to the target folder
     * @param createIfNotExists if true missing target folder will be created
     * @return meta data of the target file
     * @throws Exception
     */
    public com.google.api.services.drive.model.File moveToFolderByName(String driveFilePathToMove,
            String driveTargetFolderPath, boolean createIfNotExists) throws Exception {
        com.google.api.services.drive.model.File fileToMove = getByName(driveFilePathToMove);
        if (fileToMove == null) {
            throw new Exception("File " + driveFilePathToMove + " does not extsis in the Drive.");
        } else {
            com.google.api.services.drive.model.File targetFolder = getFolder(driveTargetFolderPath,
                    createIfNotExists);
            if (targetFolder == null) {
                throw new Exception(
                        "Target folder " + driveTargetFolderPath + " does not exists or cannot be created.");
            } else {
                return moveToFolder(fileToMove, targetFolder.getId());
            }
        }
    }

    /**
     * Move a file to another folder on the Drive
     * @param fileId source file Id
     * @param driveTargetFolderPath path to the target folder
     * @param createIfNotExists if true missing target folder will be created
     * @return meta data of the target file
     * @throws Exception
     */
    public com.google.api.services.drive.model.File moveToFolderById(String fileId, String driveTargetFolderPath,
            boolean createIfNotExists) throws Exception {
        com.google.api.services.drive.model.File fileToMove = getById(fileId);
        if (fileToMove == null) {
            throw new Exception("File with id=" + fileId + " does not extsis in the Drive.");
        } else {
            com.google.api.services.drive.model.File targetFolder = getFolder(driveTargetFolderPath,
                    createIfNotExists);
            if (targetFolder == null) {
                throw new Exception(
                        "Target folder " + driveTargetFolderPath + " does not exists or cannot be created.");
            } else {
                return moveToFolder(fileToMove, targetFolder.getId());
            }
        }
    }

    private com.google.api.services.drive.model.File moveToFolder(
            com.google.api.services.drive.model.File fileToMove, String newParentId) throws Exception {
        checkPrerequisits();
        insertFileIntoFolder(newParentId, fileToMove);
        removeAllParentFolders(fileToMove, newParentId);
        Get request = driveService.files().get(fileToMove.getId());
        return (com.google.api.services.drive.model.File) execute(request);
    }

    private void removeAllParentFolders(com.google.api.services.drive.model.File file, String exceptParentId)
            throws Exception {
        checkPrerequisits();
        if (file.getParents() != null) {
            for (ParentReference pr : file.getParents()) {
                if (pr.getId().equals(exceptParentId) == false) {
                    execute(driveService.parents().delete(file.getId(), pr.getId()));
                }
            }
        }
    }

    private void insertFileIntoFolder(String folderId, com.google.api.services.drive.model.File file)
            throws Exception {
        checkPrerequisits();
        if (file.getParents() != null) {
            for (ParentReference pr : file.getParents()) {
                if (pr.getId().equals(folderId)) {
                    throw new Exception("File id=" + file.getId()
                            + " has already a parent reference to the folder-Id=" + folderId);
                }
            }
        }
        ParentReference newParent = new ParentReference();
        newParent.setId(folderId);
        execute(driveService.parents().insert(file.getId(), newParent));
    }

    /**
     * returns the folder referenced by its virtual name, can create the folder structure if not exists
     * @param path the path to find / to create
     * @param createIfNotExists if true create the path if not exists
     * @return the file object of the leaf folder
     * @throws Exception
     */
    public com.google.api.services.drive.model.File getFolder(String path, boolean createIfNotExists)
            throws Exception {
        if (path == null || path.trim().isEmpty()) {
            throw new IllegalArgumentException("path cannot be null or empty.");
        }
        List<com.google.api.services.drive.model.File> allFolders = listAllFolders();
        List<String> pathList = new ArrayList<String>();
        StringTokenizer st = new StringTokenizer(path, "/");
        while (st.hasMoreTokens()) {
            String f = st.nextToken();
            if (f.isEmpty() == false) {
                pathList.add(f);
            }
        }
        if (pathList.isEmpty()) {
            pathList.add("/");
        }
        return getFolder(allFolders, null, pathList, 0, createIfNotExists);
    }

    private com.google.api.services.drive.model.File getFolder(
            List<com.google.api.services.drive.model.File> allFolders,
            com.google.api.services.drive.model.File parent, List<String> pathArray, int level,
            boolean createIfNotExists) throws Exception {
        com.google.api.services.drive.model.File child = null;
        String currentFolderName = pathArray.get(level);
        List<com.google.api.services.drive.model.File> childFolders = getChildFolders(allFolders,
                (parent != null ? parent.getId() : null));
        for (com.google.api.services.drive.model.File folder : childFolders) {
            if (currentFolderName.equalsIgnoreCase(folder.getTitle())) {
                child = folder;
                break;
            }
        }
        if (createIfNotExists && child == null) {
            // we have to create it
            child = createFolder((parent != null ? parent.getId() : null), currentFolderName);
            allFolders.add(child);
        }
        // check if we are at the end of the path array
        if (child != null) {
            if (level < pathArray.size() - 1) {
                // we have to continue to follow the path to the end
                child = getFolder(allFolders, child, pathArray, level + 1, createIfNotExists);
            }
        }
        return child;
    }

    public static String getFirstParentId(com.google.api.services.drive.model.File file) {
        List<ParentReference> parentRefs = file.getParents();
        if (parentRefs != null && parentRefs.size() > 0) {
            return parentRefs.get(0).getId();
        } else {
            return null;
        }
    }

    private List<com.google.api.services.drive.model.File> getChildFolders(
            List<com.google.api.services.drive.model.File> allFolders, String parentId) {
        List<com.google.api.services.drive.model.File> children = new ArrayList<com.google.api.services.drive.model.File>();
        for (com.google.api.services.drive.model.File folder : allFolders) {
            if (folder.getParents() != null) {
                for (ParentReference pr : folder.getParents()) {
                    if (parentId != null) {
                        if (pr.getId().equals(parentId)) {
                            children.add(folder);
                            break;
                        }
                    } else if (pr.getIsRoot()) {
                        children.add(folder);
                        break;
                    }
                }
            } else if (parentId == null) {
                children.add(folder);
            }
        }
        return children;
    }

    private com.google.api.services.drive.model.File createFolder(String parentId, String title) throws Exception {
        checkPrerequisits();
        com.google.api.services.drive.model.File folder = new com.google.api.services.drive.model.File();
        folder.setTitle(title);
        folder.setMimeType(FOLDER_MIME_TYPE);
        if (parentId != null) {
            ParentReference pr = new ParentReference();
            pr.setId(parentId);
            folder.setParents(Arrays.asList(pr));
        }
        return (com.google.api.services.drive.model.File) execute(driveService.files().insert(folder));
    }

    /**
     * Upload a file to the google drive
     * @param localFilePath local file path
     * @param title the alternate title, if null the file name will be used as title
     * @param driveFolderPath target folder
     * @param createDirIfNecessary if true create the target folder if not exists
     * @param overwrite overwrite an existing file with the same title, otherweise if a file already exists an exception will be thrown
     * @return the meta data of the new file
     * @throws Exception
     */
    public com.google.api.services.drive.model.File upload(String localFilePath, String title,
            String driveFolderPath, boolean createDirIfNecessary, boolean overwrite) throws Exception {
        checkPrerequisits();
        if (localFilePath == null || localFilePath.trim().isEmpty()) {
            throw new IllegalArgumentException("localFilePath cannot be null or empty");
        }
        File localFile = new File(localFilePath);
        if (localFile.canRead() == false) {
            throw new Exception("Local upload file: " + localFile.getAbsolutePath() + " cannot be read.");
        }
        if (driveFolderPath != null && driveFolderPath.trim().isEmpty() == false) {
            driveFolderPath = driveFolderPath.trim();
            int pos = driveFolderPath.lastIndexOf("/");
            if (pos == driveFolderPath.length() - 1) {
                driveFolderPath = driveFolderPath.substring(0, pos); // cut up last /
            }
        }
        if (title != null) {
            title = title.trim();
        }
        if (title == null || title.isEmpty()) {
            title = localFile.getName();
        }
        String filePath = null;
        if (driveFolderPath != null) {
            filePath = driveFolderPath + "/" + title;
        } else {
            filePath = title;
        }
        com.google.api.services.drive.model.File existingFile = getByName(filePath);
        if (overwrite == false && existingFile != null) {
            throw new Exception("File " + existingFile.getTitle() + " already exists in the Drive. File-Id="
                    + existingFile.getId());
        }
        if (existingFile == null) {
            info("Upload new file " + localFile.getAbsolutePath());
            String parentId = null;
            if (driveFolderPath != null && driveFolderPath.trim().isEmpty() == false) {
                com.google.api.services.drive.model.File parentFolder = getFolder(driveFolderPath,
                        createDirIfNecessary);
                if (parentFolder != null) {
                    parentId = parentFolder.getId();
                } else {
                    throw new Exception(
                            "Parent folder " + driveFolderPath + " does not exists or cannot be created.");
                }
            }
            com.google.api.services.drive.model.File uploadFile = new com.google.api.services.drive.model.File();
            uploadFile.setTitle(title);
            if (parentId != null) {
                ParentReference pr = new ParentReference();
                pr.setId(parentId);
                uploadFile.setParents(Arrays.asList(pr));
            }
            String mimeType = getMimeType(localFilePath);
            FileContent mediaContent = new FileContent(mimeType, localFile);
            Drive.Files.Insert insertRequest = driveService.files().insert(uploadFile, mediaContent);
            MediaHttpUploader uploader = insertRequest.getMediaHttpUploader();
            uploader.setDirectUploadEnabled(false);
            uploader.setProgressListener(new MediaHttpUploaderProgressListener() {

                @Override
                public void progressChanged(MediaHttpUploader uploader) throws IOException {
                    info("File status: " + uploader.getUploadState());
                    info("Bytes uploaded:" + uploader.getNumBytesUploaded());
                }

            });
            return (com.google.api.services.drive.model.File) execute(insertRequest);
        } else {
            info("Upload existing file " + localFile.getAbsolutePath());
            String mimeType = getMimeType(localFilePath);
            FileContent mediaContent = new FileContent(mimeType, localFile);
            Drive.Files.Update updateRequest = driveService.files().update(existingFile.getId(), existingFile,
                    mediaContent);
            MediaHttpUploader uploader = updateRequest.getMediaHttpUploader();
            uploader.setDirectUploadEnabled(false);
            uploader.setProgressListener(new MediaHttpUploaderProgressListener() {

                @Override
                public void progressChanged(MediaHttpUploader uploader) throws IOException {
                    info("File status: " + uploader.getUploadState());
                    info("Bytes uploaded:" + uploader.getNumBytesUploaded());
                }

            });
            return (com.google.api.services.drive.model.File) execute(updateRequest);
        }
    }

    /**
     * Download a file to the local file system
     * @param driveFilePath path of the file in the Drive
     * @param localFolder target folder in file system
     * @param newFileName if not null this will be the new file name
     * @param createDirs if true missing folders will be created
     * @return the loaded file meta data
     * @throws Exception
     */
    public com.google.api.services.drive.model.File downloadByName(String driveFilePath, String localFolder,
            String newFileName, boolean createDirs) throws Exception {
        com.google.api.services.drive.model.File file = getByName(driveFilePath);
        if (file == null) {
            throw new Exception("File " + driveFilePath + " does not exists in drive.");
        } else {
            return downloadById(file.getId(), localFolder, newFileName, createDirs);
        }
    }

    /**
     * Download a file to the local file system
     * @param fileId ID if the file in the Drive
     * @param localFolder target folder in file system
     * @param newFileName if not null this will be the new file name
     * @param createDirs if true missing folders will be created
     * @return the loaded file meta data
     * @throws Exception
     */
    public com.google.api.services.drive.model.File downloadById(String fileId, String localFolder,
            String newFileName, boolean createDirs) throws Exception {
        if (fileId == null || fileId.trim().isEmpty()) {
            throw new IllegalArgumentException("fileId cannot be null or empty");
        }
        if (localFolder == null || localFolder.trim().isEmpty()) {
            throw new IllegalArgumentException("localFolder cannot be null or empty");
        } else if ((localFolder.endsWith("/") || localFolder.endsWith("\\")) == false) {
            localFolder = localFolder + "/";
        }
        com.google.api.services.drive.model.File file = (com.google.api.services.drive.model.File) execute(
                driveService.files().get(fileId));
        String downLoadFilePath = null;
        if (newFileName != null && newFileName.trim().isEmpty() == false) {
            downLoadFilePath = localFolder + newFileName;
        } else {
            downLoadFilePath = localFolder + file.getTitle();
        }
        if (file.getDownloadUrl() != null) {
            downloadByUrl(file.getDownloadUrl(), downLoadFilePath, createDirs);
        }
        return file;
    }

    /**
     * Delete a file on the Drive
     * @param driveFilePath the path within the drive
     * @param ignoreMissing if true missing file will not throw an exception
     * @return the meta data of he deleted file
     * @throws Exception
     */
    public com.google.api.services.drive.model.File deleteByName(String driveFilePath, boolean ignoreMissing)
            throws Exception {
        com.google.api.services.drive.model.File file = getByName(driveFilePath);
        if (file == null) {
            if (ignoreMissing == false) {
                throw new Exception("File with file path=" + driveFilePath + " does not exists in the drive");
            } else {
                return null;
            }
        } else {
            return deleteById(file.getId(), ignoreMissing);
        }
    }

    /**
     * Delete a file on the Drive
     * @param fileId ID if he file
     * @param ignoreMissing if true a missing file will not cause an exception
     * @return the meta data of the deleted file
     * @throws Exception
     */
    public com.google.api.services.drive.model.File deleteById(String fileId, boolean ignoreMissing)
            throws Exception {
        checkPrerequisits();
        if (fileId == null || fileId.trim().isEmpty()) {
            throw new IllegalArgumentException("fileId cannot be null or empty");
        }
        com.google.api.services.drive.model.File file = getById(fileId);
        if (file != null) {
            execute(driveService.files().delete(file.getId()));
            return file;
        } else {
            if (ignoreMissing == false) {
                throw new Exception("File with id=" + fileId + " does not exists in the drive");
            } else {
                return null;
            }
        }
    }

    /**
     * Get the file meta data
     * @param driveFilePath the file path on the Drive
     * @return file meta data
     * @throws Exception
     */
    public com.google.api.services.drive.model.File getByName(String driveFilePath) throws Exception {
        checkPrerequisits();
        if (driveFilePath == null || driveFilePath.trim().isEmpty()) {
            throw new IllegalArgumentException("filePath cannot be null or empty");
        } else {
            driveFilePath = driveFilePath.trim();
        }
        int pos = driveFilePath.lastIndexOf('/');
        String folderPath = null;
        String title = driveFilePath;
        String parentId = null;
        if (pos > 0) {
            folderPath = driveFilePath.substring(0, pos);
            title = driveFilePath.substring(pos + 1);
            com.google.api.services.drive.model.File parent = getFolder(folderPath, false);
            if (parent == null) {
                return null;
            } else {
                parentId = parent.getId();
            }
        }
        StringBuilder q = new StringBuilder();
        q.append("title='");
        q.append(title);
        q.append("' and trashed=false");
        if (parentId != null) {
            q.append(" and '");
            q.append(parentId);
            q.append("' in parents");
        }
        com.google.api.services.drive.Drive.Files.List request = driveService.files().list();
        if (q.length() > 0) {
            request.setQ(q.toString().trim());
        }
        List<com.google.api.services.drive.model.File> files = executeRequest(request, null);
        if (files.size() > 0) {
            return files.get(0);
        } else {
            return null;
        }
    }

    /**
     * Get the file meta data
     * @param fileId ID of the file
     * @return meta data
     * @throws Exception
     */
    public com.google.api.services.drive.model.File getById(String fileId) throws Exception {
        checkPrerequisits();
        if (fileId == null || fileId.trim().isEmpty()) {
            throw new IllegalArgumentException("fileId cannot be null or empty");
        }
        try {
            com.google.api.services.drive.model.File file = (com.google.api.services.drive.model.File) execute(
                    driveService.files().get(fileId));
            return file;
        } catch (com.google.api.client.googleapis.json.GoogleJsonResponseException ge) {
            if (ge.getStatusCode() == 404) {
                return null; // do not worry about a missing file
            } else {
                throw ge;
            }
        }
    }

    private void downloadByUrl(String fileDownloadUrl, String localFilePath, boolean createDirs) throws Exception {
        checkPrerequisits();
        lastDownloadedFilePath = null;
        lastDownloadedFileSize = 0;
        File localFile = new File(localFilePath);
        if (createDirs && localFile.getParentFile().exists() == false) {
            if (localFile.getParentFile().mkdirs() == false) {
                throw new Exception("Unable to create parent directory: " + localFile.getParent());
            }
        }
        OutputStream fileOut = new BufferedOutputStream(new FileOutputStream(localFile));
        try {
            MediaHttpDownloader downloader = new MediaHttpDownloader(HTTP_TRANSPORT,
                    driveService.getRequestFactory().getInitializer());
            downloader.setDirectDownloadEnabled(false);
            downloader.setProgressListener(new MediaHttpDownloaderProgressListener() {

                @Override
                public void progressChanged(MediaHttpDownloader downloader) throws IOException {
                    info("File status: " + downloader.getDownloadState());
                    info("Bytes downloaded:" + downloader.getNumBytesDownloaded());
                }

            });
            downloader.download(new GenericUrl(fileDownloadUrl), fileOut);
            if (fileOut != null) {
                fileOut.flush();
                fileOut.close();
                fileOut = null;
            }
            lastDownloadedFilePath = localFile.getAbsolutePath();
            lastDownloadedFileSize = localFile.length();
        } catch (Exception e) {
            if (fileOut != null) {
                fileOut.flush();
                fileOut.close();
                fileOut = null;
            }
            Thread.sleep(200);
            if (localFile.canWrite()) {
                System.err.println("Download failed. Remove incomplete file: " + localFile.getAbsolutePath());
                localFile.delete();
            }
            throw e;
        }
    }

    private List<com.google.api.services.drive.model.File> listAllFolders() throws Exception {
        checkPrerequisits();
        com.google.api.services.drive.Drive.Files.List request = driveService.files().list();
        request.setQ("mimeType='" + FOLDER_MIME_TYPE + "' and trashed=false");
        return executeRequest(request, null);
    }

    /**
     * Lists all files from the owner = accountEmail
     * @return a list of File
     * @throws Exception
     */
    public List<com.google.api.services.drive.model.File> list(String localFilterRegex, boolean caseSensitive,
            String qString, String remoteFilter_titleStartsWith, String remoteFilter_fullTextContains,
            Date lastModifyedFrom, Date lastModifyedUntil, String owner, boolean includeFolders,
            String parentFolder, String mimeType) throws Exception {
        checkPrerequisits();
        // prepare the local filter
        Pattern pattern = null;
        if (localFilterRegex != null && localFilterRegex.trim().isEmpty() == false) {
            pattern = Pattern.compile(localFilterRegex, caseSensitive ? 0 : Pattern.CASE_INSENSITIVE);
        }
        StringBuilder q = new StringBuilder();
        if (qString != null && qString.trim().isEmpty() == false) {
            q.append(qString);
        }
        if (owner != null && owner.trim().isEmpty() == false) {
            if (q.length() > 0) {
                q.append(" and ");
            }
            q.append("'");
            q.append(owner.trim());
            q.append("' in owners");
        }
        if (remoteFilter_titleStartsWith != null && remoteFilter_titleStartsWith.trim().isEmpty() == false) {
            if (q.length() > 0) {
                q.append(" and ");
            }
            q.append("title contains '");
            q.append(remoteFilter_titleStartsWith.trim());
            q.append("'");
        }
        if (remoteFilter_fullTextContains != null && remoteFilter_fullTextContains.trim().isEmpty() == false) {
            remoteFilter_fullTextContains = remoteFilter_fullTextContains.replace("\\", "\\\\");
            if (q.length() > 0) {
                q.append(" and ");
            }
            q.append("fullText contains '");
            q.append(remoteFilter_titleStartsWith);
            q.append("'");
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        if (lastModifyedFrom != null) {
            if (q.length() > 0) {
                q.append(" and ");
            }
            q.append("modifiedDate >= '");
            q.append(sdf.format(lastModifyedFrom));
            q.append("'");
        }
        if (lastModifyedUntil != null) {
            if (q.length() > 0) {
                q.append(" and ");
            }
            q.append("modifiedDate < '");
            q.append(sdf.format(lastModifyedUntil));
            q.append("'");
        }
        if (includeFolders == false) {
            if (q.length() > 0) {
                q.append(" and ");
            }
            q.append("mimeType != '");
            q.append(FOLDER_MIME_TYPE);
            q.append("'");
        }
        if (mimeType != null) {
            if (q.length() > 0) {
                q.append(" and ");
            }
            q.append("(mimeType = '");
            q.append(mimeType);
            q.append("'");
            if (includeFolders) {
                q.append(" or mimeType = '");
                q.append(FOLDER_MIME_TYPE);
                q.append("'");
            }
            q.append(")");
        }
        // exclude trashed files
        if (q.length() > 0) {
            q.append(" and ");
        }
        q.append("trashed=false");
        if (parentFolder != null && parentFolder.trim().isEmpty() == false) {
            String parentId = null;
            com.google.api.services.drive.model.File parent = getFolder(parentFolder, false);
            if (parent != null) {
                parentId = parent.getId();
            } else {
                throw new Exception("Parent folder " + parentFolder + " does not exists.");
            }
            if (parentId != null) {
                if (q.length() > 0) {
                    q.append(" and ");
                }
                q.append("'");
                q.append(parentId.trim());
                q.append("' in parents");
            }
        }
        com.google.api.services.drive.Drive.Files.List request = driveService.files().list();
        if (q.length() > 0) {
            request.setQ(q.toString().trim());
        }
        request.setCorpus("DEFAULT");
        return executeRequest(request, pattern);
    }

    private List<com.google.api.services.drive.model.File> executeRequest(
            com.google.api.services.drive.Drive.Files.List request, Pattern pattern) throws Exception {
        List<com.google.api.services.drive.model.File> resultList = new ArrayList<com.google.api.services.drive.model.File>();
        do {
            try {
                FileList files = (FileList) execute(request);
                if (pattern != null) { // apply the local filter
                    Matcher matcher = null;
                    for (com.google.api.services.drive.model.File file : files.getItems()) {
                        matcher = pattern.matcher(file.getTitle());
                        if (matcher.find()) {
                            resultList.add(file);
                        }
                    }
                } else {
                    resultList.addAll(files.getItems());
                }
                request.setPageToken(files.getNextPageToken());
            } catch (IOException e) {
                request.setPageToken(null);
                throw e;
            }
        } while (request.getPageToken() != null && request.getPageToken().length() > 0);
        return resultList;
    }

    private void setPermissions(String role, String fileId, String emails, boolean sendEmailNotification)
            throws Exception {
        if (emails != null && emails.trim().isEmpty() == false) {
            if ("owner".equals(role) || "reader".equals(role) || "writer".equals(role)) {
                checkPrerequisits();
                StringTokenizer st = new StringTokenizer(emails, ",;");
                while (st.hasMoreTokens()) {
                    String email = st.nextToken();
                    if (email != null && email.trim().isEmpty() == false) {
                        Permission p = new Permission();
                        p.setType("user");
                        p.setValue(email);
                        p.setRole(role);
                        execute(driveService.permissions().insert(fileId, p)
                                .setSendNotificationEmails(sendEmailNotification));
                    }
                }
            } else {
                throw new IllegalArgumentException("Unknown role: " + role);
            }
        }
    }

    public void setPermissionAsOwner(String fileId, String emails, boolean sendEmailNotification) throws Exception {
        setPermissions("owner", fileId, emails, sendEmailNotification);
    }

    public void setPermissionAsReader(String fileId, String emails, boolean sendEmailNotification)
            throws Exception {
        setPermissions("reader", fileId, emails, sendEmailNotification);
    }

    public void setPermissionAsWriter(String fileId, String emails, boolean sendEmailNotification)
            throws Exception {
        setPermissions("writer", fileId, emails, sendEmailNotification);
    }

    public void setUseServiceAccount(boolean useServiceAccount) {
        this.useServiceAccount = useServiceAccount;
    }

    public void setClientSecretFile(String clientSecretFile) {
        this.clientSecretFile = clientSecretFile;
    }

    private static void loadMimeTypes() throws Exception {
        InputStream in = DriveHelper.class.getResourceAsStream("/mime.types");
        if (in == null) {
            throw new Exception("Resource mime.types could not be found");
        }
        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        String line = null;
        mimeTypeMap = new HashMap<String, String>();
        while ((line = br.readLine()) != null) {
            if (line.startsWith("#") == false) {
                StringTokenizer st = new StringTokenizer(line, "\t");
                String mimeType = null;
                if (st.hasMoreElements()) {
                    mimeType = st.nextToken().trim();
                }
                String extensions = null;
                if (st.hasMoreElements()) {
                    extensions = st.nextToken().trim();
                }
                String[] extensionArray = extensions.split("\\s");
                for (String ext : extensionArray) {
                    mimeTypeMap.put(ext, mimeType);
                }
            }
        }
    }

    /**
     * get the mime-type of a file
     * @param filePath the file path (with file name)
     * @return the mime-type according to the latest Apache Foundation definition
     * @throws Exception
     */
    public String getMimeType(String filePath) throws Exception {
        int pos = filePath.lastIndexOf('.');
        String fileExtension = null;
        if (pos > 0) {
            fileExtension = filePath.substring(pos);
        }
        if (fileExtension == null) {
            return "text/plain";
        }
        if (mimeTypeMap == null) {
            loadMimeTypes();
        }
        fileExtension = fileExtension.trim().toLowerCase();
        if (fileExtension.startsWith(".")) {
            fileExtension = fileExtension.substring(1);
        }
        return mimeTypeMap.get(fileExtension);
    }

    /**
     * builds a separated String from the list entries
     * @param list
     * @param separator
     * @return the chained strings
     */
    public static String buildChain(List<String> list, String separator) {
        if (list == null || list.isEmpty()) {
            return null;
        }
        boolean firstLoop = true;
        StringBuilder sb = new StringBuilder();
        for (String s : list) {
            if (firstLoop) {
                firstLoop = false;
            } else {
                sb.append(separator);
            }
            sb.append(s);
        }
        return sb.toString();
    }

    /**
     * build a chained list of the email addresses of the Users
     * @param list users
     * @param separator
     * @return chained string
     */
    public static String buildChainForUsers(List<User> list, String separator) {
        if (list == null || list.isEmpty()) {
            return null;
        }
        boolean firstLoop = true;
        StringBuilder sb = new StringBuilder();
        for (User user : list) {
            if (firstLoop) {
                firstLoop = false;
            } else {
                sb.append(separator);
            }
            sb.append(user.getEmailAddress());
        }
        return sb.toString();
    }

    /**
     * build a chained list of the email addresses of the writers
     * @param list users
     * @param separator
     * @return chained string
     */
    public static String buildChainForPermissionWriters(List<Permission> list, String separator) {
        if (list == null || list.isEmpty()) {
            return null;
        }
        boolean firstLoop = true;
        StringBuilder sb = new StringBuilder();
        for (Permission p : list) {
            if ("writer".equalsIgnoreCase(p.getRole())) {
                if (firstLoop) {
                    firstLoop = false;
                } else {
                    sb.append(separator);
                }
                sb.append(p.getEmailAddress());
            }
        }
        return sb.toString();
    }

    /**
     * build a chained list of the email addresses of the writers
     * @param list users
     * @param separator
     * @return chained string
     */
    public static String buildChainForPermissionReaders(List<Permission> list, String separator) {
        if (list == null || list.isEmpty()) {
            return null;
        }
        boolean firstLoop = true;
        StringBuilder sb = new StringBuilder();
        for (Permission p : list) {
            if ("reader".equalsIgnoreCase(p.getRole())) {
                if (firstLoop) {
                    firstLoop = false;
                } else {
                    sb.append(separator);
                }
                sb.append(p.getEmailAddress());
            }
        }
        return sb.toString();
    }

    /**
     * checks if the file object is a folder
     * @param file
     * @return true if folder
     */
    public static boolean isFolder(com.google.api.services.drive.model.File file) {
        return FOLDER_MIME_TYPE.equals(file.getMimeType());
    }

    public void setTimeMillisOffsetToPast(long timeMillisOffsetToPast) {
        this.timeMillisOffsetToPast = timeMillisOffsetToPast;
    }

    public void setTimeoutInSeconds(int timeoutInSeconds) {
        this.timeoutInSeconds = timeoutInSeconds;
    }

    /**
     * the file path of the last download file
     * @return file path
     */
    public String getLastDownloadedFilePath() {
        return lastDownloadedFilePath;
    }

    /**
     * the size of the last download file
     * @return size in byte
     */
    public long getLastDownloadedFileSize() {
        return lastDownloadedFileSize;
    }

    /**
     * returns the Drive service client to re-usage for other DriveHelper instances
     * @return Drive instance
     */
    public Drive getDriveService() {
        return driveService;
    }

    /**
     * set the Drive instance 
     * @param driveService
     */
    public void setDriveService(Drive driveService) {
        if (driveService == null) {
            throw new IllegalArgumentException("Drive Service cannot be null!");
        }
        this.driveService = driveService;
    }

    private void checkPrerequisits() throws Exception {
        if (driveService == null) {
            throw new Exception("Drive service client not initialized or set!");
        }
    }

    public boolean isUseApplicationClientID() {
        return useApplicationClientID;
    }

    public void setUseApplicationClientID(boolean useApplicationClientID) {
        this.useApplicationClientID = useApplicationClientID;
    }

    public void info(String message) {
        if (logger != null) {
            logger.info(message);
        } else {
            System.out.println("INFO:" + message);
        }
    }

    public void debug(String message) {
        if (logger != null) {
            logger.debug(message);
        } else {
            System.out.println("DEBUG:" + message);
        }
    }

    public void warn(String message) {
        if (logger != null) {
            logger.warn(message);
        } else {
            System.err.println("WARN:" + message);
        }
    }

    public void error(String message) {
        error(message, null);
    }

    public void error(String message, Exception e) {
        if (logger != null) {
            if (e != null) {
                logger.error(message, e);
            } else {
                logger.error(message);
            }
        } else {
            System.err.println("ERROR:" + message);
        }
    }

    public void setLogger(Logger logger) {
        this.logger = logger;
    }

    /**
     * sets the maximum attempts to execute a request in case of errors
     * @param maxRetriesInCaseOfErrors
     */
    public void setMaxRetriesInCaseOfErrors(Integer maxRetriesInCaseOfErrors) {
        if (maxRetriesInCaseOfErrors != null) {
            this.maxRetriesInCaseOfErrors = maxRetriesInCaseOfErrors;
        }
    }

    private com.google.api.client.json.GenericJson execute(DriveRequest<?> request) throws IOException {
        try {
            Thread.sleep(innerLoopWaitInterval);
        } catch (InterruptedException e) {
        }
        com.google.api.client.json.GenericJson response = null;
        int waitTime = 1000;
        for (currentAttempt = 0; currentAttempt < maxRetriesInCaseOfErrors; currentAttempt++) {
            errorCode = 0;
            try {
                response = (GenericJson) request.execute();
                break;
            } catch (IOException ge) {
                warn("Got error:" + ge.getMessage());
                if (ge instanceof HttpResponseException) {
                    errorCode = ((HttpResponseException) ge).getStatusCode();
                    errorMessage = ((HttpResponseException) ge).getMessage();
                }
                if (Util.canBeIgnored(ge) == false) {
                    error("Stop processing because of the error does not allow a retry.");
                    throw ge;
                }
                if (currentAttempt == (maxRetriesInCaseOfErrors - 1)) {
                    error("All repetitions of the request failed:" + ge.getMessage(), ge);
                    throw ge;
                } else {
                    // wait
                    try {
                        info("Retry request in " + waitTime + "ms");
                        Thread.sleep(waitTime);
                    } catch (InterruptedException ie) {
                    }
                    int random = (int) Math.random() * 500;
                    waitTime = (waitTime * 2) + random;
                }
            }
        }
        return response;
    }

    public int getLastHttpStatusCode() {
        return errorCode;
    }

    public String getLastHttpStatusMessage() {
        return errorMessage;
    }

}