Skip to content

andy-miles/onedrive-java-sdk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

61 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Contributors Forks Stargazers Issues LinkedIn


Logo

ondrive-java-sdk

A Java SDK to access OneDrive drives and files.
Maven Project Info - Javadoc
Report Bug - Request Feature

Table of Contents
  1. About The Project
  2. Usage
  3. Roadmap
  4. Contributing
  5. License
  6. Contact

About The Project

A configurable and extensible SDK for Java programmatic access to a user's OneDrive account via the Microsoft Graph API. This SDK is still in active development, but most documented APIs are implemented.

Feature Highlights

  1. OAuth user authentication out-of-the-box for use by client-side desktop applications
    1. Or you can roll your own OAuth solution to obtain the auth code and persist tokens for server-to-server use-cases
  2. Automatic credential token refresh support
  3. Synchronous and asynchronous file transfer operations with a customizable transfer progress callback interface for extensibility
  4. Business account support to access group resources as well as SharePoint document libraries
    1. Note: This is currently untested with a real business account. Please file a bug report if any issues are encountered.

Current Unsupported Features

  1. Upload sessions for uploading larges files in segments
  2. Remote uploads from URL (in preview)
  3. Obtaining content for a Thumbnail

Usage

Getting Started

Per the App Registration documentation, your application needs to be registered via the Azure Apps Registration Page.

Key configuration to note:

  1. The following delegated API permissions are recommended: Files.ReadWrite.All User.Read offline_access
    1. The following delegated API permission are recommended for business accounts in order to access SharePoint sites: Sites.ReadWrite.All Sites.Manage.All Sites.FullControl.All
  2. If using the default OAuth receiver to handle the redirect for auth code grants, then set the redirect URL to http://localhost:8890/Callback
  3. Generate your own client secret, and record your application's client ID and client secret value.
    1. You can save this as a JAR bundled resource within your project named /ms-onedrive-credentials.json and should be formatted as:

      {
        "clientId" : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "clientSecret" : "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
      }

Primary Classes

The primary classes used to interact with a OneDrive account is modeled as a tree structure and is as follows:

  1. OneDrive (javadoc) to obtain user accessible drives
    1. BusinessOneDrive (javadoc) to obtain business-related user accessible drives and sites
  2. Drive (javadoc) to access folders
  3. DriveFolder (javadoc) to manage folders, access subfolders, and upload new files
  4. DrivePackage (javadoc) to manage folders, access subfolders, and upload new files for packages (OneNote)
  5. DriveFile (javadoc) to manage, upload (as a new version), and download a file

Recipes

Obtaining an authenticated OneDrive object

// Used to initiate the OAuth flow, persist refreshed tokens, or use persisted refresh tokens.
OneDriveFactoryStateManager factoryStateManager = OneDriveFactoryStateManager.builder()
        .stateFile(Paths.get("./OneDriveUserState.json")) // Path to save/read user auth tokens
        .build();
try {
    OneDrive oneDrive = factoryStateManager.getInstance();
    // Access drives, folders, and files
} finally {
    // Updates persisted auth tokens if refreshed during usage
    factoryStateManager.saveState();
}

For more information, please refer to javadoc or source.

Customizing user auth token state persistence

AuthInfoStore example

The AuthInfoStore interface provides the ability to implement custom logic to manage user auth token state persistence (e.g., database, remote service, etc.). The default implementation is SingleUserFileBasedAuthInfoStore that saves the auth state to the file system. It also serves as an example on how to implement your own AuthInfoStore implementation.

public class MyAuthInfoStore implements AuthInfoStore {
    private DependentDestinationStore myStore;

    @Override
    public void store(String id, AuthInfo authInfo) throws IOException {
        myStore.save(id, authInfo);
    }

    @Override
    public AuthInfo retrieve(String id) throws IOException {
        myStore.get(id);
    }
}

OneDriveFactoryStateManager factoryStateManager = OneDriveFactoryStateManager.builder()
           .authInfoStore(new MyAuthInfoStore()))
           .build();
try {
    OneDrive oneDrive = factoryStateManager.getInstance();
    // Access drives, folders, and files
} finally {
    // Updates persisted auth tokens if refreshed during usage to your custom store
    factoryStateManager.saveState();
}

Using the provided SingleUserEncryptedFileBasedAuthInfoStore

