Skip to content

Commit e1c6b73

Browse files
committed
Merge branch 'development' into feat/DX-7325-variant-branch-support
2 parents f29b8c8 + 7038404 commit e1c6b73

14 files changed

Lines changed: 1388 additions & 91 deletions

File tree

.github/workflows/issues-jira.yml

Lines changed: 0 additions & 31 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# CHANGELOG
22

3+
## v2.7.2
4+
5+
### Jul 06, 2026
6+
- Snyk fixes
7+
8+
## v2.7.1
9+
10+
### Jun 29, 2026
11+
- Snyk fixes
12+
13+
## v2.7.0
14+
15+
### Jun 15, 2026
16+
- Enhancement: Endpoint integration
17+
318
## v2.6.0
419

520
### Feb 23, 2026

pom.xml

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<modelVersion>4.0.0</modelVersion>
66
<groupId>com.contentstack.sdk</groupId>
77
<artifactId>java</artifactId>
8-
<version>2.6.0</version>
8+
<version>2.7.2</version>
99
<packaging>jar</packaging>
1010
<name>contentstack-java</name>
1111
<description>Java SDK for Contentstack Content Delivery API</description>
@@ -24,7 +24,7 @@
2424
<retrofit-source.version>3.0.0</retrofit-source.version>
2525
<loggin.version>5.3.2</loggin.version>
2626
<jococo-plugin.version>0.8.5</jococo-plugin.version>
27-
<lombok-source.version>1.18.42</lombok-source.version>
27+
<lombok-source.version>1.18.44</lombok-source.version>
2828
<junit-jupiter.version>5.11.4</junit-jupiter.version>
2929
<junit-jupiter-engine.version>5.8.0-M1</junit-jupiter-engine.version>
3030
<gson.version>2.8.8</gson.version>
@@ -33,10 +33,10 @@
3333
<maven-gpg-plugin.version>1.5</maven-gpg-plugin.version>
3434
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
3535
<nexus-staging-maven-plugin.version>1.6.13</nexus-staging-maven-plugin.version>
36-
<json-version>20251224</json-version>
36+
<json-version>20260522</json-version>
3737
<jacoco-maven-plugin-version>0.8.11</jacoco-maven-plugin-version>
3838
<maven-release-plugin-version>2.5.3</maven-release-plugin-version>
39-
<contentstack-utils-version>1.3.0</contentstack-utils-version>
39+
<contentstack-utils-version>1.5.1</contentstack-utils-version>
4040
</properties>
4141

4242
<!-- Parent POM removed to avoid 403 from Maven Central in CI (Snyk, etc.). -->
@@ -178,11 +178,6 @@
178178
</exclusions>
179179
</dependency>
180180

181-
<dependency>
182-
<groupId>com.fasterxml.jackson.core</groupId>
183-
<artifactId>jackson-databind</artifactId>
184-
<version>2.21.2</version>
185-
</dependency>
186181
<dependency>
187182
<groupId>com.slack.api</groupId>
188183
<artifactId>bolt</artifactId>
@@ -229,17 +224,17 @@
229224
<dependency>
230225
<groupId>org.springframework</groupId>
231226
<artifactId>spring-core</artifactId>
232-
<version>6.2.18</version>
227+
<version>6.2.19</version>
233228
</dependency>
234229
<dependency>
235230
<groupId>org.springframework</groupId>
236231
<artifactId>spring-beans</artifactId>
237-
<version>6.2.18</version>
232+
<version>6.2.19</version>
238233
</dependency>
239234
<dependency>
240235
<groupId>org.springframework</groupId>
241236
<artifactId>spring-web</artifactId>
242-
<version>6.2.18</version>
237+
<version>6.2.19</version>
243238
</dependency>
244239
<!-- Fix CVE-2020-15250: junit pulled by json-simple -->
245240
<dependency>
@@ -462,6 +457,34 @@
462457
<artifactId>maven-jxr-plugin</artifactId>
463458
<version>2.3</version>
464459
</plugin>
460+
461+
<!--
462+
Refresh the bundled regions.json from the Contentstack artifact registry.
463+
Run whenever Contentstack adds new regions or service keys, then commit
464+
the updated src/main/resources/assets/regions.json.
465+
466+
Usage:
467+
mvn exec:exec@refresh-regions
468+
-->
469+
<plugin>
470+
<groupId>org.codehaus.mojo</groupId>
471+
<artifactId>exec-maven-plugin</artifactId>
472+
<version>3.1.0</version>
473+
<executions>
474+
<execution>
475+
<id>refresh-regions</id>
476+
<goals>
477+
<goal>exec</goal>
478+
</goals>
479+
<configuration>
480+
<executable>bash</executable>
481+
<arguments>
482+
<argument>${project.basedir}/scripts/download-regions.sh</argument>
483+
</arguments>
484+
</configuration>
485+
</execution>
486+
</executions>
487+
</plugin>
465488
</plugins>
466489
</build>
467490

