Skip to content

Commit 680ee8c

Browse files
committed
Change to continuation token handling
1 parent f28f11b commit 680ee8c

File tree

6 files changed

+111
-10
lines changed

6 files changed

+111
-10
lines changed

sdk/digitaltwins/azure-digitaltwins-core/src/main/java/com/azure/digitaltwins/core/DigitalTwinsAsyncClient.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,17 @@
4646
import com.azure.digitaltwins.core.models.UpdateComponentOptions;
4747
import com.azure.digitaltwins.core.models.UpdateDigitalTwinOptions;
4848
import com.azure.digitaltwins.core.models.UpdateRelationshipOptions;
49+
import com.azure.json.JsonProviders;
50+
import com.azure.json.JsonReader;
51+
import com.azure.json.JsonWriter;
4952
import reactor.core.publisher.Mono;
5053

5154
import java.io.IOException;
55+
import java.io.StringWriter;
5256
import java.io.UncheckedIOException;
5357
import java.util.ArrayList;
5458
import java.util.List;
59+
import java.util.Map;
5560
import java.util.Objects;
5661
import java.util.stream.Collectors;
5762

@@ -1541,7 +1546,23 @@ <T> Mono<PagedResponse<T>> queryNextPage(String nextLink, Class<T> clazz, QueryO
15411546
context = Context.NONE;
15421547
}
15431548

1544-
QuerySpecification querySpecification = new QuerySpecification().setContinuationToken(nextLink);
1549+
// The nextLink continuation token is a stringified JSON object. Attempt to deserialize the nextLink into any
1550+
// Object and inspect whether is what a stringified JSON value (List = array, Map = object, String = string).
1551+
// If it was a stringified value, use nextLink as-is. If not, serialize the nextLink into a JSON string.
1552+
String nextLinkToUse;
1553+
try (JsonReader jsonReader = JsonProviders.createReader(nextLink)) {
1554+
Object untyped = jsonReader.readUntyped();
1555+
1556+
if (untyped instanceof List || untyped instanceof Map || untyped instanceof String) {
1557+
nextLinkToUse = nextLink;
1558+
} else {
1559+
nextLinkToUse = serializeNextLink(nextLink);
1560+
}
1561+
} catch (IOException ex) {
1562+
nextLinkToUse = serializeNextLink(nextLink);
1563+
}
1564+
1565+
QuerySpecification querySpecification = new QuerySpecification().setContinuationToken(nextLinkToUse);
15451566

15461567
return protocolLayer.getQueries()
15471568
.queryTwinsWithResponseAsync(querySpecification, OptionsConverter.toProtocolLayerOptions(options), context)
@@ -1557,6 +1578,15 @@ <T> Mono<PagedResponse<T>> queryNextPage(String nextLink, Class<T> clazz, QueryO
15571578
objectPagedResponse.getDeserializedHeaders()));
15581579
}
15591580

1581+
private static String serializeNextLink(String nextLink) {
1582+
try (StringWriter writer = new StringWriter(); JsonWriter jsonWriter = JsonProviders.createWriter(writer)) {
1583+
jsonWriter.writeString(nextLink);
1584+
return writer.toString();
1585+
} catch (IOException ex) {
1586+
throw LOGGER.logExceptionAsError(new UncheckedIOException(ex));
1587+
}
1588+
}
1589+
15601590
//endregion Query APIs
15611591

15621592
//region Event Route APIs

sdk/digitaltwins/azure-digitaltwins-core/src/main/java/com/azure/digitaltwins/core/implementation/models/QuerySpecification.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33
// Code generated by Microsoft (R) AutoRest Code Generator.
4-
54
package com.azure.digitaltwins.core.implementation.models;
65

