Skip to content

Commit 0237d4c

Browse files
committed
Merge branch 'feat/2.1-next' into jpportier/DEVEXP-1224-consent-management
2 parents 52272b1 + 66e77c3 commit 0237d4c

9 files changed

Lines changed: 108 additions & 17 deletions

File tree

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
2-
* @asein-sinch @JPPortier @matsk-sinch @rpredescu-sinch
2+
* @asein-sinch @JPPortier @matsk-sinch @rpredescu-sinch @edu-sinch @Hurus111

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ All notable changes to the **Sinch Java SDK** are documented in this file.
2121
### Conversation
2222
- **[feature]** Support `Consents` API: `listIdentities` and `listAuditRecords` endpoints
2323

24+
### SDK
25+
- **[fix]** `HttpClientApache`: declare now `headersToBeAdded` as `volatile` to guarantee visibility across threads in concurrent usage
26+
- **[fix]** `HttpClientApache`: wrap response-body `Scanner` in a try-with-resources block to prevent resource leaks; gracefully handle empty (`null`) response entities
27+
- **[fix]** `SinchClient`: guard against a `NullPointerException` when `java.vendor` system property is absent while building the `User-Agent` auxiliary flag
28+
- **[fix]** `Configuration`: correct copy-paste error in `toString()` and Javadoc — `conversationContext` label was incorrectly attributed to the Voice domain
29+
30+
### Examples / Snippets
31+
- **[doc]** Fix typos in `conversation/conversations/Create` and `voice/applications/GetEventDestinations` snippets
32+
2433
---
2534

2635
## v2.0 – 2026-03-31

client/src/main/com/sinch/sdk/SinchClient.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
import com.sinch.sdk.models.adapters.DualToneMultiFrequencyMapper;
2020
import java.io.IOException;
2121
import java.io.InputStream;
22+
import java.util.ArrayList;
2223
import java.util.Collection;
23-
import java.util.Collections;
2424
import java.util.Objects;
2525
import java.util.Properties;
2626
import java.util.logging.Logger;
@@ -430,11 +430,16 @@ private String formatSdkUserAgentHeader() {
430430
}
431431

432432
private String formatAuxiliaryFlag() {
433+
return formatAuxiliaryFlag(SDK.AUXILIARY_FLAG);
434+
}
433435

434-
Collection<String> values = Collections.singletonList(System.getProperty("java.vendor"));
435-
436-
if (!StringUtil.isEmpty(SDK.AUXILIARY_FLAG)) {
437-
values.add(SDK.AUXILIARY_FLAG);
436+
// Package-private to allow unit-testing with an arbitrary flag value
437+
String formatAuxiliaryFlag(String auxiliaryFlag) {
438+
Collection<String> values = new ArrayList<>();
439+
String vendor = System.getProperty("java.vendor");
440+
values.add(StringUtil.isEmpty(vendor) ? "" : vendor);
441+
if (!StringUtil.isEmpty(auxiliaryFlag)) {
442+
values.add(auxiliaryFlag);
438443
}
439444
return String.join(",", values);
440445
}

client/src/main/com/sinch/sdk/http/HttpClientApache.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class HttpClientApache implements com.sinch.sdk.core.http.HttpClient {
4747

4848
private static final Logger LOGGER = Logger.getLogger(HttpClientApache.class.getName());
4949

50-
private Map<String, String> headersToBeAdded;
50+
private volatile Map<String, String> headersToBeAdded;
5151

5252
private volatile CloseableHttpClient client;
5353

@@ -71,10 +71,15 @@ private static HttpResponse processResponse(ClassicHttpResponse response) throws
7171
}
7272

7373
HttpEntity entity = response.getEntity();
74-
Scanner s = new Scanner(entity.getContent()).useDelimiter("\\A");
75-
String content = (s.hasNext() ? s.next() : "");
76-
77-
return new HttpResponse(statusCode, message, headers, content.getBytes(StandardCharsets.UTF_8));
74+
if (null == entity) {
75+
return new HttpResponse(statusCode, message, headers, null);
76+
}
77+
try (Scanner s =
78+
new Scanner(entity.getContent(), StandardCharsets.UTF_8.name()).useDelimiter("\\A")) {
79+
String content = (s.hasNext() ? s.next() : "");
80+
return new HttpResponse(
81+
statusCode, message, headers, content.getBytes(StandardCharsets.UTF_8));
82+
}
7883
}
7984

