Skip to content

Commit e50ec47

Browse files
Merge pull request #192 from contentstack/staging
DX | 13-10-2025 | Release
2 parents 2561b21 + f02bbe7 commit e50ec47

File tree

10 files changed

+519
-3
lines changed

10 files changed

+519
-3
lines changed

changelog.md

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

3+
## v1.9.0
4+
5+
### Oct 13, 2025
6+
7+
- Feature : Variant Group support
8+
39
## v1.8.0
410

511
### Sep 15, 2025

pom.xml

Lines changed: 1 addition & 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.8.0</version>
10+
<version>1.9.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>

src/main/java/com/contentstack/cms/stack/Stack.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,29 @@ public GlobalField globalField(@NotNull String globalFiledUid) {
377377
return new GlobalField(this.client,this.headers,globalFiledUid);
378378
}
379379

380+
/**
381+
* Creates a new instance of VariantGroup for managing variant groups.
382+
* This method is used when you want to create a new variant group.
383+
*
384+
* @return A new VariantGroup instanceroup variantGroup() {
385+
return new VariantGroup(this.client,this.headers);
386+
}
387+
*/
388+
public VariantGroup variantGroup() {
389+
return new VariantGroup(this.client,this.headers);
390+
}
391+
392+
/**
393+
* Creates a new instance of VariantGroup for managing a specific variant group.
394+
* This method is used when you want to work with an existing variant group.
395+
*
396+
* @param variantGroupUid The UID of the variant group to manage
397+
* @return A new VariantGroup instance configured for the specified variant group
398+
*/
399+
public VariantGroup variantGroup(@NotNull String variantGroupUid) {
400+
return new VariantGroup(this.client,this.headers,variantGroupUid);
401+
}
402+
380403
/**
381404
* Contentstack has a sophisticated multilingual capability. It allows you to
382405
* create and publish entries in any
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
package com.contentstack.cms.stack;
2+
import java.util.Arrays;
3+
import java.util.HashMap;
4+
import java.util.List;
5+
import java.util.Map;
6+
7+
import org.jetbrains.annotations.NotNull;
8+
import org.json.simple.JSONArray;
9+
import org.json.simple.JSONObject;
10+
11+
import com.contentstack.cms.BaseImplementation;
12+
13+
import okhttp3.ResponseBody;
14+
import retrofit2.Call;
15+
import retrofit2.Retrofit;
16+
17+
/**
18+
* The VariantGroup class provides functionality to manage variant groups in Contentstack.
19+
* Variant groups allow you to manage different versions of your content for various use cases,
20+
* such as A/B testing, localization, or personalization.
21+
*/
22+
public class VariantGroup implements BaseImplementation<VariantGroup> {
23+
protected final VariantsService service;
24+
protected final Map<String, Object> headers;
25+
protected Map<String, Object> params;
26+
private final Retrofit instance;
27+
private String variantGroupUid;
28+
private List<String> branches;
29+
30+
/**
31+
* Creates a new VariantGroup instance without a specific variant group UID.
32+
* This constructor is used when creating new variant groups.
33+
*
34+
* @param instance The Retrofit instance for making API calls
35+
* @param headers The headers to be included in API requests
36+
*/
37+
protected VariantGroup(Retrofit instance, Map<String, Object> headers) {
38+
this.headers = new HashMap<>();
39+
this.headers.putAll(headers);
40+
this.params = new HashMap<>();
41+
this.instance = instance;
42+
this.service = instance.create(VariantsService.class);
43+
this.branches = Arrays.asList("main"); // Default to main branch
44+
}
45+
46+
/**
47+
* Creates a new VariantGroup instance with a specific variant group UID.
48+
* This constructor is used when working with existing variant groups.
49+
*
50+
* @param instance The Retrofit instance for making API calls
51+
* @param headers The headers to be included in API requests
52+
* @param variantGroupUid The unique identifier of the variant group
53+
*/
54+
protected VariantGroup(Retrofit instance, Map<String, Object> headers, String variantGroupUid) {
55+
this.headers = new HashMap<>();
56+
this.headers.putAll(headers);
57+
this.params = new HashMap<>();
58+
this.instance = instance;
59+
this.variantGroupUid = variantGroupUid;
60+
this.service = instance.create(VariantsService.class);
61+
this.branches = Arrays.asList("main"); // Default to main branch
62+
}
63+
64+
/**
65+
* Validates that the variant group UID is not null or empty.
66+
* This method is called before operations that require a valid variant group UID.
67+
*
68+
* @throws IllegalAccessError if the variant group UID is null or empty
69+
*/
70+
void validate() {
71+
if (this.variantGroupUid == null || this.variantGroupUid.isEmpty())
72+
throw new IllegalAccessError("Variant group uid can not be null or empty");
73+
}
74+
75+
/**
76+
* Sets the branches for the variant group using a List of branch names.
77+
* These branches will be used when linking or unlinking content types to the variant group.
78+
*
79+
* @param branches A List of String values representing the branch names
80+
* @return The current VariantGroup instance for method chaining
81+
*/
82+
public VariantGroup setBranches(List<String> branches) {
83+
this.branches = branches;
84+
return this;
85+
}
86+
87+
/**
88+
* Sets the branches for the variant group using varargs (variable number of arguments).
89+
* This is a convenience method that allows passing branch names directly as arguments.
90+
* These branches will be used when linking or unlinking content types to the variant group.
91+
*
92+
* @param branches Variable number of String arguments representing branch names
93+
* @return The current VariantGroup instance for method chaining
94+
*/
95+
public VariantGroup setBranches(String... branches) {
96+
this.branches = Arrays.asList(branches);
97+
return this;
98+
}
99+
100+
/**
101+
* @param key A string representing the key of the parameter. It cannot be
102+
* null and must be
103+
* provided as a non-null value.
104+
* @param value The "value" parameter is of type Object, which means it can
105+
* accept any type of
106+
* object as its value.
107+
* @return instance of VariantGroup
108+
*/
109+
@Override
110+
public VariantGroup addParam(@NotNull String key, @NotNull Object value) {
111+
this.params.put(key, value);
112+
return this;
113+
}
114+
115+
/**
116+
* @param key The key parameter is a string that represents the name or
117+
* identifier of the header.
118+
* It is used to specify the type of information being sent in the
119+
* header.
120+
* @param value The value parameter is a string that represents the value of the
121+
* header.
122+
* @return instance of VariantGroup
123+
*/
124+
@Override
125+
public VariantGroup addHeader(@NotNull String key, @NotNull String value) {
126+
this.headers.put(key, value);
127+
return this;
128+
}
129+
130+
/**
131+
* @param headers A HashMap containing key-value pairs of headers, where the key
132+
* is a String
133+
* representing the header name and the value is a String
134+
* representing the header value.
135+
* @return instance of VariantGroup
136+
*/
137+
@Override
138+
public VariantGroup addHeaders(@NotNull HashMap<String, String> headers) {
139+
this.headers.putAll(headers);
140+
return this;
141+
}
142+
143+
144+
/**
145+
* @param headers The "params" parameter is a HashMap that maps String keys to
146+
* Object values. It is
147+
* annotated with @NotNull, indicating that it cannot be null.
148+
* @return instance of VariantGroup
149+
*/
150+
@Override
151+
public VariantGroup addParams(@NotNull HashMap<String, Object> headers) {
152+
this.params.putAll(headers);
153+
return this;
154+
}
155+
156+
157+
/**
158+
* clears all params in the request
159+
*/
160+
protected void clearParams() {
161+
this.params.clear();
162+
}
163+
164+
/**
165+
* Retrieves a list of all variant groups.
166+
* This method does not require a variant group UID to be set.
167+
*
168+
* @return A Call object that can be executed to perform the API request to fetch all variant groups
169+
*/
170+
public Call<ResponseBody> find() {
171+
return this.service.fetchVariantGroups(this.headers, this.params);
172+
}
173+
174+
/**
175+
* Links content types to the variant group.
176+
*
177+
* @param contentTypeUids Array of content type UIDs to link to the variant group
178+
* @return A Call object that can be executed to perform the API request
179+
* @throws IllegalAccessError if the variant group UID is not set
180+
* @throws IllegalArgumentException if contentTypeUids is empty
181+
*/
182+
public Call<ResponseBody> linkContentTypes(@NotNull String... contentTypeUids) {
183+
if (contentTypeUids.length == 0) {
184+
throw new IllegalArgumentException("Content type UIDs cannot be empty");
185+
}
186+
return updateContentTypeLinks(contentTypeUids, true);
187+
}
188+
189+
/**
190+
* Unlinks content types from the variant group.
191+
*
192+
* @param contentTypeUids Array of content type UIDs to unlink from the variant group
193+
* @return A Call object that can be executed to perform the API request
194+
* @throws IllegalAccessError if the variant group UID is not set
195+
* @throws IllegalArgumentException if contentTypeUids is empty
196+
*/
197+
public Call<ResponseBody> unlinkContentTypes(@NotNull String... contentTypeUids) {
198+
if (contentTypeUids.length == 0) {
199+
throw new IllegalArgumentException("Content type UIDs cannot be empty");
200+
}
201+
return updateContentTypeLinks(contentTypeUids, false);
202+
}
203+
204+
/**
205+
* Updates the linking status of content types to a variant group.
206+
* This private method handles both linking and unlinking operations.
207+
*
208+
* @param contentTypeUids Array of content type UIDs to update
209+
* @param isLink true to link content types, false to unlink
210+
* @return A Call object that can be executed to perform the API request
211+
* @throws IllegalAccessError if the variant group UID is not set
212+
*/
213+
@SuppressWarnings("unchecked")
214+
private Call<ResponseBody> updateContentTypeLinks(String[] contentTypeUids, boolean isLink) {
215+
validate();
216+
217+
// Construct the request body
218+
JSONObject requestBody = new JSONObject();
219+
JSONArray contentTypes = new JSONArray();
220+
JSONArray branches = new JSONArray();
221+
for (String branch : this.branches) {
222+
branches.add(branch);
223+
}
224+
225+
for (String uid : contentTypeUids) {
226+
JSONObject contentType = new JSONObject();
227+
contentType.put("uid", uid);
228+
contentType.put("status", isLink ? "linked" : "unlinked");
229+
contentTypes.add(contentType);
230+
}
231+
requestBody.put("uid", this.variantGroupUid);
232+
requestBody.put("branches", branches);
233+
requestBody.put("content_types", contentTypes);
234+
return this.service.updateVariantGroupContentTypes(this.headers, this.variantGroupUid, this.params, requestBody);
235+
}
236+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.contentstack.cms.stack;
2+
3+
import java.util.Map;
4+
5+
import org.json.simple.JSONObject;
6+
7+
import okhttp3.ResponseBody;
8+
import retrofit2.Call;
9+
import retrofit2.http.Body;
10+
import retrofit2.http.GET;
11+
import retrofit2.http.HeaderMap;
12+
import retrofit2.http.PUT;
13+
import retrofit2.http.Path;
14+
import retrofit2.http.QueryMap;
15+
16+
/**
17+
* Service interface for variant group related API endpoints.
18+
*/
19+
public interface VariantsService {
20+
21+
@GET("variant_groups")
22+
Call<ResponseBody> fetchVariantGroups(
23+
@HeaderMap Map<String, Object> headers,
24+
@QueryMap Map<String, Object> queryParam);
25+
26+
@PUT("variant_groups/{variant_group_uid}/variants")
27+
Call<ResponseBody> updateVariantGroupContentTypes(
28+
@HeaderMap Map<String, Object> headers,
29+
@Path("variant_group_uid") String variantGroupUid,
30+
@QueryMap Map<String, Object> queryParam,
31+
@Body JSONObject body);
32+
}

src/test/java/com/contentstack/cms/TestClient.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@ public class TestClient {
1717
public final static String USER_ID = (env.get("userId") != null) ? env.get("userId") : "c11e668e0295477f";
1818
public final static String OWNERSHIP = (env.get("ownershipToken") != null) ? env.get("ownershipToken")
1919
: "ownershipTokenId";
20+
// file deepcode ignore NonCryptoHardcodedSecret/test: <please specify a reason of ignoring this>
2021
public final static String API_KEY = (env.get("apiKey") != null) ? env.get("apiKey") : "apiKey99999999";
2122
public final static String MANAGEMENT_TOKEN = (env.get("managementToken") != null) ? env.get("managementToken")
2223
: "managementToken99999999";
2324

2425
public final static String DEV_HOST = "api.contentstack.io";
2526
// (env.get("dev_host") != null) ? env.get("dev_host") : "api.contentstack.io";
27+
public final static String VARIANT_GROUP_UID = (env.get("variantGroupUid") != null) ? env.get("variantGroupUid")
28+
: "variantGroupUid99999999";
2629
private static Contentstack instance;
2730
private static Stack stackInstance;
2831

src/test/java/com/contentstack/cms/models/LoginDetailTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ void getterSetterUserModelLastName() {
102102
@Test
103103
void getterSetterUserModelUsername() {
104104
UserModel userModel = new UserModel();
105+
// deepcode ignore NoHardcodedCredentials/test: <please specify a reason of ignoring this>
105106
userModel.setUsername("***REMOVED***");
106107
Assertions.assertEquals("***REMOVED***",
107108
userModel.getUsername());

src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
StackAPITest.class,
2020
TokenAPITest.class,
2121
OrgApiTests.class,
22-
GlobalFieldAPITest.class
22+
GlobalFieldAPITest.class,
23+
VariantGroupAPITest.class,
24+
VariantGroupTest.class
2325

24-
})
26+
})
2527
public class APISanityTestSuite {
2628

2729
}

0 commit comments

Comments
 (0)