Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@Value.Immutable
@JsonSerialize(as = ContentView.class)
@JsonDeserialize(as = ContentView.class)
@Schema(description = "Contentlet with Styles info")
@Schema(description = "Content within a Page and Styles info")
public interface AbstractContentView {

@JsonProperty("containerId")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.dotcms.rest.api.v1.page;

import com.dotmarketing.util.UtilMethods;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Holds the data received from the UI or the REST Endpoint related to the Contentlets that are
* referenced in a Container.
*/
public class ContainerEntry {

private final String personaTag;
private final String id;
private final String uuid;
private final List<String> contentIds;
private final Map<String, Map<String, Object>> stylePropertiesMap;

public ContainerEntry(final String personaTag, final String id, final String uuid) {
this.id = id;
this.uuid = uuid;
this.personaTag = personaTag;
this.contentIds = new ArrayList<>();
this.stylePropertiesMap = new HashMap<>();
}

public ContainerEntry(final String personaTag, final String id, final String uuid,
final List<String> contentIds,
final Map<String, Map<String, Object>> stylePropertiesMap) {
this.id = id;
this.uuid = uuid;
this.personaTag = personaTag;

// Defensive copy of contentIds
this.contentIds =
UtilMethods.isSet(contentIds) ? new ArrayList<>(contentIds) : new ArrayList<>();

// Defensive deep copy of style properties
this.stylePropertiesMap = new HashMap<>();
if (UtilMethods.isSet(stylePropertiesMap)) {
stylePropertiesMap.forEach((key, value) -> {
this.stylePropertiesMap.put(key, new HashMap<>(value));
});
}
}

public String getPersonaTag() {
return personaTag;
}

public String getContainerId() {
return id;
}

public List<String> getContentIds() {
return Collections.unmodifiableList(contentIds);
}

public void addContentId(final String contentId) {
this.contentIds.add(contentId);
}

public String getContainerUUID() {
return uuid;
}

public Map<String, Map<String, Object>> getStylePropertiesMap() {
return Collections.unmodifiableMap(stylePropertiesMap);
}

public void setStyleProperties(final String contentletId,
final Map<String, Object> styleProperties) {
if (styleProperties == null) {
this.stylePropertiesMap.remove(contentletId);
return;
}
this.stylePropertiesMap.put(contentletId, new HashMap<>(styleProperties));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package com.dotcms.rest.api.v1.page;

import com.dotcms.rest.api.Validated;
import com.dotmarketing.util.UtilMethods;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema.RequiredMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Form for updating contentlet styles within a container on a page. Accepts JSON where contentlet
* IDs are dynamic keys with their style properties as values.
* <p>
* Example JSON:
* <pre>
* {
* "containerId": "SYSTEM_CONTAINER",
* "uuid": "1",
* "contentlet-id-1": { "width": "100px", "color": "#FF0000" },
* "contentlet-id-2": { "margin": "10px" }
* }
* </pre>
*/
@Schema(description = "Container with contentlet style properties")
public class ContentWithStylesForm extends Validated {

@JsonProperty("identifier")
@Schema(
description = "Container identifier",
example = "//demo.dotcms.com/application/containers/default/",
requiredMode = RequiredMode.REQUIRED
)
private final String containerId;

@JsonProperty("uuid")
@Schema(
description = "Container unique identifier (UUID)",
example = "1",
requiredMode = RequiredMode.REQUIRED
)
private final String uuid;

@JsonIgnore
@Schema(hidden = true)
private final Map<String, Map<String, Object>> contentletStyles;

/**
* Constructor for Jackson deserialization. Validation is performed in {@link #validate()}
* which should be called after deserialization.
*/
@JsonCreator
public ContentWithStylesForm(
@JsonProperty("identifier") final String containerId,
@JsonProperty("uuid") final String uuid) {

this.containerId = containerId;
this.uuid = uuid;
this.contentletStyles = new HashMap<>();
}

/**
* Captures dynamic properties (contentlet IDs with their styles). Called by Jackson for any
* JSON property not explicitly mapped.
*
* @param contentletId The contentlet identifier (JSON key)
* @param styleProperties The style properties for this contentlet (JSON value)
*/
@JsonAnySetter
public void addContentletStyle(final String contentletId, final Object styleProperties) {
// Only process if it's not one of the known fields and is a Map
if (!"containerId".equals(contentletId) && !"uuid".equals(contentletId)
&& styleProperties instanceof Map) {

final Map<String, Object> styles = new HashMap<>((Map<String, Object>) styleProperties);
this.contentletStyles.put(contentletId, styles);
}
}

public String getContainerId() {
return containerId;
}

public String getUuid() {
return uuid;
}

/**
* Returns a map of contentlet IDs to their style properties. Key: contentlet identifier Value:
* Map of style property names to values
*
* @return Unmodifiable map of contentlet styles
*/
@JsonIgnore
public Map<String, Map<String, Object>> getContentletStyles() {
return Collections.unmodifiableMap(contentletStyles);
}

/**
* Validates the form and returns a list of validation errors. This allows collecting multiple
* errors before throwing, providing better user experience.
*
* @return List of ContentletStylingErrorEntity objects, empty if validation passes
*/
public List<ContentletStylingErrorEntity> validate() {
final List<ContentletStylingErrorEntity> errors = new ArrayList<>();

if (!UtilMethods.isSet(containerId)) {
errors.add(new ContentletStylingErrorEntity(
"CONTAINER_REQUIRED",
"containerId cannot be empty",
"identifier",
null,
containerId,
uuid
));
}
if (!UtilMethods.isSet(uuid)) {
errors.add(new ContentletStylingErrorEntity(
"CONTAINER_UUID_REQUIRED",
"uuid cannot be empty",
"uuid",
null,
containerId,
uuid
));
}
if (contentletStyles.isEmpty()) {
errors.add(new ContentletStylingErrorEntity(
"CONTENTLET_STYLES_REQUIRED",
"styles must be set for at least one contentlet, it cannot be empty",
"\"contentlet-id\": { \"margin\": \"10px\" }"
));
}

return errors;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.dotcms.rest.api.v1.page;

import java.util.List;

import com.dotcms.rest.ErrorEntity;
import com.dotcms.rest.ResponseEntityView;
import com.dotcms.rest.exception.BadRequestException;
import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
Expand All @@ -10,14 +14,83 @@ public class ContentletStylingErrorEntity extends ErrorEntity {
private final String uuid;
private final String containerId;

public ContentletStylingErrorEntity(String code, String message, String contentletId,
String containerId, String uuid) {
super(code, message);
/**
* Constructor for creating a ContentletStylingErrorEntity with all fields.
*
* @param code Error code
* @param message Error message
* @param fieldName Field name where error occurred
* @param contentletId Contentlet ID
* @param containerId Container ID
* @param uuid UUID of the container
*/
public ContentletStylingErrorEntity(String code, String message, String fieldName,
String contentletId, String containerId, String uuid) {
super(code, message, fieldName);
this.contentletId = contentletId;
this.containerId = containerId;
this.uuid = uuid;
}

/**
* Simplified constructor for form-level validation errors. Use this when the error is not
* specific to a contentlet (e.g., missing container ID).
*
* @param errorCode Error code
* @param message Error message
* @param fieldName Field name where error occurred
*/
public ContentletStylingErrorEntity(String errorCode, String message, String fieldName) {
this(errorCode, message, fieldName, null, null, null);
}

/**
* Centralized method for throwing BadRequestException with styling errors. This ensures
* consistent error response format across all styling operations.
*
* @param errors List of ErrorEntity objects describing the validation failures
* @throws BadRequestException Always throws this exception with the provided errors
*/
public static void throwStylingBadRequest(List<ErrorEntity> errors) {
throw new BadRequestException(
null,
new ResponseEntityView<>(errors),
"Invalid Style Properties configuration"
);
}

/**
* Throws a BadRequestException with a single form-level validation error.
*
* @param errorCode Error code
* @param message Error message
* @param fieldName Field name where error occurred
* @throws BadRequestException Always throws with a single styling error
*/
public static void throwSingleError(String errorCode, String message, String fieldName) {
final ContentletStylingErrorEntity error =
new ContentletStylingErrorEntity(errorCode, message, fieldName);
throwStylingBadRequest(List.of(error));
}

/**
* Throws a BadRequestException with a single form-level error and additional contentlet
* details.
*
* @param errorCode Error code
* @param message Error message
* @param fieldName Field name where error occurred
* @param contentletId Contentlet ID
* @param containerId Container ID
* @param uuid UUID of the container
*/
public static void throwSingleError(String errorCode, String message, String fieldName,
String contentletId, String containerId, String uuid) {
final ContentletStylingErrorEntity error = new ContentletStylingErrorEntity(errorCode,
message, fieldName, contentletId, containerId, uuid);
throwStylingBadRequest(List.of(error));
}

public String getContainerId() {
return containerId;
}
Expand Down
Loading
Loading