Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@
</includes>
<reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory>
<!-- Skip during default lifecycle (e.g. publish); run tests locally with: mvn test -DskipTests=false -->
<!-- <skipTests>true</skipTests> -->
<skipTests>true</skipTests>
<testFailureIgnore>true</testFailureIgnore>
Comment thread
reeshika-h marked this conversation as resolved.
</configuration>
</plugin>
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/com/contentstack/cms/Contentstack.java
Original file line number Diff line number Diff line change
Expand Up @@ -439,16 +439,20 @@ public Stack stack(@NotNull String key) {
* property). Within a stack, you can create content structures, content
* entries, users, etc. related to the project
* <p>
* Passing {@code branch} sets the {@value com.contentstack.cms.core.Util#BRANCH} request header for this stack.
* That header applies to entries and entry-variant operations ({@code …/variants/…}) unless overridden per instance,
* e.g. {@link com.contentstack.cms.stack.Entry#addBranch(String)} replaces {@code branch} for that entry only.
* <p>
* <b> Example </b>
*
* <pre>
* Contentstack client = new Contentstack.Builder().build();
* Stack org = client.stack();
* Stack stack = client.stack("API_KEY", "MANAGEMENT_TOKEN", "feature-branch");
* </pre>
*
* @param managementToken the authorization for the stack
* @param apiKey the apiKey for the stack
* @param branch the branch that include branching in the response
* @param branch branch UID or alias for the {@value com.contentstack.cms.core.Util#BRANCH} header
* @return the stack instance
*/
public Stack stack(@NotNull String apiKey, @NotNull String managementToken, @NotNull String branch) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ private ErrorMessages() {
public static final String RELEASE_UID_REQUIRED = "Release UID is required. Provide a valid Release UID and try again.";
public static final String ROLE_UID_REQUIRED = "Role UID is required. Provide a valid Role UID and try again.";
public static final String VARIANT_GROUP_UID_REQUIRED = "Variant Group UID is required. Provide a valid Variant Group UID and try again.";
public static final String VARIANT_UID_REQUIRED = "Variant UID is required. Provide a valid Variant UID and try again.";
public static final String WEBHOOK_UID_REQUIRED = "Webhook UID is required. Provide a valid Webhook UID and try again.";
public static final String WORKFLOW_UID_REQUIRED = "Workflow UID is required. Provide a valid Workflow UID and try again.";

Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/contentstack/cms/core/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ public class Util {
public static final String AUTHTOKEN = "authtoken";
public static final String EARLY_ACCESS_HEADER = "x-header-ea";
public static final String BRANCH = "branch";

/**
* Request header to fetch a base entry with a specific entry variant applied (personalization).
*/
public static final String X_CS_VARIANT_UID = "x-cs-variant-uid";

/**
* Required on publish/unpublish when the request body includes {@code entry.variants} (entry variant flows).
*/
public static final String API_VERSION = "api_version";

/** Value for {@link #API_VERSION} when publishing or unpublishing entry variants per Content Management API. */
public static final String API_VERSION_ENTRY_VARIANTS_PUBLISH = "3.2";

public static final String X_USER_AGENT = "X-User-Agent";
public static final String USER_AGENT = "User-Agent";
public static final String CONTENT_TYPE = "Content-Type";
Expand Down
180 changes: 178 additions & 2 deletions src/main/java/com/contentstack/cms/stack/Entry.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.contentstack.cms.stack;

import com.contentstack.cms.core.ErrorMessages;
import com.contentstack.cms.core.Util;

import com.contentstack.cms.BaseImplementation;
import okhttp3.ResponseBody;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.json.simple.JSONObject;
import retrofit2.Call;
import retrofit2.Retrofit;
Expand Down Expand Up @@ -68,6 +70,52 @@ private void validateCT() {
Objects.requireNonNull(this.contentTypeUid, ERROR_CT_UID);
}

private void validateVariantUid(@NotNull String variantUid) {
Objects.requireNonNull(variantUid, ErrorMessages.VARIANT_UID_REQUIRED);
if (variantUid.isEmpty()) {
throw new IllegalArgumentException(ErrorMessages.VARIANT_UID_REQUIRED);
}
}
Comment thread
reeshika-h marked this conversation as resolved.

/**
* Header map for a variant request when {@code branchUid} is supplied for this call only (does not mutate {@link #headers}).
* Null or blank {@code branchUid} keeps {@link #headers} as-is (stack / {@link #addBranch(String)} behavior).
*/
private Map<String, Object> variantHeadersWithOptionalBranch(@Nullable String branchUid) {
if (branchUid == null || branchUid.isEmpty()) {
return this.headers;
}
HashMap<String, Object> copy = new HashMap<>(this.headers);
copy.put(Util.BRANCH, branchUid);
return copy;
Comment thread
reeshika-h marked this conversation as resolved.
Outdated
}

/**
* Sets the branch header for requests scoped to a stack branch (e.g. development).
* Overrides the branch set via {@link com.contentstack.cms.Contentstack#stack(String, String, String)} for this
* {@link Entry} instance only (including entry-variant CRUD, publish, and unpublish). Uses header key {@value Util#BRANCH}.
*
* @param branchUid branch UID or alias target branch UID
* @return this entry instance for chaining
*/
public Entry addBranch(@NotNull String branchUid) {
this.headers.put(Util.BRANCH, branchUid);
return this;
}
Comment thread
reeshika-h marked this conversation as resolved.

/**
* Sets {@value Util#X_CS_VARIANT_UID} for {@link #fetch()} / {@link #fetchAsPojo()} to retrieve the base entry with a
* specific variant applied (personalization).
*
* @param variantUid Content variant UID (e.g. {@code cs…})
* @return this entry instance for chaining
*/
public Entry withAppliedVariantUid(@NotNull String variantUid) {
validateVariantUid(variantUid);
this.headers.put(Util.X_CS_VARIANT_UID, variantUid);
return this;
}

/**
* Sets header for the request
*
Expand Down Expand Up @@ -710,6 +758,102 @@ public Call<ResponseBody> importExisting() {
return this.service.importExisting(this.headers, this.contentTypeUid, this.entryUid, this.params);
}

/**
* Retrieves all entry variants for this entry.
* <p>
* Use {@link #addParam(String, Object)} for optional queries such as {@code locale}, {@code include_workflow}.
* Branch scope: stack {@value Util#BRANCH} from {@link com.contentstack.cms.Contentstack#stack(String, String, String)}
* is forwarded; {@link #addBranch(String)} overrides for this entry only.
*
* @return Retrofit call for GET …/entries/{entry_uid}/variants
* @see <a href="https://www.contentstack.com/docs/developers/apis/content-management-api/#get-all-entry-variants">Get all entry variants</a>
*/
public Call<ResponseBody> fetchEntryVariants() {
validateCT();
validateEntry();
return this.service.fetchEntryVariants(this.headers, this.contentTypeUid, this.entryUid, this.params);
}

/**
* Retrieves a single entry variant using {@link #headers} for {@value Util#BRANCH} (stack default and/or {@link #addBranch(String)}).
*
* @param variantUid variant UID path segment
* @return Retrofit call for GET …/variants/{variant_uid}
* @see #fetchEntryVariant(String, String)
*/
public Call<ResponseBody> fetchEntryVariant(@NotNull String variantUid) {
return fetchEntryVariant(variantUid, null);
}

/**
* Retrieves a single entry variant with an optional per-call {@value Util#BRANCH} override.
* <p>
* When {@code branchUid} is non-blank, it replaces {@value Util#BRANCH} on this request only (stack and {@link #addBranch(String)}
* values are not mutated on the entry). When {@code branchUid} is {@code null} or blank, behavior matches {@link #fetchEntryVariant(String)}.
* {@link #withAppliedVariantUid(String)} ({@value Util#X_CS_VARIANT_UID}) is unrelated to branch.
*
* @param variantUid variant UID path segment
* @param branchUid optional branch UID or alias for this request only; {@code null} or empty to use entry headers
* @return Retrofit call for GET …/variants/{variant_uid}
*/
public Call<ResponseBody> fetchEntryVariant(@NotNull String variantUid, @Nullable String branchUid) {
validateCT();
validateEntry();
validateVariantUid(variantUid);
return this.service.fetchEntryVariant(variantHeadersWithOptionalBranch(branchUid), this.contentTypeUid,
this.entryUid, variantUid, this.params);
}

/**
* Creates an entry variant. Uses PUT …/variants/{variant_uid} (CMA upsert — same URL as {@link #updateEntryVariant}).
* <p>
* Branch scope: inherits stack {@value Util#BRANCH}; override with {@link #addBranch(String)} or {@link #addHeader(String, String)}
* ({@value Util#BRANCH}) on this entry. Variant personalization header {@value Util#X_CS_VARIANT_UID} is orthogonal.
*
* @param variantUid variant UID path segment
* @param requestBody JSON body per API (typically wraps fields under {@code entry})
* @see <a href="https://www.contentstack.com/docs/developers/apis/content-management-api/#create-entry-variant">Create Entry Variant</a>
*/
public Call<ResponseBody> createEntryVariant(@NotNull String variantUid, @NotNull JSONObject requestBody) {
validateCT();
validateEntry();
validateVariantUid(variantUid);
return this.service.createEntryVariant(this.headers, this.contentTypeUid, this.entryUid, variantUid, this.params,
requestBody);
}

/**
* Updates an entry variant. Same HTTP request shape as create (PUT upsert).
* <p>
* Branch scope: inherits stack {@value Util#BRANCH}; override with {@link #addBranch(String)} or {@link #addHeader(String, String)}
* ({@value Util#BRANCH}) on this entry.
*
* @see <a href="https://www.contentstack.com/docs/developers/apis/content-management-api/#update-entry-variant">Update Entry Variant</a>
*/
public Call<ResponseBody> updateEntryVariant(@NotNull String variantUid, @NotNull JSONObject requestBody) {
validateCT();
validateEntry();
validateVariantUid(variantUid);
return this.service.updateEntryVariant(this.headers, this.contentTypeUid, this.entryUid, variantUid, this.params,
requestBody);
}

/**
* Deletes an entry variant.
* <p>
* Branch scope: inherits stack {@value Util#BRANCH}; override with {@link #addBranch(String)} or {@link #addHeader(String, String)}
* ({@value Util#BRANCH}) on this entry.
*
* @param variantUid variant UID path segment
* @return Retrofit call for DELETE …/variants/{variant_uid}
*/
public Call<ResponseBody> deleteEntryVariant(@NotNull String variantUid) {
validateCT();
validateEntry();
validateVariantUid(variantUid);
return this.service.deleteEntryVariant(this.headers, this.contentTypeUid, this.entryUid, variantUid, this.params);
}

/**
* To Publish an entry request lets you publish an entry either immediately or
* schedule it for a later date/time.
Expand Down Expand Up @@ -752,7 +896,25 @@ public Call<ResponseBody> importExisting() {
public Call<ResponseBody> publish(@NotNull JSONObject requestBody) {
validateCT();
validateEntry();
return this.service.publish(this.headers, this.contentTypeUid, this.entryUid, requestBody);
return this.service.publish(this.headers, this.contentTypeUid, this.entryUid, this.params, requestBody);
}

/**
* Publishes entry variants using the entry publish endpoint with {@code entry.variants} in the body.
* Sends header {@value Util#API_VERSION}={@value Util#API_VERSION_ENTRY_VARIANTS_PUBLISH} unless already set on this entry instance.
* Use {@link #addParam(String, Object)} for optional {@code locale} query parameter.
* <p>
* Branch scope: stack {@value Util#BRANCH} is copied into the publish request headers together with {@code api_version};
* override with {@link #addBranch(String)} or {@link #addHeader(String, String)} ({@value Util#BRANCH}) on this entry.
*
* @param requestBody full publish payload including {@code entry}, {@code locale}, etc.
*/
public Call<ResponseBody> publishEntryVariants(@NotNull JSONObject requestBody) {
validateCT();
validateEntry();
HashMap<String, Object> publishHeaders = new HashMap<>(this.headers);
publishHeaders.putIfAbsent(Util.API_VERSION, Util.API_VERSION_ENTRY_VARIANTS_PUBLISH);
return this.service.publish(publishHeaders, this.contentTypeUid, this.entryUid, this.params, requestBody);
}

/**
Expand Down Expand Up @@ -816,9 +978,23 @@ public Call<ResponseBody> publishWithReference(@NotNull JSONObject requestBody)
public Call<ResponseBody> unpublish(@NotNull JSONObject requestBody) {
validateCT();
validateEntry();
return this.service.unpublish(this.headers, this.contentTypeUid, this.entryUid, requestBody);
return this.service.unpublish(this.headers, this.contentTypeUid, this.entryUid, this.params, requestBody);
}

/**
* Unpublishes entry variants via the entry unpublish endpoint with {@code entry.variants} in the body.
* Sends header {@value Util#API_VERSION}={@value Util#API_VERSION_ENTRY_VARIANTS_PUBLISH} unless already set.
* <p>
* Branch scope: stack {@value Util#BRANCH} is forwarded; override with {@link #addBranch(String)} or {@link #addHeader(String, String)}
* ({@value Util#BRANCH}) on this entry.
*/
public Call<ResponseBody> unpublishEntryVariants(@NotNull JSONObject requestBody) {
validateCT();
validateEntry();
HashMap<String, Object> unpublishHeaders = new HashMap<>(this.headers);
unpublishHeaders.putIfAbsent(Util.API_VERSION, Util.API_VERSION_ENTRY_VARIANTS_PUBLISH);
return this.service.unpublish(unpublishHeaders, this.contentTypeUid, this.entryUid, this.params, requestBody);
}

/**
* Get instance of taxonomy search filter class instance through which we can query on taxonomy based on content type
Expand Down
52 changes: 51 additions & 1 deletion src/main/java/com/contentstack/cms/stack/EntryService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import retrofit2.Call;
import retrofit2.http.*;

import java.util.List;
import java.util.Map;

public interface EntryService {
Expand Down Expand Up @@ -147,6 +146,7 @@ Call<ResponseBody> publish(
@HeaderMap Map<String, Object> headers,
@Path("content_type_uid") String contentTypeUid,
@Path("entry_uid") String entryUid,
@QueryMap(encoded = true) Map<String, Object> queryParameters,
@Body JSONObject requestBody);

@POST("bulk/publish?x-bulk-action=publish")
Expand All @@ -160,8 +160,58 @@ Call<ResponseBody> unpublish(
@HeaderMap Map<String, Object> headers,
@Path("content_type_uid") String contentTypeUid,
@Path("entry_uid") String entryUid,
@QueryMap(encoded = true) Map<String, Object> queryParameters,
@Body JSONObject requestBody);

@GET("content_types/{content_type_uid}/entries/{entry_uid}/variants")
Call<ResponseBody> fetchEntryVariants(
@HeaderMap Map<String, Object> headers,
@Path("content_type_uid") String contentTypeUid,
@Path("entry_uid") String entryUid,
@QueryMap(encoded = true) Map<String, Object> queryParameters);

@GET("content_types/{content_type_uid}/entries/{entry_uid}/variants/{variant_uid}")
Call<ResponseBody> fetchEntryVariant(
@HeaderMap Map<String, Object> headers,
@Path("content_type_uid") String contentTypeUid,
@Path("entry_uid") String entryUid,
@Path("variant_uid") String variantUid,
@QueryMap(encoded = true) Map<String, Object> queryParameters);

/**
* Create entry variant (PUT …/variants/{variant_uid}). Same HTTP contract as update — CMA upserts on this path.
*/
@Headers("Content-Type: application/json")
@PUT("content_types/{content_type_uid}/entries/{entry_uid}/variants/{variant_uid}")
Call<ResponseBody> createEntryVariant(
@HeaderMap Map<String, Object> headers,
@Path("content_type_uid") String contentTypeUid,
@Path("entry_uid") String entryUid,
@Path("variant_uid") String variantUid,
@QueryMap(encoded = true) Map<String, Object> queryParameters,
@Body JSONObject requestBody);

/**
* Update entry variant — delegates to {@link #createEntryVariant}; API uses one PUT upsert for both operations.
*/
default Call<ResponseBody> updateEntryVariant(
Map<String, Object> headers,
String contentTypeUid,
String entryUid,
String variantUid,
Map<String, Object> queryParameters,
JSONObject requestBody) {
return createEntryVariant(headers, contentTypeUid, entryUid, variantUid, queryParameters, requestBody);
}

@DELETE("content_types/{content_type_uid}/entries/{entry_uid}/variants/{variant_uid}")
Call<ResponseBody> deleteEntryVariant(
@HeaderMap Map<String, Object> headers,
@Path("content_type_uid") String contentTypeUid,
@Path("entry_uid") String entryUid,
@Path("variant_uid") String variantUid,
@QueryMap(encoded = true) Map<String, Object> queryParameters);

@GET("content_types/{content_type_uid}/entries")
Call<ResponseBody> filterTaxonomy(
@HeaderMap Map<String, Object> headers,
Expand Down
20 changes: 20 additions & 0 deletions src/test/java/com/contentstack/cms/TestClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@ public class TestClient {
public final static String DEV_HOST = (env.get("dev_host") != null) ? env.get("dev_host").trim() : "api.contentstack.io";
public final static String VARIANT_GROUP_UID = (env.get("variantGroupUid") != null) ? env.get("variantGroupUid")
: "variantGroupUid99999999";

/** Content type UID for entry-variant tests (default {@code blog}). */
public static final String ENTRY_VARIANT_CONTENT_TYPE_UID =
env.get("entryVariantContentTypeUid") != null ? env.get("entryVariantContentTypeUid") : "blog";

Comment thread
reeshika-h marked this conversation as resolved.
/** Stack branch for entry-variant tests (default {@code develop}). */
public static final String ENTRY_VARIANT_BRANCH =
env.get("entryVariantBranch") != null ? env.get("entryVariantBranch") : "develop";

Comment thread
reeshika-h marked this conversation as resolved.
/** Locale query param for entry-variant tests (default {@code en-us}). */
public static final String ENTRY_VARIANT_LOCALE =
env.get("entryVariantLocale") != null ? env.get("entryVariantLocale") : "en-us";

Comment thread
reeshika-h marked this conversation as resolved.
/**
* {@code true} when {@code apiKey} / {@code managementToken} were not loaded from {@code .env} (defaults apply).
*/
public static boolean isUsingDefaultStackCredentials() {
return env.get("apiKey") == null || env.get("managementToken") == null;
}

private static Contentstack instance;
private static Stack stackInstance;

Expand Down
2 changes: 2 additions & 0 deletions src/test/java/com/contentstack/cms/UnitTestSuite.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.contentstack.cms;

import com.contentstack.cms.core.AuthInterceptorTest;
import com.contentstack.cms.stack.EntryVariantUnitTest;
import com.contentstack.cms.stack.EnvironmentUnitTest;
import com.contentstack.cms.stack.GlobalFieldUnitTests;
import com.contentstack.cms.stack.LocaleUnitTest;
Expand All @@ -25,6 +26,7 @@
ContentstackUnitTest.class,

// Stack module tests (only public classes)
EntryVariantUnitTest.class,
EnvironmentUnitTest.class,
GlobalFieldUnitTests.class,
LocaleUnitTest.class,
Expand Down
Loading
Loading