-
Notifications
You must be signed in to change notification settings - Fork 29
Updating a file via LargeFileUploadTask does not work (NullPointerException) #1517
Description
This is a duplication of #microsoftgraph/msgraph-sdk-java#1804.
Initially, I opened the issue on that repo, but it is more correct opening it here
As stated in the object, updating (i.e. modifying) a file via the LargeFileUploadTask does not work and results in a NullPointerException.
Expected behavior
Not receiving a NullPointerException
Actual behavior
Receiving a NullPointerException
API version
implementation 'com.microsoft.graph:microsoft-graph:6.2.0' // Include the sdk as a dependency
Steps to reproduce the behavior
- Create a new file
DriveItemUploadableProperties uploadableProperties = new DriveItemUploadableProperties();
uploadableProperties.setName(name);
uploadableProperties.setFileSize(length);
uploadableProperties.getAdditionalData().put("@microsoft.graph.conflictBehavior", "fail");
CreateUploadSessionPostRequestBody uploadSessionPostRequestBody = new CreateUploadSessionPostRequestBody();
uploadSessionPostRequestBody.setItem(uploadableProperties);
UploadSession uploadSession = drive.items().byDriveItemId(parent.getId() + ":/" + name + ":").createUploadSession().post(uploadSessionPostRequestBody);
LargeFileUploadTask<DriveItem> largeFileUploadTask = new LargeFileUploadTask<>(
client.getRequestAdapter(), uploadSession, content, length, DriveItem::createFromDiscriminatorValue);
UploadResult<DriveItem> uploadResult = largeFileUploadTask.upload();- Update it
Note the difference in the upload session creation: above (creation) we use the parentId and the file namebyDriveItemId(parent.getId() + ":/" + name + ":")while, below (update), we use the file idbyDriveItemId(id)
DriveItemUploadableProperties uploadableProperties = new DriveItemUploadableProperties();
uploadableProperties.setName(name);
uploadableProperties.setFileSize(length);
uploadableProperties.getAdditionalData().put("@microsoft.graph.conflictBehavior", "replace");
CreateUploadSessionPostRequestBody uploadSessionPostRequestBody = new CreateUploadSessionPostRequestBody();
uploadSessionPostRequestBody.setItem(uploadableProperties);
UploadSession uploadSession = drive.items().byDriveItemId(id).createUploadSession().post(uploadSessionPostRequestBody);
LargeFileUploadTask<DriveItem> largeFileUploadTask = new LargeFileUploadTask<>(
client.getRequestAdapter(), uploadSession, content, length, DriveItem::createFromDiscriminatorValue);
UploadResult<DriveItem> uploadResult = largeFileUploadTask.upload();- You have a NullPointerException
java.lang.NullPointerException: Cannot invoke "java.util.Collection.toArray()" because "c" is null
at java.base/java.util.ArrayList.([ArrayList.java:181](vscode-file://vscode-app/c:/Users/03200692/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
at com.microsoft.graph.core.models.UploadSession.getNextExpectedRanges([UploadSession.java:61](vscode-file://vscode-app/c:/Users/03200692/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
at com.microsoft.graph.core.requests.upload.UploadResponseHandler.handleResponse([UploadResponseHandler.java:79](vscode-file://vscode-app/c:/Users/03200692/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
at com.microsoft.graph.core.requests.upload.UploadSliceRequestBuilder.put([UploadSliceRequestBuilder.java:69](vscode-file://vscode-app/c:/Users/03200692/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
at com.microsoft.graph.core.tasks.LargeFileUploadTask.uploadSlice([LargeFileUploadTask.java:207](vscode-file://vscode-app/c:/Users/03200692/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
at com.microsoft.graph.core.tasks.LargeFileUploadTask.upload([LargeFileUploadTask.java:131](vscode-file://vscode-app/c:/Users/03200692/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
at com.microsoft.graph.core.tasks.LargeFileUploadTask.upload([LargeFileUploadTask.java:111](vscode-file://vscode-app/c:/Users/03200692/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
[...]
Important
The file on the onedrive is correctly updated!!!
Bug hunting
When you create a file, the server returns a 201 Created with the following response: :
{
"createdBy":{
"application":{
"id":"4c3cb947"
},
"user":{
"id":"83a259e7e5f58e69"
}
},
"createdDateTime":"2024-02-21T10:53:40.397Z",
"cTag":"aYzo4M0EyNTlFN0U1RjU4RTY5ITE3NjM4OS4yNTc",
"eTag":"aODNBMjU5RTdFNUY1OEU2OSExNzYzODkuMA",
"id":"83A259E7E5F58E69!176389",
"lastModifiedBy":{
"application":{
"id":"4c3cb947"
},
"user":{
"id":"83a259e7e5f58e69"
}
},
"lastModifiedDateTime":"2024-02-21T10:53:40.397Z",
"name":"f1",
"parentReference":{
"driveId":"83a259e7e5f58e69",
"driveType":"personal",
"id":"83A259E7E5F58E69!176388",
"name":"testFolder1708512810754",
"path":"/drive/root:/testFolder1708512810754"
},
"size":4096,
"webUrl":"https://1drv.ms/u/s!AGmO9eXnWaKDiuIF",
"items":[
],
"file":{
"hashes":{
"quickXorHash":"5gRtDBd9iwj8tz2tb1ViJeM3gX8=",
"sha1Hash":"3CCFE9D871858B15B3F00FCB0AF47D171D9D9E2F",
"sha256Hash":"55A65AE013961B1FC704C0371DC4A6936A6251685B09B014AB48BDAC27258AC7"
},
"mimeType":"application/octet-stream"
},
"fileSystemInfo":{
"createdDateTime":"2024-02-21T10:53:40.396Z",
"lastModifiedDateTime":"2024-02-21T10:53:40.396Z"
},
"reactions":{
"commentCount":0
},
"tags":[
],
"lenses":[
]
}while, when you update a file the server returns a 200 OK with the following response:
{
"createdBy":{
"application":{
"id":"4c3cb947"
},
"user":{
"id":"83a259e7e5f58e69"
}
},
"createdDateTime":"2024-02-21T10:53:40.397Z",
"cTag":"aYzo4M0EyNTlFN0U1RjU4RTY5ITE3NjM4OS4yNTg",
"eTag":"aODNBMjU5RTdFNUY1OEU2OSExNzYzODkuMw",
"id":"83A259E7E5F58E69!176389",
"lastModifiedBy":{
"application":{
"id":"4c3cb947"
},
"user":{
"id":"83a259e7e5f58e69"
}
},
"lastModifiedDateTime":"2024-02-21T10:54:04.21Z",
"name":"f1",
"parentReference":{
"driveId":"83a259e7e5f58e69",
"driveType":"personal",
"id":"83A259E7E5F58E69!176388",
"name":"testFolder1708512810754",
"path":"/drive/root:/testFolder1708512810754"
},
"size":4096,
"webUrl":"https://1drv.ms/u/s!AGmO9eXnWaKDiuIF",
"items":[
],
"file":{
"hashes":{
"quickXorHash":"T43AsKcwz5bu2iAV5IcCghvWqRY=",
"sha1Hash":"2C8C824E1300C1FE6F3A8C919F9ECDD2CD47CF8D",
"sha256Hash":"7FF463935CD4132EBA2958AB568514A249EFA2A0A7E07CC2916223603162FD65"
},
"mimeType":"application/octet-stream"
},
"fileSystemInfo":{
"createdDateTime":"2024-02-21T10:53:40.396Z",
"lastModifiedDateTime":"2024-02-21T10:54:04.193Z"
},
"reactions":{
"commentCount":0
},
"tags":[
],
"lenses":[
]
}The response is the same (and, actually, it's as documented in the REST reference, but the problem is in the HTTP response code. The response, indeed, is parsed by com.microsoft.graph.core.requests.upload.UploadResponseHandler#handleResponse:
Objects.requireNonNull(response);
Objects.requireNonNull(factory);
try (final ResponseBody body = response.body()) {
if (Objects.isNull(body)) {
throw new ApiException(ErrorConstants.Messages.NO_RESPONSE_FOR_UPLOAD);
}
try(final InputStream in = body.byteStream()){
final String contentType = body.contentType().toString().split(";")[0]; //contentType.toString() returns in format <mediaType>;<charset>, we only want the mediaType.
if(!response.isSuccessful()) {
throw new ApiExceptionBuilder()
.withMessage(ErrorConstants.Codes.GENERAL_EXCEPTION)
.withResponseStatusCode(response.code())
.withResponseHeaders(HeadersCompatibility.getResponseHeaders(response.headers()))
.build();
}
UploadResult<T> uploadResult = new UploadResult<>();
if (response.code() == HttpURLConnection.HTTP_CREATED) {
if (body.contentLength() > 0) {
final ParseNode uploadTypeParseNode = parseNodeFactory.getParseNode(contentType, in);
uploadResult.itemResponse = uploadTypeParseNode.getObjectValue(factory);
}
final String location = response.headers().get("location");
if(!Objects.isNull(location) && !location.isEmpty()) {
uploadResult.location = new URI(location);
}
} else {
final ParseNode parseNode = parseNodeFactory.getParseNode(contentType, in);
final UploadSession uploadSession = parseNode.getObjectValue(UploadSession::createFromDiscriminatorValue);
if (!uploadSession.getNextExpectedRanges().isEmpty()) {
uploadResult.uploadSession = uploadSession;
} else {
uploadResult.itemResponse = parseNode.getObjectValue(factory);
}
}
return uploadResult;
}
}
catch(IOException | URISyntaxException ex) {
throw new RuntimeException(ex);
}At the 18th line there is the block that manages the creation response.code() == HttpURLConnection.HTTP_CREATED; if you update the file instead of creating it, you receive a 201 and go in the else block which fails at the line if (!uploadSession.getNextExpectedRanges().isEmpty()) because the received JSON (see above) does not have any property called nextExpectedRanges thus uploadSession.getNextExpectedRanges() returns null and invoking isEmpty() causes the NPE.
Possible solution
Replace line 18 of the method:
if (response.code() == HttpURLConnection.HTTP_CREATED) {with:
int responseCode = response.code();
if (responseCode == HttpURLConnection.HTTP_CREATED || responseCode == HttpURLConnection.HTTP_OK) {I haven't tried it (not quite at ease with github and pull requests, but that should work)