scripts/download-regions.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env bash
2+
# Download the latest regions.json from the Contentstack artifacts registry and
3+
# write it to src/main/resources/assets/regions.json so it gets bundled into
4+
# the SDK jar on the next build.
5+
#
6+
# Usage:
7+
# ./scripts/download-regions.sh
8+
# mvn exec:exec@refresh-regions
9+
#
10+
# Run this whenever Contentstack announces new regions or service keys, then
11+
# commit the updated file:
12+
# git add src/main/resources/assets/regions.json
13+
# git commit -m "chore: refresh regions.json"
14+
15+
set -euo pipefail
16+
17+
REGIONS_URL="https://artifacts.contentstack.com/regions.json"
18+
DEST="$(dirname "$0")/../src/main/resources/assets/regions.json"
19+
DEST="$(cd "$(dirname "$DEST")" && pwd)/$(basename "$DEST")"
20+
21+
echo "Downloading regions.json from ${REGIONS_URL} ..."
22+
23+
if command -v curl &>/dev/null; then
24+
curl --silent --show-error --fail --location \
25+
--retry 3 --retry-delay 2 \
26+
-o "${DEST}" "${REGIONS_URL}"
27+
elif command -v wget &>/dev/null; then
28+
wget --quiet --tries=3 --waitretry=2 -O "${DEST}" "${REGIONS_URL}"
29+
else
30+
echo "Error: neither curl nor wget found. Install one and retry." >&2
31+
exit 1
32+
fi
33+
34+
# Validate the downloaded file contains a "regions" array
35+
if ! python3 -c "import sys, json; d=json.load(open('${DEST}')); assert 'regions' in d and len(d['regions']) > 0" 2>/dev/null &&
36+
! python -c "import sys, json; d=json.load(open('${DEST}')); assert 'regions' in d and len(d['regions']) > 0" 2>/dev/null; then
37+
# Fallback validation without Python — just check the key exists
38+
if ! grep -q '"regions"' "${DEST}"; then
39+
echo "Error: downloaded file does not look like a valid regions.json" >&2
40+
rm -f "${DEST}"
41+
exit 1
42+
fi
43+
fi
44+
45+
REGION_COUNT=$(grep -o '"id"' "${DEST}" | wc -l | tr -d ' ')
46+
echo "contentstack-java: regions.json updated (${REGION_COUNT} regions) → ${DEST}"

src/main/java/com/contentstack/sdk/CSHttpConnection.java

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package com.contentstack.sdk;
22

3-
import com.fasterxml.jackson.databind.ObjectMapper;
4-
import com.fasterxml.jackson.databind.json.JsonMapper;
5-
import com.fasterxml.jackson.databind.type.MapType;
63
import java.io.IOException;
74
import java.io.UnsupportedEncodingException;
85
import java.net.SocketTimeoutException;
96
import java.net.URLEncoder;
7+
import java.math.BigDecimal;
108
import java.nio.charset.StandardCharsets;
9+
import java.util.ArrayList;
1110
import java.util.HashMap;
1211
import java.util.Iterator;
1312
import java.util.LinkedHashMap;
@@ -201,6 +200,41 @@ private JSONObject createOrderedJSONObject(Map<String, Object> map) {
201200
return json;
202201
}
203202

