Skip to content

Commit 0679339

Browse files
committed
feat: Add dynamic endpoint resolution and region management
- Introduced dynamic endpoint resolution via `Endpoint.getContentstackEndpoint()` - Added convenience method `Contentstack.Builder.setRegion(String)` for region targeting - Implemented static proxy methods for ad-hoc URL resolution in `Contentstack` - Added `Contentstack.refreshRegions()` to refresh regions registry from live source - Bundled `regions.json` in JAR and auto-refreshed during build - Added public accessors `Contentstack.getHost()` and `Contentstack.getBaseUrl()` - Created `Endpoint` class to manage endpoint resolution and caching - Implemented tests for endpoint resolution and region management
1 parent d4d05b4 commit 0679339

10 files changed

Lines changed: 1056 additions & 2 deletions

File tree

changelog.md

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

3+
## v1.12.0
4+
5+
### Jun 05, 2026
6+
7+
- Feature: Dynamic endpoint resolution via `Endpoint.getContentstackEndpoint()` backed by the Contentstack Regions Registry (`https://artifacts.contentstack.com/regions.json`). Resolves the correct API URL for any of the 7 supported regions (NA, EU, AU, Azure NA, Azure EU, GCP NA, GCP EU) and 18 service keys without hardcoding host strings.
8+
- Feature: `Contentstack.Builder.setRegion(String)` — convenience method to target a region directly (e.g. `.setRegion("eu")`), automatically resolving the correct Content Management API host.
9+
- Feature: `Contentstack.getContentstackEndpoint(region, service)` and `Contentstack.getContentstackEndpoints(region)` static proxy methods for ad-hoc URL resolution.
10+
- Feature: `Contentstack.refreshRegions()` — forces a live download of the regions registry and refreshes the in-memory cache, so newly published regions or service URLs are available without upgrading the SDK.
11+
- Feature: `regions.json` bundled in the JAR and auto-refreshed at build time via `scripts/download-regions.sh` (invoked on the Maven `generate-resources` phase).
12+
- Feature: `Contentstack.getHost()` and `Contentstack.getBaseUrl()` public accessors on the client instance.
13+
314
## v1.11.2
415

516
### Jun 01, 2026

pom.xml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<artifactId>cms</artifactId>
88
<packaging>jar</packaging>
99
<name>contentstack-management-java</name>
10-
<version>1.11.2</version>
10+
<version>1.12.0</version>
1111
<description>Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an
1212
API-first approach
1313
</description>
@@ -278,6 +278,26 @@
278278
</execution>
279279
</executions>
280280
</plugin>
281+
<plugin>
282+
<groupId>org.codehaus.mojo</groupId>
283+
<artifactId>exec-maven-plugin</artifactId>
284+
<version>3.1.0</version>
285+
<executions>
286+
<execution>
287+
<id>download-regions</id>
288+
<phase>generate-resources</phase>
289+
<goals>
290+
<goal>exec</goal>
291+
</goals>
292+
<configuration>
293+
<executable>bash</executable>
294+
<arguments>
295+
<argument>${project.basedir}/scripts/download-regions.sh</argument>
296+
</arguments>
297+
</configuration>
298+
</execution>
299+
</executions>
300+
</plugin>
281301
<plugin>
282302
<groupId>org.apache.maven.plugins</groupId>
283303
<artifactId>maven-source-plugin</artifactId>

scripts/download-regions.sh

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Downloads the Contentstack regions registry from the official source and
4+
# saves it to src/main/resources/regions.json.
5+
#
6+
# Invoked automatically by Maven on the generate-resources phase, and
7+
# manually via: bash scripts/download-regions.sh
8+
#
9+
# Requires: curl (preferred) or wget as fallback
10+
11+
set -euo pipefail
12+
13+
URL="https://artifacts.contentstack.com/regions.json"
14+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15+
DEST="${SCRIPT_DIR}/../src/main/resources/regions.json"
16+
DIR="$(dirname "$DEST")"
17+
18+
mkdir -p "$DIR"
19+
20+
data=""
21+
22+
# --- Attempt 1: curl (preferred) --------------------------------------------
23+
if command -v curl &>/dev/null; then
24+
data=$(curl --silent --fail --location --max-time 30 "$URL") || data=""
25+
fi
26+
27+
# --- Attempt 2: wget fallback -----------------------------------------------
28+
if [[ -z "$data" ]] && command -v wget &>/dev/null; then
29+
data=$(wget --quiet --timeout=30 -O - "$URL") || data=""
30+
fi
31+
32+
# --- Validate and write ------------------------------------------------------
33+
if [[ -z "$data" ]]; then
34+
echo "contentstack/cms: Warning — could not download regions.json." >&2
35+
echo " The SDK will attempt to download it at runtime on first use." >&2
36+
exit 0 # non-fatal: runtime fallback in Endpoint.java handles it
37+
fi
38+
39+
# Basic validation: must contain a "regions" key
40+
if ! echo "$data" | grep -q '"regions"'; then
41+
echo "contentstack/cms: Warning — downloaded data is not valid regions.json." >&2
42+
exit 0
43+
fi
44+
45+
echo "$data" > "$DEST"
46+
47+
region_count=$(echo "$data" | grep -o '"id"' | wc -l | tr -d ' ')
48+
echo "contentstack/cms: regions.json downloaded (${region_count} regions)."

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

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.jetbrains.annotations.NotNull;
1515