76
import com.azure.core.annotation.Fluent;
@@ -16,6 +15,7 @@
1615
*/
1716
@Fluent
1817
public final class QuerySpecification implements JsonSerializable<QuerySpecification> {
18+
1919
/*
2020
* The query to execute. This value is ignored if a continuation token is provided.
2121
*/
@@ -34,7 +34,7 @@ public QuerySpecification() {
3434

3535
/**
3636
* Get the query property: The query to execute. This value is ignored if a continuation token is provided.
37-
*
37+
*
3838
* @return the query value.
3939
*/
4040
public String getQuery() {
@@ -43,7 +43,7 @@ public String getQuery() {
4343

4444
/**
4545
* Set the query property: The query to execute. This value is ignored if a continuation token is provided.
46-
*
46+
*
4747
* @param query the query value to set.
4848
* @return the QuerySpecification object itself.
4949
*/
@@ -55,7 +55,7 @@ public QuerySpecification setQuery(String query) {
5555
/**
5656
* Get the continuationToken property: A token which is used to retrieve the next set of results from a previous
5757
* query.
58-
*
58+
*
5959
* @return the continuationToken value.
6060
*/
6161
public String getContinuationToken() {
@@ -65,7 +65,7 @@ public String getContinuationToken() {
6565
/**
6666
* Set the continuationToken property: A token which is used to retrieve the next set of results from a previous
6767
* query.
68-
*
68+
*
6969
* @param continuationToken the continuationToken value to set.
7070
* @return the QuerySpecification object itself.
7171
*/
@@ -81,13 +81,15 @@ public QuerySpecification setContinuationToken(String continuationToken) {
8181
public JsonWriter toJson(JsonWriter jsonWriter) throws IOException {
8282
jsonWriter.writeStartObject();
8383
jsonWriter.writeStringField("query", this.query);
84-
jsonWriter.writeStringField("continuationToken", this.continuationToken);
84+
if (this.continuationToken != null) {
85+
jsonWriter.writeRawField("continuationToken", this.continuationToken);
86+
}
8587
return jsonWriter.writeEndObject();
8688
}
8789

8890
/**
8991
* Reads an instance of QuerySpecification from the JsonReader.
90-
*
92+
*
9193
* @param jsonReader The JsonReader being read.
9294
* @return An instance of QuerySpecification if the JsonReader was pointing to an instance of it, or null if it was
9395
* pointing to JSON null.
@@ -99,7 +101,6 @@ public static QuerySpecification fromJson(JsonReader jsonReader) throws IOExcept
99101
while (reader.nextToken() != JsonToken.END_OBJECT) {
100102
String fieldName = reader.getFieldName();
101103
reader.nextToken();
102-
103104
if ("query".equals(fieldName)) {
104105
deserializedQuerySpecification.query = reader.getString();
105106
} else if ("continuationToken".equals(fieldName)) {
@@ -108,7 +109,6 @@ public static QuerySpecification fromJson(JsonReader jsonReader) throws IOExcept
108109
reader.skipChildren();
109110
}
110111
}
111-
112112
return deserializedQuerySpecification;
113113
});
114114
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
& (Join-Path $PSScriptRoot ".." ".." ".." ".." eng scripts Invoke-Codegeneration.ps1) -Directory $PSScriptRoot

sdk/digitaltwins/azure-digitaltwins-core/swagger/autorest.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ implementation-subpackage: implementation
4747
models-subpackage: implementation.models
4848
custom-types-subpackage: models
4949
required-fields-as-ctor-args: true
50+
customization-class: src/main/java/DigitalTwinsCustomization.java
5051
```
5152
5253
## This directive removes the specified enum values from the swagger so the code generator will expose IfNonMatch header as an option instead of always attaching it to requests with its only default value.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>com.azure</groupId>
9+
<artifactId>azure-code-customization-parent</artifactId>
10+
<version>1.0.0-beta.1</version> <!-- {x-version-update;com.azure:azure-code-customization-parent;current} -->
11+
<relativePath>../../../parents/azure-code-customization-parent</relativePath>
12+
</parent>
13+
14+
<name>Microsoft Azure Digital Twins code customization</name>
15+
<description>This package contains code customization for Azure Digital Twins</description>
16+
17+
<groupId>com.azure.tools</groupId>
18+
<artifactId>azure-digitaltwins-core-autorest-customization</artifactId>
19+
<version>1.0.0-beta.1</version>
20+
<packaging>jar</packaging>
21+
</project>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import com.azure.autorest.customization.ClassCustomization;
5+
import com.azure.autorest.customization.Customization;
6+
import com.azure.autorest.customization.LibraryCustomization;
7+
import com.azure.autorest.customization.PackageCustomization;
8+
import com.github.javaparser.StaticJavaParser;
9+
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
10+
import com.github.javaparser.ast.body.MethodDeclaration;
11+
import org.slf4j.Logger;
12+
13+
/**
14+
* This class contains the customization code to customize the AutoRest generated code for Digital Twins.
15+
*/
16+
public class DigitalTwinsCustomization extends Customization {
17+
18+
@Override
19+
public void customize(LibraryCustomization customization, Logger logger) {
20+
PackageCustomization implModels = customization.getPackage("com.azure.digitaltwins.core.implementation.models");
21+
22+
customizeQuerySpecification(implModels.getClass("QuerySpecification"));
23+
}
24+
25+
/**
26+
* Customization for Digital Twins {@code QuerySpecification}.
27+
* <p>
28+
* Digital Twins designed continuation tokens to use a JSON stringified value, rather than a full object type. So,
29+
* QuerySpecification's handling of its {@code continuationToken} field in JSON serialization needs to write the
30+
* value as raw JSON rather than a JSON string. If written as a JSON string it would strigify the continuation token
31+
* a second time, resulting in incorrect JSON being sent to the service, resulting in the operation failing.
32+
*/
33+
private static void customizeQuerySpecification(ClassCustomization customization) {
34+
customization.customizeAst(ast -> ast.getClassByName(customization.getClassName()).ifPresent(clazz -> {
35+
MethodDeclaration toJsonMethod = clazz.getMethodsByName("toJson").get(0);
36+
String toJsonMethodBodyString = toJsonMethod.getBody().get().toString();
37+
38+
// Replace 'jsonWriter.writeStringField("continuationToken", this.continuationToken)' with
39+
// if (this.continuationToken != null) { jsonWriter.writeRawField("continuationToken", this.continuationToken); }
40+
// to have the continuationToken written as raw JSON as needed by the service to prevent double
41+
// stringifying.
42+
toJsonMethodBodyString = toJsonMethodBodyString.replace(
43+
"jsonWriter.writeStringField(\"continuationToken\", this.continuationToken);",
44+
"if (this.continuationToken != null) {jsonWriter.writeRawField(\"continuationToken\", this.continuationToken);}");
45+
toJsonMethod.setBody(StaticJavaParser.parseBlock(toJsonMethodBodyString));
46+
}));
47+
}
48+
}

0 commit comments

Comments
 (0)