SingleUserEncryptedFileBasedAuthInfoStore example

This SDK also provides a SingleUserEncryptedFileBasedAuthInfoStore implementation that encrypts and saves the auth state to the file system. A specified keystore file path along with associated passwords must be specified. If the key store file does not exist, a new one will be created along with a newly generated crypto key that is used to encrypt and decrypt the AuthInfo.

Path myOneDriveUserState = Paths.get("./MyOneDriveUserState.json")
Path myKeyStorePath = Paths.get("./MyKeyStoreFile");
char[] myKeyStorePassword = System.getProperty("MyKeyStorePassword").toCharArray();
char[] myAuthStateCryptoKeyPassword = System.getProperty("MyCryptoKeyPassword").toCharArray();

// Helper to manage storage of the crypto key
KeyStoreHelper keyStoreHelper = new KeyStoreHelper(myKeyStorePath, myKeyStorePassword);
// Helper to encrypt/decrypt AuthInfo contents
CryptoHelper cryptoHelper = new CryptoHelperFactory(keyStoreHelper, myAuthStateCryptoKeyPassword).newInstance();
// The AuthInfoStore implementation that encrypts + saves and reads + decrypts from the filesystem.
SingleUserEncryptedFileBasedAuthInfoStore authInfoStore =
        new SingleUserEncryptedFileBasedAuthInfoStore(myOneDriveUserState, cryptoHelper);

OneDriveFactoryStateManager factoryStateManager = OneDriveFactoryStateManager.builder()
        .authInfoStore(authInfoStore)
        .build();

Customizing the HTTP client configuration

OkHttpClientBuilder example

If your use-case requires configuring the underlying OkHttpClient instance (e.g., configuring your own SSL cert verification, proxy, and/or connection timeouts), you can configure the client with the provided OkHttpClientBuilder, or alternatively with OkHttp's builder.

OkHttpClient httpClient = OkHttpClientBuilder.builder()
        .trustManager(myX509TrustManager) // Custom trust manager for self/internally signed SSL/TLS certs
        .hostnameVerifier(myHostnameVerifier) // Custom hostname verification for SSL/TLS endpoints
        .proxy(myProxy, myProxyUsername, myProxyPassword) // Proxy config
        .connectTimeout(8000L) // connection timeout in milliseconds
        .readTimeout(5000L) // read timeout in milliseconds
        .writeTimeout(5000L) // write timeout in milliseconds
        .build();
OneDriveFactoryStateManager factoryStateManager = OneDriveFactoryStateManager.builder()
        .httpClient(httpClient)
        .stateFile(Paths.get("./OneDriveUserState.json")) // Path to save/read user auth tokens
        .build();

try {
    OneDrive oneDrive = factoryStateManager.getInstance();
    // Access drives, folders, and files
} finally {
    // Updates persisted auth tokens if refreshed during usage
    factoryStateManager.saveState();
}

Obtaining a OneDrive with a custom OAuth flow

Custom OAuth flow

Once you obtain the authCode, you can initialize a new OneDrive directly via:

OneDrive oneDrive = new OneDrive(OneDriveConnectionBuilder.newInstance()
      .clientId(clientId) // Your application's client identifier
      .clientSecret(clientSecret) // Your application's client secret
      .redirectUrl(redirectUrl) // Your custom redirect URL that was used to obtain the authCode
      .build(authCode));

While token refresh is automated during the runtime lifecycle of the OneDrive object, persisting the user AuthInfo is required for subsequent initialization(s) to prevent the user from granting authorization each time an instance is created until the user explicitly revokes the grant or when it expires. Example of subsequent initialization with AuthInfo

AuthInfo authInfo = getAuthInfo(); // Obtain the persisted AuthInfo from your application
OneDrive oneDrive = new OneDrive(OneDriveConnectionBuilder.newInstance()
        .clientId(clientId) // Your application's client identifier
        .clientSecret(clientSecret) // Your application's client secret
        .redirectUrl(redirectUrl) // Your custom redirect URL that was used to obtain the authCode
        .build(authInfo));
authInfo = oneDrive.getAuthInfo(); // Gets the updated tokens after refresh

Obtaining a BusinessOneDrive instance

BusinessOneDrive initialization
OkHttpClient httpClient = new OkHttpClientBuilder().build();