1616
import com.contentstack.cms.core.AuthInterceptor;
17+
import com.contentstack.cms.core.Endpoint;
1718
import com.contentstack.cms.core.Util;
1819
import static com.contentstack.cms.core.Util.API_KEY;
1920
import static com.contentstack.cms.core.Util.AUTHORIZATION;
@@ -561,6 +562,81 @@ public CompletableFuture<Void> oauthLogout() {
561562
return oauthLogout(false);
562563
}
563564

565+
/**
566+
* Forces a live download of the Contentstack regions registry and replaces
567+
* the in-memory cache. Useful when new regions or service URLs have been
568+
* published since the SDK JAR was built.
569+
*
570+
* <pre>{@code
571+
* int count = Contentstack.refreshRegions();
572+
* // now getContentstackEndpoint() resolves from the freshly downloaded registry
573+
* }</pre>
574+
*
575+
* @return the number of regions loaded from the live registry
576+
* @throws RuntimeException if the download fails
577+
*/
578+
public static int refreshRegions() {
579+
return Endpoint.refresh();
580+
}
581+
582+
/**
583+
* Resolves a Contentstack service endpoint URL for the given region.
584+
*
585+
* <pre>{@code
586+
* // Full URL
587+
* String url = Contentstack.getContentstackEndpoint("eu", "contentManagement");
588+
* // → "https://eu-api.contentstack.com"
589+
*
590+
* // Host only (for Builder.setHost)
591+
* String host = Contentstack.getContentstackEndpoint("eu", "contentManagement", true);
592+
* // → "eu-api.contentstack.com"
593+
* }</pre>
594+
*
595+
* @param region region ID or alias (e.g. {@code na}, {@code eu}, {@code azure-na})
596+
* @param service service key (e.g. {@code contentManagement}, {@code contentDelivery})
597+
* @param omitHttps when {@code true}, strips {@code https://} from the returned URL
598+
* @return the resolved URL string
599+
* @throws IllegalArgumentException if region or service is unknown
600+
*/
601+
public static String getContentstackEndpoint(String region, String service, boolean omitHttps) {
602+
return Endpoint.getContentstackEndpoint(region, service, omitHttps);
603+
}
604+
605+
/**
606+
* Resolves a Contentstack service endpoint URL for the given region (with scheme).
607+
*
608+
* @param region region ID or alias
609+
* @param service service key
610+
* @return the resolved URL including {@code https://}
611+
* @throws IllegalArgumentException if region or service is unknown
612+
*/
613+
public static String getContentstackEndpoint(String region, String service) {
614+
return Endpoint.getContentstackEndpoint(region, service);
615+
}
616+
617+
/**
618+
* Returns all endpoint URLs for the given region as an ordered map.
619+
*
620+
* @param region region ID or alias
621+
* @return map of service name → URL (includes {@code https://})
622+
* @throws IllegalArgumentException if region is unknown or empty
623+
*/
624+
public static java.util.Map<String, String> getContentstackEndpoints(String region) {
625+
return Endpoint.getContentstackEndpoints(region);
626+
}
627+
628+
/**
629+
* Returns all endpoint URLs for the given region as an ordered map.
630+
*
631+
* @param region region ID or alias
632+
* @param omitHttps when {@code true}, strips {@code https://} from every URL
633+
* @return map of service name → URL
634+
* @throws IllegalArgumentException if region is unknown or empty
635+
*/
636+
public static java.util.Map<String, String> getContentstackEndpoints(String region, boolean omitHttps) {
637+
return Endpoint.getContentstackEndpoints(region, omitHttps);
638+
}
639+
564640
public Contentstack(Builder builder) {
565641
this.host = builder.hostname;
566642
this.port = builder.port;
@@ -577,6 +653,16 @@ public Contentstack(Builder builder) {
577653
this.retryConfig = builder.retryConfig;
578654
}
579655

656+
/** Returns the API hostname this client is configured to target. */
657+
public String getHost() {
658+
return host;
659+
}
660+
661+
/** Returns the full Retrofit base URL (e.g. {@code https://eu-api.contentstack.com/v3/}). */
662+
public String getBaseUrl() {
663+
return instance.baseUrl().toString();
664+
}
665+
580666
public RetryConfig getRetryConfig() {
581667
return retryConfig;
582668
}
@@ -665,6 +751,30 @@ public Builder setHost(@NotNull String hostname) {
665751
return this;
666752
}
667753

754+
/**
755+
* Configures the client to target a specific Contentstack region by resolving
756+
* the correct Content Management API host from the bundled regions registry.
757+
*
758+
* <p>This is a convenience alternative to calling {@link #setHost(String)} with
759+
* a manually constructed hostname.
760+
*
761+
* <pre>{@code
762+
* Contentstack client = new Contentstack.Builder()
763+
* .setAuthtoken("authtoken")
764+
* .setRegion(ContentstackRegion.EU)
765+
* .build();
766+
* }</pre>
767+
*
768+
* @param region region ID or alias (e.g. {@code "na"}, {@code "eu"}, {@code "azure-na"}).
769+
* Use constants from {@link com.contentstack.cms.core.ContentstackRegion}.
770+
* @return this Builder
771+
* @throws IllegalArgumentException if the region is unknown or empty
772+
*/
773+
public Builder setRegion(@NotNull String region) {
774+
this.hostname = Endpoint.getContentstackEndpoint(region, "contentManagement", true);
775+
return this;
776+
}
777+
668778
/**
669779
* Set port for client instance
670780
*

0 commit comments

Comments
 (0)