8085
private static Map<String, List<String>> transformResponseHeaders(Header[] headers) {

client/src/main/com/sinch/sdk/models/Configuration.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ public String toString() {
5151
+ verificationContext
5252
+ ", voiceContext="
5353
+ voiceContext
54-
+ ", conversationRegion="
55-
+ conversationContext
5654
+ ", conversationContext="
5755
+ conversationContext
5856
+ "}";
@@ -153,9 +151,9 @@ public Optional<ApplicationCredentials> getApplicationCredentials() {
153151
}
154152

155153
/**
156-
* Get Voice domain related execution context
154+
* Get Conversation domain related execution context
157155
*
158-
* @return Current Voice context
156+
* @return Current Conversation context
159157
* @since 1.0
160158
*/
161159
public Optional<ConversationContext> getConversationContext() {

client/src/test/java/com/sinch/sdk/SinchClientTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,43 @@ void voiceApplicationManagementUrlFromUrl() {
218218
client.getConfiguration().getVoiceContext().get().getVoiceApplicationManagementUrl(),
219219
"my foo url");
220220
}
221+
222+
@Test
223+
void formatAuxiliaryFlagReturnsVendorWhenFlagIsEmpty() {
224+
SinchClient client = new SinchClient();
225+
// AUXILIARY_FLAG is "" in SDK — only vendor should be present, no comma separator
226+
String result = client.formatAuxiliaryFlag("");
227+
assertNotNull(result);
228+
assertEquals(
229+
System.getProperty("java.vendor"),
230+
result,
231+
"When auxiliaryFlag is empty, the result should match java.vendor exactly");
232+
}
233+
234+
@Test
235+
void formatAuxiliaryFlagHandlesNullJavaVendor() {
236+
String original = System.getProperty("java.vendor");
237+
try {
238+
System.clearProperty("java.vendor");
239+
SinchClient client = new SinchClient();
240+
String result = client.formatAuxiliaryFlag("");
241+
assertNotNull(result);
242+
assertFalse(result.contains("null"), "Null java.vendor must not render as 'null' string");
243+
} finally {
244+
if (null != original) {
245+
System.setProperty("java.vendor", original);
246+
}
247+
}
248+
}
249+
250+
@Test
251+
void formatAuxiliaryFlagAppendsNonEmptyFlag() {
252+
SinchClient client = new SinchClient();
253+
String result = client.formatAuxiliaryFlag("my-wrapper/1.0");
254+
assertNotNull(result);
255+
assertTrue(result.contains(","), "A comma must separate vendor from the auxiliary flag");
256+
assertTrue(
257+
result.endsWith(",my-wrapper/1.0"),
258+
"Auxiliary flag must be the last element after the comma");
259+
}
221260
}

client/src/test/java/com/sinch/sdk/http/HttpClientApacheTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,16 @@
99
import com.sinch.sdk.core.http.HttpRequest;
1010
import com.sinch.sdk.core.http.HttpResponse;
1111
import com.sinch.sdk.core.models.ServerConfiguration;
12+
import java.io.ByteArrayInputStream;
1213
import java.lang.reflect.Field;
14+
import java.lang.reflect.Method;
15+
import java.nio.charset.StandardCharsets;
1316
import java.util.*;
17+
import java.util.Scanner;
1418
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
19+
import org.apache.hc.core5.http.ClassicHttpResponse;
20+
import org.apache.hc.core5.http.Header;
21+
import org.apache.hc.core5.http.HttpEntity;
1522
import org.junit.jupiter.api.BeforeEach;
1623
import org.junit.jupiter.api.Test;
1724

@@ -75,4 +82,32 @@ void testInvokeApiHandles401WithEmptyHeaders() throws Exception {
7582
// AND: resetToken() is not called (no headers to check)
7683
verify(mockAuthManager, never()).resetToken();
7784
}
85+
86+
@Test
87+
void processResponseDecodesBodyAsUtf8() throws Exception {
88+
String nonAscii = "Ünïcödé resülts";
89+
byte[] utf8Bytes = nonAscii.getBytes(StandardCharsets.UTF_8);
90+
91+
HttpEntity entity = mock(HttpEntity.class);
92+
when(entity.getContent()).thenReturn(new ByteArrayInputStream(utf8Bytes));
93+
94+
ClassicHttpResponse httpResponse = mock(ClassicHttpResponse.class);
95+
when(httpResponse.getCode()).thenReturn(200);
96+
when(httpResponse.getReasonPhrase()).thenReturn("OK");
97+
when(httpResponse.getHeaders()).thenReturn(new Header[0]);
98+
when(httpResponse.getEntity()).thenReturn(entity);
99+
100+
Method processResponse =
101+
HttpClientApache.class.getDeclaredMethod("processResponse", ClassicHttpResponse.class);
102+
processResponse.setAccessible(true);
103+
104+
HttpResponse result = (HttpResponse) processResponse.invoke(null, httpResponse);
105+
106+
assertNotNull(result.getContent());
107+
try (Scanner s =
108+
new Scanner(result.getContent(), StandardCharsets.UTF_8.name()).useDelimiter("\\A")) {
109+
String decoded = s.hasNext() ? s.next() : "";
110+
assertEquals(nonAscii, decoded, "Response body must round-trip through UTF-8 correctly");
111+
}
112+
}
78113
}

examples/snippets/src/main/java/conversation/conversations/Create.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static void main(String[] args) {
5252

5353
LOGGER.info(
5454
String.format(
55-
"Create conversation for applicatoin with ID '%s'", conversationApplicationId));
55+
"Create conversation for application with ID '%s'", conversationApplicationId));
5656

5757
Conversation response = conversationsService.create(request);
5858

examples/snippets/src/main/java/voice/applications/GetEventDestinations.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public static void main(String[] args) {
3333

3434
ApplicationsService applicationsService = client.voice().v1().applications();
3535

36-
LOGGER.info(String.format("Get even destinations for application key '%s'", applicationKey));
36+
LOGGER.info(String.format("Get event destinations for application key '%s'", applicationKey));
3737

3838
EventDestinations response = applicationsService.getEventDestinations(applicationKey);
3939

0 commit comments

Comments
 (0)