BusinessAccountAuthManager authManager = BusinessAccountAuthManager.builderWithAuthCode()
        .authCode(myAuthCode) // Auth code from your OAuth handshake
        .clientId(myClientId) // Your application's client identifier
        .clientSecret(myClientSecret) // Your application's client secret
        .httpClient(httpClient)
        .redirectUrl(myRedirectUrl) // The redirect URL associated with your OAuth flow
        .buildWithAuthCode();

// Discover and authenticate with a registered service
List<Service> services = authManager.getServices();
authManager.authenticateService(services.get(0));

// Create a new BusinessOneDrive instance
BusinessOneDrive oneDrive = new BusinessOneDrive(
    OneDriveConnectionBuilder.newInstance()
        .httpClient(httpClient)
        .authManager(authManager)
        .build(authManager.getAuthInfo()));

// Access business related resources
Site rootSite = oneDrive.getRootSite();

Obtaining list of contents of a user's default drive

DriveFolder rootFolder = oneDrive.getUserDrive().getRootFolder();

// All children containing either DriveFile or DriveFolder types
List<? extends DriveItemType> allContents = rootFolder.getChildren();

// All child folders with the root folder as parent
List<DriveFolder> driveFolders = rootFolder.getChildFolders();

// All child files with the root folder as parent
List<DriveFile> driveFiles = rootFolder.getChildFiles()

Upload a new file to the "Documents" folder

DriveFolder documentsFolder = documentsFolder = root.search("Documents").stream()
        .filter(DriveItemType::isFolder)
        .findFirst()
        .map(DriveFolder.class::cast)
        .orElseThrow(() -> new IllegalStateException("Documents not found"));

DriveFile myDrivefile = documentsFolder.upload(new File("./MyFile.zip"));

Upload a new file asynchronously

DriveFileUploadExecution uploadedExec = documentsFolder.uploadAsync(new File("./MyFile.zip"));

// Block on upload completion
DriveFile myDrivefile = uploadedExec.get();

Download a file to the specified folder

DriveFile myDriveFile = root.search("MyFile.zip").stream()
        .filter(DriveItemType::isFile)
        .findFirst()
        .map(DriveFile.class::cast)
        .orElseThrow(() -> new IllegalStateException("MyFile.zip not found"));

myDriveFile.download(Path.of("./"));

Download a file asynchronously

DriveFileDownloadExecution downloadExec = myDriveFile.downloadAsync(Paths.get("./"));

// Block on download completion
long downloadedBytes = downloadExec.get();

Monitoring transfer progress

The TransferProgressCallback interface provides the ability to implement custom logic on update, completion, or failure scenarios (e.g., GUI updates) during file transfers. The default implementation is LogProgressCallback that logs transfer updates to the configured log via SLF4J. It also serves as an example on how to implement your own TransferProgressCallback implementation.

public class MyProgressCallback implements TransferProgressCallback {
   @Override
   public void onUpdate(long currentBytes, long totalBytes) { ... }

   @Override
   public void onFailure(Throwable cause) { ... }

   @Override
   public void onComplete(long bytesTransferred) { ... }
}

// Upload
DriveFile myDrivefile = myFolder.upload(new File("./MyFile.zip"), new MyProgressCallback());
DriveFileUploadExecution uploadedExec = myFolder.uploadAsync(new File("./MyFile.zip"), new MyProgressCallback());

// Download
myFile.download(Path.of("./"), new MyProgressCallback());
DriveFileDownloadExecution downloadExec = myFile.downloadAsync(Paths.get("./"), new MyProgressCallback());

Roadmap

  • Add functional test coverage (use of a MockWebServer)
  • Add integration test coverage
  • Group and Site based access for non-personal accounts
  • Add an interface to access and persist tokens for the OneDriveFactoryStateManager (e.g., tokens stored via a database or service) (v0.1.1)
  • Obtaining embeddable file previews (v0.1.2)
  • Remote uploads from URL (in preview)
  • Obtaining content for a Thumbnail
  • Add configuration of a retry policy + strategy to support automatic retries for retriable errors

See the open issues for a full list of proposed features and known issues.

Contributing

If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

License

Distributed under the GPLv3 license. See LICENSE for more information.

Contact

Andy Miles - andy.miles@amilesend.com

Project Link: https://github.com/andy-miles/onedrive-java-sdk