203+
/**
204+
* Recursively converts a parsed {@link JSONObject} into plain Java collections that
205+
* mirror what the response models expect: JSON objects become {@link LinkedHashMap}
206+
* (preserving key order) and JSON arrays become {@link ArrayList}.
207+
*/
208+
private static Map<String, Object> jsonToOrderedMap(JSONObject object) {
209+
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
210+
for (String key : object.keySet()) {
211+
map.put(key, convertJsonValue(object.get(key)));
212+
}
213+
return map;
214+
}
215+
216+
private static Object convertJsonValue(Object value) {
217+
if (value == null || value == JSONObject.NULL) {
218+
return null;
219+
}
220+
if (value instanceof JSONObject) {
221+
return jsonToOrderedMap((JSONObject) value);
222+
}
223+
if (value instanceof JSONArray) {
224+
JSONArray array = (JSONArray) value;
225+
ArrayList<Object> list = new ArrayList<>(array.length());
226+
for (int i = 0; i < array.length(); i++) {
227+
list.add(convertJsonValue(array.get(i)));
228+
}
229+
return list;
230+
}
231+
// Normalize floating-point numbers to Double to match the previous parser's output.
232+
if (value instanceof BigDecimal) {
233+
return ((BigDecimal) value).doubleValue();
234+
}
235+
return value;
236+
}
237+
204238
private void getService(String requestUrl) throws IOException {
205239

206240
this.headers.put(X_USER_AGENT_KEY, "contentstack-delivery-java/" + SDK_VERSION);
@@ -226,12 +260,11 @@ private void getService(String requestUrl) throws IOException {
226260
response = pluginResponseImp(request, response);
227261
}
228262
try {
229-
// Use Jackson to parse the JSON while preserving order
230-
ObjectMapper mapper = JsonMapper.builder().build();
231-
MapType type = mapper.getTypeFactory().constructMapType(LinkedHashMap.class, String.class,
232-
Object.class);
233-
Map<String, Object> responseMap = mapper.readValue(response.body().string(), type);
234-
263+
// Parse the JSON into ordered maps/lists using org.json. Nested objects
264+
// become LinkedHashMap and arrays become ArrayList, matching the shape
265+
// the response models expect.
266+
Map<String, Object> responseMap = jsonToOrderedMap(new JSONObject(response.body().string()));
267+
235268
// Use the custom method to create an ordered JSONObject
236269
responseJSON = createOrderedJSONObject(responseMap);
237270
if (this.config.livePreviewEntry != null && !this.config.livePreviewEntry.isEmpty()) {

src/main/java/com/contentstack/sdk/Config.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public class Config {
1818
protected String livePreviewContentType = null;
1919
protected String livePreviewEntryUid = null;
2020
protected String host = "cdn.contentstack.io";
21+
protected boolean hostOverridden = false;
2122
protected String version = "v3";
2223
protected String scheme = "https://";
2324
protected String endpoint;
@@ -167,6 +168,7 @@ public String getHost() {
167168
public void setHost(String hostName) {
168169
if (hostName != null && !hostName.isEmpty()) {
169170
host = hostName;
171+
hostOverridden = true;
170172
}
171173
}
172174

src/main/java/com/contentstack/sdk/Contentstack.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.contentstack.sdk;
22

3+
import java.util.Map;
34
import java.util.Objects;
45

56
/**
@@ -98,6 +99,60 @@ private static void validateCredentials(String stackApiKey, String deliveryToken
9899
}
99100
}
100101

102+
/**
103+
* Returns the Contentstack API URL for the given region and service.
104+
*
105+
* <p>Delegates to {@link Endpoint#getContentstackEndpoint(String, String)} — provided as a
106+
* convenience so callers can reach endpoint resolution through the same top-level class they
107+
* use to create stacks.
108+
*
109+
* @param region region ID or alias (e.g. {@code "na"}, {@code "eu"}, {@code "azure-na"})
110+
* @param service service key (e.g. {@code "contentDelivery"}, {@code "contentManagement"})
111+
* @return full URL including {@code https://} scheme
112+
* @throws IllegalArgumentException if the region or service is not recognised
113+
*/
114+
public static String getContentstackEndpoint(String region, String service) {
115+
return Endpoint.getContentstackEndpoint(region, service);
116+
}
117+
118+
/**
119+
* Returns the Contentstack API URL for the given region and service, optionally stripping
120+
* the {@code https://} scheme.
121+
*
122+
* @param region region ID or alias
123+
* @param service service key
124+
* @param omitHttps when {@code true}, returns the bare host without {@code https://}
125+
* @return URL or bare host
126+
* @throws IllegalArgumentException if the region or service is not recognised
127+
*/
128+
public static String getContentstackEndpoint(String region, String service, boolean omitHttps) {
129+
return Endpoint.getContentstackEndpoint(region, service, omitHttps);
130+
}
131+
132+
/**
133+
* Returns all service endpoints for the given region as an ordered map of service key to URL.
134+
*
135+
* @param region region ID or alias
136+
* @return map of service key → full URL
137+
* @throws IllegalArgumentException if the region is not recognised
138+
*/
139+
public static Map<String, String> getContentstackEndpoints(String region) {
140+
return Endpoint.getAllEndpoints(region);
141+
}
142+
143+
/**
144+
* Returns all service endpoints for the given region, optionally stripping the
145+
* {@code https://} scheme from every URL.
146+
*
147+
* @param region region ID or alias
148+
* @param omitHttps when {@code true}, returns bare hosts without {@code https://}
149+
* @return map of service key → URL or bare host
150+
* @throws IllegalArgumentException if the region is not recognised
151+
*/
152+
public static Map<String, String> getContentstackEndpoints(String region, boolean omitHttps) {
153+
return Endpoint.getAllEndpoints(region, omitHttps);
154+
}
155+
101156
private static Stack initializeStack(String stackApiKey, String deliveryToken, String environment, Config config) {
102157
Stack stack = new Stack(stackApiKey.trim());
103158
stack.setHeader("api_key", stackApiKey);

0 commit comments

Comments
 (0)