Skip to content

Commit 0290ea2

Browse files
authored
CASL-1785: fix HttpStorageProperties bug (#575)
* CASL-1785: fix HttpStorageProperties bug Signed-off-by: Marcin-Here <ext-marcin.grabowski@here.com> * CASL-1785: added test to confirm HttpStorageProperties correct behavior. Signed-off-by: Marcin-Here <ext-marcin.grabowski@here.com> --------- Signed-off-by: Marcin-Here <ext-marcin.grabowski@here.com>
1 parent b10c1ea commit 0290ea2

8 files changed

Lines changed: 239 additions & 14 deletions

File tree

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ mavenPassword=YourPassword
1616
# here-naksha-lib-psql/src/commonMain/kotlin/naksha/psql/LibPsql.kt (adminVersion property)
1717
# Warning: Only update LibPsql version, if there is a change in SQL functions!
1818
# The reason is, that this version is encoded in the database, and when updated, forced an upgrade!
19-
version=3.0.0-beta.35
19+
version=3.0.0-beta.36
2020

2121
org.gradle.jvmargs=-Xmx12g
2222
kotlin.code.style=official

here-naksha-app-service/src/jvmMain/resources/swagger/openapi.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ servers:
1212
info:
1313
title: "Naskha Hub-API"
1414
description: "Naksha Hub-API is a REST API to provide simple access to geo data."
15-
version: "3.0.0-beta.35"
15+
version: "3.0.0-beta.36"
1616

1717
security:
1818
- AccessToken: [ ]

here-naksha-lib-model/src/commonMain/kotlin/naksha/model/NakshaVersion.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class NakshaVersion(
8989
* The current version as string to constant usage cases.
9090
* @since 3.0
9191
*/
92-
const val CURRENT = "3.0.0-beta.35"
92+
const val CURRENT = "3.0.0-beta.36"
9393
// WARNING: Do not update this property manually, it is automatically modified when building!
9494
// Edit version only in `gradle.properties` file, which is used as well to create artifacts!
9595

here-naksha-storage-http/src/jvmMain/java/com/here/naksha/storage/http/HttpStorage.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,7 @@ protected void initStorage(@NotNull NakshaStorage config, @Nullable Boolean crea
6262
if (httpStorageProperties == null || httpStorageProperties.getUrl() == null) {
6363
throw new IllegalArgumentException("A HTTP storage must have properties containing a 'url'");
6464
}
65-
this.defaultKeyProperties = new KeyProperties(
66-
config.getId(),
67-
httpStorageProperties.getUrl(),
68-
httpStorageProperties.getHeaders(),
69-
httpStorageProperties.getConnectTimeout(),
70-
httpStorageProperties.getSocketTimeout(),
71-
httpStorageProperties.getMaxRetries()
72-
);
65+
this.defaultKeyProperties = KeyProperties.fromHttpStorageProperties(config.getId(), httpStorageProperties);
7366
}
7467

7568
@NotNull

here-naksha-storage-http/src/jvmMain/java/com/here/naksha/storage/http/HttpStorageProperties.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package com.here.naksha.storage.http;
2020

21+
import naksha.base.JvmMapProxy;
2122
import naksha.model.NakshaError;
2223
import naksha.model.NakshaException;
2324
import naksha.model.NakshaVersion;
@@ -35,6 +36,16 @@
3536
@AvailableSince(NakshaVersion.v2_0_12)
3637
public class HttpStorageProperties extends NakshaProperties {
3738

39+
static final class HeaderMap extends JvmMapProxy<String, String> {
40+
private HeaderMap() {
41+
super(String.class, String.class);
42+
}
43+
44+
private void putHeaders(@NotNull Map<String, String> headers) {
45+
headers.forEach(this::put);
46+
}
47+
}
48+
3849
public static final Integer DEF_CONNECTION_TIMEOUT_SEC = 20;
3950
public static final Integer DEF_SOCKET_TIMEOUT_SEC = 90;
4051
public static final Integer DEF_MAX_RETRIES = 1;
@@ -105,11 +116,29 @@ public void setMaxRetries(final @Nullable Integer maxRetries) {
105116
* By default: 'Content-Type: application/json' and 'Accept-Encoding: gzip'
106117
*/
107118
public @NotNull Map<String, String> getHeaders() {
108-
return getOrSet(HEADERS, DEFAULT_HEADERS);
119+
final Object raw = get(HEADERS);
120+
if (raw instanceof HeaderMap) {
121+
return (HeaderMap) raw;
122+
}
123+
if (raw instanceof Map<?, ?>) {
124+
HeaderMap headers = toHeaderMap((Map<?, ?>) raw);
125+
setRaw(HEADERS, headers);
126+
return headers;
127+
}
128+
HeaderMap headers = new HeaderMap();
129+
headers.putHeaders(DEFAULT_HEADERS);
130+
setRaw(HEADERS, headers);
131+
return headers;
109132
}
110133

111134
public void setHeaders(final @Nullable Map<String, String> headers) {
112-
setRaw(HEADERS, headers);
135+
if (headers == null) {
136+
setRaw(HEADERS, null);
137+
return;
138+
}
139+
HeaderMap headerMap = new HeaderMap();
140+
headerMap.putHeaders(headers);
141+
setRaw(HEADERS, headerMap);
113142
}
114143

115144
public @NotNull HttpInterface getProtocol() {
@@ -134,4 +163,14 @@ public void setHeaders(final @Nullable Map<String, String> headers) {
134163

135164
public void setProtocol(final HttpInterface protocol) {setRaw(HTTP_INTERFACE, protocol);}
136165

166+
private @NotNull HeaderMap toHeaderMap(@NotNull Map<?, ?> rawHeaders) {
167+
HeaderMap headers = new HeaderMap();
168+
rawHeaders.forEach((key, value) -> {
169+
if (key instanceof String && value instanceof String) {
170+
headers.put((String) key, (String) value);
171+
}
172+
});
173+
return headers;
174+
}
175+
137176
}

here-naksha-storage-http/src/jvmMain/java/com/here/naksha/storage/http/RequestSender.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,19 @@ public KeyProperties(
165165
this.maxRetries = maxRetries;
166166
}
167167

168+
public static @NotNull KeyProperties fromHttpStorageProperties(
169+
@NotNull String name,
170+
@NotNull HttpStorageProperties properties) {
171+
return new KeyProperties(
172+
name,
173+
properties.getUrl(),
174+
properties.getHeaders(),
175+
properties.getConnectTimeout(),
176+
properties.getSocketTimeout(),
177+
properties.getMaxRetries()
178+
);
179+
}
180+
168181
public @NotNull String getName() {
169182
return name;
170183
}

here-naksha-storage-http/src/jvmTest/java/com/here/naksha/storage/http/HttpStoragePropertiesTest.java

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
package com.here.naksha.storage.http;
22

33
import org.junit.jupiter.api.Test;
4+
import naksha.base.JvmBoxingUtil;
5+
import naksha.base.JvmJsonUtil;
6+
import naksha.base.Platform;
7+
import naksha.model.objects.NakshaStorage;
8+
9+
import java.io.IOException;
10+
import java.io.InputStream;
11+
import java.nio.charset.StandardCharsets;
12+
import java.util.HashMap;
13+
import java.util.Map;
414

515
import static org.junit.jupiter.api.Assertions.*;
616

717
class HttpStoragePropertiesTest {
818

19+
private static final String TEST_RESOURCE_DIR = "/unit_test_data/HttpStorageProperties/";
20+
921
@Test
1022
void shouldReturnDefaultValuesOnCreation() {
1123
// Given: a new HttpStorageProperties object created with the default constructor
@@ -39,4 +51,160 @@ void should_set_and_get_all_properties_correctly() {
3951
assertEquals(testSocketTimeout, properties.getSocketTimeout());
4052
assertEquals(testMaxRetries, properties.getMaxRetries());
4153
}
42-
}
54+
55+
@Test
56+
void shouldNormalizeRawHeadersMapFromBoxedProperties() {
57+
final HttpStorageProperties properties = new HttpStorageProperties();
58+
final Map<String, String> rawHeaders = new HashMap<>();
59+
rawHeaders.put("Authorization", "Bearer <token>");
60+
rawHeaders.put("Content-Type", "application/json");
61+
62+
// Simulate the v3 boxed/proxy state before the typed getter runs its normalization logic.
63+
properties.setRaw("headers", rawHeaders);
64+
65+
final Map<String, String> headers = properties.getHeaders();
66+
assertEquals("Bearer <token>", headers.get("Authorization"));
67+
assertEquals("application/json", headers.get("Content-Type"));
68+
assertEquals(2, headers.size());
69+
assertEquals(headers, properties.getRaw("headers"));
70+
assertTrue(headers instanceof HttpStorageProperties.HeaderMap);
71+
assertInstanceOf(Map.class, properties.getRaw("headers"));
72+
assertNotSame(rawHeaders, headers);
73+
}
74+
75+
@Test
76+
void shouldStoreHeadersAsProxyWhenUsingSetter() {
77+
final HttpStorageProperties properties = new HttpStorageProperties();
78+
final Map<String, String> headers = Map.of(
79+
"Authorization", "Bearer exampleToken",
80+
"Content-Type", "application/json"
81+
);
82+
83+
properties.setHeaders(headers);
84+
85+
assertEquals(headers, properties.getHeaders());
86+
assertTrue(properties.getHeaders() instanceof HttpStorageProperties.HeaderMap);
87+
assertInstanceOf(Map.class, properties.getRaw("headers"));
88+
assertNotSame(headers, properties.getRaw("headers"));
89+
}
90+
91+
@Test
92+
void shouldReturnDefaultsForInvalidRawValues() {
93+
final HttpStorageProperties properties = new HttpStorageProperties();
94+
95+
properties.setRaw("headers", "invalid");
96+
97+
assertEquals(HttpStorageProperties.DEFAULT_HEADERS, properties.getHeaders());
98+
}
99+
100+
@Test
101+
void shouldDeserializeAllFieldsFromJson() {
102+
final HttpStorageProperties properties = jsonResourceToPropertiesOrFail("t01_testConvertAllFields");
103+
104+
assertEquals("https://example.org", properties.getUrl());
105+
assertEquals(60, properties.getConnectTimeout());
106+
assertEquals(3600, properties.getSocketTimeout());
107+
108+
final Map<String, String> headers = properties.getHeaders();
109+
assertEquals("Bearer <token>", headers.get("Authorization"));
110+
assertEquals("application/json", headers.get("Content-Type"));
111+
assertEquals(2, headers.size());
112+
}
113+
114+
@Test
115+
void shouldPreserveHeadersWhenBoxingStoragePropertiesFromJson() {
116+
final String storageJson;
117+
try {
118+
storageJson = jsonResourceToStringOrFail("t05_testBoxStorageProperties");
119+
} catch (IOException e) {
120+
fail("Unable to convert json resource", e);
121+
return;
122+
}
123+
124+
final NakshaStorage storage = JvmBoxingUtil.box(Platform.fromJSON(storageJson), NakshaStorage.class);
125+
assertNotNull(storage);
126+
127+
final HttpStorageProperties properties = JvmBoxingUtil.box(storage.getProperties(), HttpStorageProperties.class);
128+
assertNotNull(properties);
129+
130+
final Object rawHeaders = properties.get("headers");
131+
assertInstanceOf(Map.class, rawHeaders);
132+
assertFalse(rawHeaders instanceof HttpStorageProperties.HeaderMap);
133+
134+
final Map<String, String> headers = properties.getHeaders();
135+
assertEquals("Bearer boxed-token", headers.get("Authorization"));
136+
assertEquals("demo", headers.get("X-Tenant"));
137+
assertFalse(headers.containsKey("Accept-Encoding"));
138+
assertEquals(2, headers.size());
139+
assertTrue(headers instanceof HttpStorageProperties.HeaderMap);
140+
}
141+
142+
@Test
143+
void shouldDeserializeMissingValuesToDefaultsFromJson() {
144+
final HttpStorageProperties properties = jsonResourceToPropertiesOrFail("t02_testConvertMissingToNull");
145+
146+
assertEquals("https://example.org", properties.getUrl());
147+
assertEquals(HttpStorageProperties.DEF_CONNECTION_TIMEOUT_SEC, properties.getConnectTimeout());
148+
assertEquals(HttpStorageProperties.DEF_SOCKET_TIMEOUT_SEC, properties.getSocketTimeout());
149+
assertEquals(HttpStorageProperties.DEF_MAX_RETRIES, properties.getMaxRetries());
150+
assertEquals(HttpStorageProperties.DEFAULT_HEADERS, properties.getHeaders());
151+
}
152+
153+
@Test
154+
void shouldIgnoreExcessFieldsInJson() {
155+
assertDoesNotThrow(() -> jsonResourceToPropertiesOrFail("t03_testDontThrowOnExcessFields"));
156+
}
157+
158+
@Test
159+
void shouldDeserializeMissingUrlAsNullInJson() {
160+
final HttpStorageProperties properties = jsonResourceToPropertiesOrFail("t04_testThrowOnMissingMandatory");
161+
162+
assertNull(properties.getUrl());
163+
assertEquals(60, properties.getConnectTimeout());
164+
assertEquals(3600, properties.getSocketTimeout());
165+
assertEquals("Bearer <token>", properties.getHeaders().get("Authorization"));
166+
assertEquals("application/json", properties.getHeaders().get("Content-Type"));
167+
}
168+
169+
@Test
170+
void shouldUseNormalizedHeadersMapInKeyProperties() {
171+
final HttpStorageProperties properties = new HttpStorageProperties();
172+
final Map<String, String> rawHeaders = new HashMap<>();
173+
rawHeaders.put("Authorization", "Bearer <token>");
174+
rawHeaders.put("Content-Type", "application/json");
175+
properties.setUrl("https://example.org");
176+
properties.setRaw("headers", rawHeaders);
177+
178+
final RequestSender.KeyProperties fromProperties = RequestSender.KeyProperties.fromHttpStorageProperties("test-storage", properties);
179+
180+
assertNotNull(fromProperties.getDefaultHeaders());
181+
assertTrue(fromProperties.getDefaultHeaders() instanceof HttpStorageProperties.HeaderMap);
182+
assertNotSame(rawHeaders, fromProperties.getDefaultHeaders());
183+
assertEquals("Bearer <token>", fromProperties.getDefaultHeaders().get("Authorization"));
184+
assertEquals("application/json", fromProperties.getDefaultHeaders().get("Content-Type"));
185+
assertEquals(2, fromProperties.getDefaultHeaders().size());
186+
}
187+
188+
private HttpStorageProperties jsonResourceToPropertiesOrFail(String fileName) {
189+
try {
190+
String json = jsonResourceToStringOrFail(fileName);
191+
HttpStorageProperties properties = JvmJsonUtil.readJsonAs(json, HttpStorageProperties.class);
192+
assertNotNull(properties);
193+
return properties;
194+
} catch (IOException e) {
195+
fail("Unable to convert json resource", e);
196+
return null;
197+
}
198+
}
199+
200+
private String jsonResourceToStringOrFail(String fileName) throws IOException {
201+
String resource = TEST_RESOURCE_DIR + fileName + ".json";
202+
203+
try (InputStream testResourceStream = this.getClass().getResourceAsStream(resource)) {
204+
if (testResourceStream == null) {
205+
throw new IOException("Could not access " + resource + " resource");
206+
}
207+
return new String(testResourceStream.readAllBytes(), StandardCharsets.UTF_8);
208+
}
209+
}
210+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"id": "http-storage",
3+
"type": "Storage",
4+
"className": "com.here.naksha.storage.http.HttpStorage",
5+
"properties": {
6+
"url": "https://example.org",
7+
"headers": {
8+
"Authorization": "Bearer boxed-token",
9+
"X-Tenant": "demo"
10+
}
11+
}
12+
}

0 commit comments

Comments
 (0)