> payload = new HashMap<>();
+ payload.put("spaces", tokens);
+ String json = ResourceFactory.GSON.toJson(payload);
+ return Base64.getEncoder().encodeToString(
+ json.getBytes(java.nio.charset.StandardCharsets.UTF_8));
+ }
+
private OkHttpClient.Builder setLogger(OkHttpClient.Builder okBuilder) {
if (logger != null) {
switch (logLevel) {
@@ -812,8 +836,16 @@ public OkHttpClient.Builder defaultCallFactoryBuilder() {
.connectionPool(OK_HTTP_CLIENT.connectionPool())
.addInterceptor(new AuthorizationHeaderInterceptor(token))
.addInterceptor(new UserAgentHeaderInterceptor(createUserAgent()))
- .addInterceptor(new ContentfulUserAgentHeaderInterceptor(sections))
- .addInterceptor(new ErrorInterceptor(logSensitiveData));
+ .addInterceptor(new ContentfulUserAgentHeaderInterceptor(sections));
+
+ // Add cross-space resolution header if tokens are configured
+ if (crossSpaceTokens != null && !crossSpaceTokens.isEmpty()) {
+ String encodedHeader = encodeCrossSpaceTokens(crossSpaceTokens);
+ okBuilder.addInterceptor(new HeaderInterceptor(
+ "x-contentful-resource-resolution", encodedHeader));
+ }
+
+ okBuilder.addInterceptor(new ErrorInterceptor(logSensitiveData));
setLogger(okBuilder);
useTls12IfWanted(okBuilder);
@@ -868,6 +900,31 @@ public Builder setIntegration(String name, String version) {
return this;
}
+ /**
+ * Sets cross-space tokens for resolving cross-space references.
+ *
+ * This enables automatic resolution of entries and assets from other spaces by providing
+ * access tokens for those spaces. Cross-space resources will be included in the response's
+ * includes section.
+ *
+ * The maximum number of extra spaces supported is 20 (21 total including the main space).
+ *
+ * @param spaceIdToToken a map of space IDs to their corresponding access tokens (CDA tokens).
+ * @return this builder for chaining.
+ * @throws IllegalArgumentException if the map is null or contains more than 20 spaces.
+ */
+ public Builder setCrossSpaceTokens(Map spaceIdToToken) {
+ checkNotNull(spaceIdToToken, "Cross-space tokens map must not be null.");
+ if (spaceIdToToken.size() > CROSS_SPACE_TOKENS_MAX) {
+ throw new IllegalArgumentException(
+ String.format("Maximum %d extra spaces supported "
+ + "for cross-space resolution, but %d provided.",
+ CROSS_SPACE_TOKENS_MAX, spaceIdToToken.size()));
+ }
+ this.crossSpaceTokens = spaceIdToToken;
+ return this;
+ }
+
/**
* Create CDAClient, using the specified configuration options.
*
diff --git a/src/main/java/com/contentful/java/cda/ResourceUtils.java b/src/main/java/com/contentful/java/cda/ResourceUtils.java
index 1df1e6a..a856c75 100644
--- a/src/main/java/com/contentful/java/cda/ResourceUtils.java
+++ b/src/main/java/com/contentful/java/cda/ResourceUtils.java
@@ -65,7 +65,12 @@ static SynchronizedSpace nextSpace(SynchronizedSpace space, CDAClient client) {
static void resolveLinks(ArrayResource array, CDAClient client) {
for (CDAEntry entry : array.entries().values()) {
ensureContentType(entry, client);
- for (CDAField field : entry.contentType().fields()) {
+ CDAContentType contentType = entry.contentType();
+ if (contentType == null || contentType.fields() == null) {
+ // Content type may be null for cross-space entries
+ continue;
+ }
+ for (CDAField field : contentType.fields()) {
if (field.linkType() != null) {
resolveSingleLink(entry, field, array);
} else if ("Array".equals(field.type) && "Link".equals(field.items().get("type"))) {
@@ -88,13 +93,19 @@ public static void ensureContentType(CDAEntry entry, CDAClient client) {
}
String contentTypeId = extractNested(entry.attrs(), "contentType", "sys", "id");
+ if (contentTypeId == null) {
+ return;
+ }
+
try {
contentType = client.cacheTypeWithId(contentTypeId).blockingFirst();
+ entry.setContentType(contentType);
} catch (CDAResourceNotFoundException e) {
+ if (client.hasCrossSpaceTokens) {
+ return;
+ }
throw new CDAContentTypeNotFoundException(entry.id(), CDAEntry.class, contentTypeId, e);
}
-
- entry.setContentType(contentType);
}
@SuppressWarnings("unchecked")
diff --git a/src/main/java/com/contentful/java/cda/rich/RichTextFactory.java b/src/main/java/com/contentful/java/cda/rich/RichTextFactory.java
index 71aa9c7..a3a2afb 100644
--- a/src/main/java/com/contentful/java/cda/rich/RichTextFactory.java
+++ b/src/main/java/com/contentful/java/cda/rich/RichTextFactory.java
@@ -2,6 +2,7 @@
import com.contentful.java.cda.ArrayResource;
import com.contentful.java.cda.CDAClient;
+import com.contentful.java.cda.CDAContentType;
import com.contentful.java.cda.CDAEntry;
import com.contentful.java.cda.CDAField;
@@ -78,7 +79,12 @@ public class RichTextFactory {
public static void resolveRichTextField(ArrayResource array, CDAClient client) {
for (CDAEntry entry : array.entries().values()) {
ensureContentType(entry, client);
- for (CDAField field : entry.contentType().fields()) {
+ CDAContentType contentType = entry.contentType();
+ if (contentType == null || contentType.fields() == null) {
+ // Content type may be null for cross-space entries
+ continue;
+ }
+ for (CDAField field : contentType.fields()) {
if ("RichText".equals(field.type())) {
resolveRichDocument(entry, field);
resolveRichLink(array, entry, field);