Javaを使ってSharePointへファイルをアップロードする

# はじめに
文中に出てくるMicrosoftのドキュメントを読めば何となく出来ると思いますが、若干躓いたのでメモします。

# 準備
## Microsoft Graph Java SDK
今回はMicrosoft Graph Java SDKを使います。手順はMicrosoftのページに記載されています。
[Microsoft Graph Java SDK をインストールする](https://learn.microsoft.com/ja-jp/graph/sdks/sdk-installation#install-the-microsoft-graph-java-sdk)

## クライアントID/クライアントシークレット生成(AzureAD)
この手順も既に色々な方が手順を記載しています。最近だと↓の記事が新しいでしょうか。
[次世代のコラボレーション！Microsoft TeamsにChatGPTを組み込む方法](https://qiita.com/akiraokusawa/items/9736c98f39e16040fc77)

アクセス権は「file.readwrite.all」「sites.ReadWrite.all」が必要のようです。詳細はMicrosoftのページに記載されています。
DriveItem の内容をアップロードまたは置換する
https://learn.microsoft.com/ja-jp/graph/api/driveitem-put-content?view=graph-rest-1.0&tabs=http


## siteidの取得
ファイルをアップロードしたいサイトを一意に示すIDがsiteidのようです。siteidはSharePoint管理センターとかで、URLに表示されますが、Graph Explorerを使って取得することができます。

Graph Explorerにブラウザでアクセスしてログインします。
https://developer.microsoft.com/en-us/graph/graph-explorer

以下のURLをGETでリクエストします。

```
https://graph.microsoft.com/v1.0/sites/<hostname>.sharepoint.com:/sites/<sitename>
```

サイトのURLが以下の場合は、

```
https://XXXXX.sharepoint.com/sites/YYYYY
```

リクエストするURLは以下になります。
```
https://graph.microsoft.com/v1.0/sites/XXXXX.sharepoint.com:/sites/YYYYY
```

以下のJSONがレスポンスされます。
```json
{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#sites/$entity",
    "createdDateTime": "2017-11-10T00:57:10.92Z",
    "description": "*****",
    "id": "*****.sharepoint.com,16bc4df0-7e1a-4cec-9ade-86f56a8e9b6e,320ed486-518c-4495-8997-1af19888f3c6",
    "lastModifiedDateTime": "2023-05-02T08:38:46Z",
    "name": "zangyo",
    "webUrl": "https://*****.sharepoint.com/sites/*****",
    "displayName": "*****",
    "root": {},
    "siteCollection": {
        "hostname": "*****.sharepoint.com"
    }
}
```

上から5行目のIDにsiteidが含まれています。
```
"id": "*****.sharepoint.com,16bc4df0-7e1a-4cec-9ade-******,320ed486-518c-4495-8997-1a*****",
```

カンマ区切りの2つ目の「16bc〜」がsiteidになります。

# 実装

SDKにはファイルサイズが小さい用と大きい用の２つが用意されています。何をもって小さい大きいのかよく分からなかったです。

## ファイルサイズが小さい場合
Microsoftのドキュメントにサンプルが載っていますが、サンプルでは自分のOneDriveにアップロードする場合なので、SharePointでは若干変更が必要です。

DriveItem の内容をアップロードまたは置換する
https://learn.microsoft.com/ja-jp/graph/api/driveitem-put-content?view=graph-rest-1.0&tabs=java

```java
import java.io.FileInputStream;
import java.io.IOException;

import org.apache.commons.io.IOUtils;

import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.requests.GraphServiceClient;

public class SharePointUploadTest {

	public static void main(String[] args) throws IOException {

		final ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
		        .clientId("<クライアントID>")
		        .clientSecret("<クライアントシークレット>")
		        .tenantId("<テナントID>")
		        .build();

		final TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(clientSecretCredential);

		GraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider( tokenCredentialAuthProvider ).buildClient();

		FileInputStream inputStream = new FileInputStream("<アップロードするファイルパス>");
        byte[] stream = IOUtils.toByteArray(inputStream);

        String siteid = "<siteid>";
		graphClient.sites().byId(siteid).drive().root().itemWithPath("<アップロード先フォルダ&ファイル名>").content().buildRequest().put(stream);
	}
}
```

サイトの「Shared Documents」がルートになるようなので、以降のパスを指定します。
「/hoge/hoge1.png」この場合は「Shared Documents/hoge/hoge1.png」にアップロードされます。
- 指定されたフォルダが存在しない場合は自動で作成される。
- 同名ファイルが存在した場合は上書き

このサンプルソースで130MBのファイルをアップロードしたところ正常終了しました。Javaヒープサイズ次第なような気がしますが。

## ファイルサイズが大きい場合
再開可能なAPI(UploadSession)が用意されており、ファイルサイズが大きい場合はこちらを使用します。Javaの場合は再開機能はサポートされていないようです。
UploadSessionを使うと、チャンクな感じでアップロードすることができます。

driveItem: createUploadSession
https://learn.microsoft.com/ja-jp/graph/api/driveitem-createuploadsession?view=graph-rest-1.0&preserve-view=true

```java
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.models.DriveItem;
import com.microsoft.graph.models.DriveItemCreateUploadSessionParameterSet;
import com.microsoft.graph.models.DriveItemUploadableProperties;
import com.microsoft.graph.models.UploadSession;
import com.microsoft.graph.requests.GraphServiceClient;
import com.microsoft.graph.tasks.IProgressCallback;
import com.microsoft.graph.tasks.LargeFileUploadTask;

public class SharePointUploadTest2 {

	public static void main(String[] args) throws IOException {

		final ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
		        .clientId("<クライアントID>")
		        .clientSecret("<クライアントシークレット>")
		        .tenantId("<テナントID>")
		        .build();

		final TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(clientSecretCredential);

		GraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider( tokenCredentialAuthProvider ).buildClient();

		File file = new File("<アップロードするファイルパス>");
		FileInputStream inputStream = new FileInputStream(file);

		IProgressCallback callback = new IProgressCallback() {
		    @Override
		    public void progress(final long current, final long max) {
		        System.out.println(
		            String.format("Uploaded %d bytes of %d total bytes", current, max)
		        );
		    }
		};

		DriveItemCreateUploadSessionParameterSet uploadParams =
			    DriveItemCreateUploadSessionParameterSet.newBuilder()
			        .withItem(new DriveItemUploadableProperties()).build();


		UploadSession uploadSession = graphClient.sites().byId("<siteid>").drive().root()
				.itemWithPath("<アップロード先フォルダ&ファイル名>")
				.createUploadSession(uploadParams)
				.buildRequest().post();

		LargeFileUploadTask<DriveItem> largeFileUploadTask =
			    new LargeFileUploadTask<DriveItem>
			        (uploadSession, graphClient, inputStream, file.length(), DriveItem.class);
		largeFileUploadTask.upload(0, null, callback);
	}
}
```

Microsoftのサンプルをサイト指定にしただけで、あとは殆ど変更していません。

# 終わりに
サンプルを見ればすぐ出来ますが、今回はsiteidの取得の仕方で大分悩みました。