diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/ContainerUUIDChanged.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/ContainerUUIDChanged.java index 3c7756d3d4a5..8c1dc84e2a07 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/ContainerUUIDChanged.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/ContainerUUIDChanged.java @@ -3,9 +3,22 @@ import com.dotmarketing.portlets.templates.design.bean.ContainerUUID; /** - * It is a change do by the end point into a {@link ContainerUUID} + * Represents a Container layout change in a dotCMS Template. + * + *

An instance of a Container in a Template is defined by its ID -- or file path for File + * Containers -- and its current instance ID. Such an instance ID allows content authors to add the + * same Container more than once in a Template. If one or more Containers of the same type are moved + * or deleted from the Template, the instance IDs of the remaining Containers may need to be updated + * so that such IDs are sorted sequentially. This class helps keep track of such changes.

+ * + *

Instance IDs are simple numeric values that are sorted in ascendant order, from top to bottom, + * from left to right, and their value starts with 1.

+ * + * @author Freddy Rodriguez + * @since Apr 23rd, 2018 */ public class ContainerUUIDChanged { + private final ContainerUUID oldContainer; private final ContainerUUID newContainer; @@ -14,11 +27,23 @@ public ContainerUUIDChanged(final ContainerUUID oldContainer, final ContainerUUI this.newContainer = newContainer; } - public ContainerUUID getOld() { + /** + * Returns the Container instance ID information before the Template's layout was changed. + * + * @return The {@link ContainerUUID} instance before the Template's layout was changed. + */ + public ContainerUUID getPreviousInfo() { return oldContainer; } - public ContainerUUID getNew() { + /** + * Returns the Container instance ID information after the Template's layout was changed. This + * can translate into the instance ID being updated. + * + * @return The {@link ContainerUUID} instance after the Template's layout was changed. + */ + public ContainerUUID getNewInfo() { return newContainer; } + } diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageForm.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageForm.java index 90236de1017c..17b14dae0577 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageForm.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageForm.java @@ -1,47 +1,49 @@ package com.dotcms.rest.api.v1.page; -import java.io.IOException; -import java.util.*; -import java.util.stream.Stream; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.dotcms.repackage.javax.validation.constraints.NotNull; -import com.dotcms.rest.api.Validated; +import com.dotcms.exception.ExceptionUtil; import com.dotcms.rest.exception.BadRequestException; import com.dotmarketing.portlets.templates.design.bean.ContainerUUID; import com.dotmarketing.portlets.templates.design.bean.TemplateLayout; import com.dotmarketing.portlets.templates.model.Template; import com.dotmarketing.util.Logger; import com.dotmarketing.util.UtilMethods; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.ImmutableList; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.common.collect.ImmutableMap; -import javax.annotation.Nullable; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; /** - * {@link PageResource}'s form + * Represents the layout of a Template in dotCMS. + * + *

All Containers that make up the structure of a Template -- along with its rows and columns -- + * are organized and transformed into an instance of this class.

+ * + * @author Freddy Rodriguez + * @since Nov 22nd, 2017 */ @JsonDeserialize(builder = PageForm.Builder.class) class PageForm { private final String themeId; private final String title; - private final String hostId; + private final String siteId; private final TemplateLayout layout; private final Map changes; private final Map newlyContainersUUID; - public PageForm(final String themeId, final String title, final String hostId, final TemplateLayout layout, - final Map changes, Map newlyContainersUUID) { + public PageForm(final String themeId, final String title, final String siteId, final TemplateLayout layout, + final Map changes, final Map newlyContainersUUID) { this.themeId = themeId; this.title = title; - this.hostId = hostId; + this.siteId = siteId; this.layout = layout; this.changes = ImmutableMap. builder().putAll(changes).build(); this.newlyContainersUUID = ImmutableMap.copyOf(newlyContainersUUID); @@ -66,10 +68,10 @@ public String getTitle() { /** * - * @return Layout's host + * @return Layout's site */ - public String getHostId() { - return hostId; + public String getSiteId() { + return siteId; } public boolean isAnonymousLayout() { @@ -84,7 +86,28 @@ public TemplateLayout getLayout() { return layout; } - public ContainerUUIDChanged getChange (String identifier, String uuid) { + /** + * Allows you to determine whether the instance ID of a given Container has changed or not based + * on the modifications that are being persisted. This makes it easier for the API to be able to + * update the necessary instance IDs across the page's Template so that contents are displayed + * in the appropriate order. + * + *

For instance, if a Template has three instances of the Default Container -- "1", "2", and + * "3" -- and instance "2" is deleted, the change list will be:

+ *
    + *
  • Old Instance ID = "1" / New Instance ID = "1" -- The value remains.
  • + *
  • Instance ID "2" is NOT present as it is the one that was removed from the Template + * .
  • + *
  • Old Instance ID = "3" / New Instance ID = "2" -- Because the second instance was + * removed, the third Container now takes the second instance's ID.
  • + *
+ * + * @param identifier The ID of the Container, or its path in case it's a Container as File. + * @param uuid The current instance ID of the Container. + * + * @return The {@link ContainerUUIDChanged} instance that contains the old and new instance IDs. + */ + public ContainerUUIDChanged getChangeInContainerInstanceIDs(final String identifier, final String uuid) { ContainerUUIDChanged containerUUIDChanged = this.changes.get(getChangeKey(identifier, uuid)); if (containerUUIDChanged == null) { @@ -96,14 +119,37 @@ public ContainerUUIDChanged getChange (String identifier, String uuid) { return containerUUIDChanged; } + /** + * Returns the instance ID of a Container that has just been added to the Template. That is, a + * Container that didn't exist and was added by this change in particular. + * + * @param identifier The ID or file path of the recently-added Container + * + * @return The instance ID of the recently-added Container. + */ public String getNewlyContainerUUID (String identifier) { return this.newlyContainersUUID.get(identifier); } + /** + * Generates the key that identifies the potential change in a Container instance ID. + * + * @param containerUUID The {@link ContainerUUID} object whose information may have changed.. + * + * @return The key that identifies the potential change in a Container instance ID. + */ private static String getChangeKey(ContainerUUID containerUUID) { return getChangeKey(containerUUID.getIdentifier(), containerUUID.getUUID()); } + /** + * Generates the key that identifies a potential change in a Container's instance ID. + * + * @param identifier The ID or file path to a given Container. + * @param uuid The current instance ID of the Container. + * + * @return The key that identifies the potential change in a Container instance ID. + */ private static String getChangeKey(String identifier, String uuid) { return String.format("%s - %s", identifier, uuid); } @@ -154,6 +200,14 @@ public Builder layout(Map layout) { return this; } + /** + * Transforms the Template's layout as a data Map into an instance of + * {@link TemplateLayout}. + * + * @return An instance of {@link TemplateLayout} that represents the Template's layout. + * + * @throws BadRequestException If the Template's layout is invalid or missing. + */ private TemplateLayout getTemplateLayout() throws BadRequestException { if (layout == null) { @@ -164,8 +218,10 @@ private TemplateLayout getTemplateLayout() throws BadRequestException { this.setContainersUUID(); final String layoutString = MAPPER.writeValueAsString(layout); return MAPPER.readValue(layoutString, TemplateLayout.class); - } catch (IOException e) { - throw new BadRequestException(e, "An error occurred when proccessing the JSON request"); + } catch (final IOException e) { + throw new BadRequestException(e, String.format("An error occurred when processing" + + " the layout for Template '%s'", UtilMethods.isSet(title) ? title : "- " + + "Anonymous Template -")); } } @@ -190,35 +246,43 @@ private void setContainersUUID() { getAllContainers().forEach(container -> setChange(maxUUIDByContainer, container)); } - private void setChange(Map maxUUIDByContainer, Map container) { + /** + * Loads the data Maps of every Container in the Template's layout, with its previous and + * updated instance ID. + * + * @param maxInstanceIDByContainer Keeps track of the maximum instance ID that has been + * assigned to a previously added Container of the same + * type. It helps increase the instance ID one by one in + * order. + * @param container The current Container instance inside the Template. + */ + private void setChange(final Map maxInstanceIDByContainer, final Map container) { try { final String containerId = container.get("identifier"); - final long currentUUID = maxUUIDByContainer.get(containerId) != null ? - maxUUIDByContainer.get(containerId) : 0; - final long nextUUID = currentUUID + 1; + final long currentInstanceID = maxInstanceIDByContainer.get(containerId) != null ? + maxInstanceIDByContainer.get(containerId) : 0; + final long nextInstanceID = currentInstanceID + 1; if (container.get("uuid") != null) { - final ContainerUUID oldContainerUUID = MAPPER.readValue(MAPPER.writeValueAsString(container), + final ContainerUUID oldContainerInstanceID = MAPPER.readValue(MAPPER.writeValueAsString(container), ContainerUUID.class); - container.put("uuid", String.valueOf(nextUUID)); - final ContainerUUID newContainerUUID = MAPPER.readValue(MAPPER.writeValueAsString(container), + container.put("uuid", String.valueOf(nextInstanceID)); + final ContainerUUID newContainerInstanceID = MAPPER.readValue(MAPPER.writeValueAsString(container), ContainerUUID.class); - - String changeKey = getChangeKey(oldContainerUUID); - changes.put(changeKey, new ContainerUUIDChanged(oldContainerUUID, newContainerUUID)); + changes.put(getChangeKey(oldContainerInstanceID), new ContainerUUIDChanged(oldContainerInstanceID, newContainerInstanceID)); } else { - container.put("uuid", String.valueOf(nextUUID)); - newlyContainersUUID.put(containerId, String.valueOf(nextUUID)); + container.put("uuid", String.valueOf(nextInstanceID)); + newlyContainersUUID.put(containerId, String.valueOf(nextInstanceID)); } - maxUUIDByContainer.put(containerId, nextUUID); - - } catch (IOException e) { - Logger.error(this.getClass(),"Exception on setContainersUUID exception message: " + e.getMessage(), e); + maxInstanceIDByContainer.put(containerId, nextInstanceID); + } catch (final IOException e) { + Logger.error(this.getClass(),String.format("Failed to map changed Container instance IDs in Template " + + "'%s': %s", UtilMethods.isSet(title) ? title : "- Anonymous Template -", + ExceptionUtil.getErrorMessage(e)), e); } } - private Stream> getAllContainers() { final Stream> bodyContainers = ((List>) ((Map) layout.get("body")).get("rows")) @@ -242,5 +306,7 @@ private Stream> getAllContainers() { public PageForm build(){ return new PageForm(themeId, title, hostId, getTemplateLayout(), changes, newlyContainersUUID); } + } + } diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java index 499a1e417392..6b557f7691e1 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java @@ -10,7 +10,6 @@ import com.dotcms.ema.EMAWebInterceptor; import com.dotcms.ema.resolver.EMAConfigStrategy; import com.dotcms.ema.resolver.EMAConfigStrategyResolver; -import com.dotcms.exception.ExceptionUtil; import com.dotcms.rest.InitDataObject; import com.dotcms.rest.ResponseEntityBooleanView; import com.dotcms.rest.ResponseEntityView; @@ -99,6 +98,8 @@ import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import static com.dotcms.util.DotPreconditions.checkNotNull; + /** * Provides different methods to access information about HTML Pages in dotCMS. For example, * users of this end-point can get the metadata of an HTML Page (i.e., information about the @@ -321,17 +322,22 @@ public Response render(@Context final HttpServletRequest originalRequest, return res; } - /** - * Save a template and link it with a page, If the page already has a anonymous template linked then it is updated, - * otherwise a new template is created and the old link template remains unchanged + * Saves a Template and links it with an HTML Page. If the page already has an Anonymous + * Template linked to it, it will be updated in these new changes. Otherwise, a new Anonymous + * Template is created and the previously linked Template will remain unchanged. * - * @see Template#isAnonymous() + * @param request The current instance of the {@link HttpServletRequest}. + * @param response The current instance of the {@link HttpServletResponse}. + * @param pageId The ID of the page that the Template will be linked to. + * @param variantNameParam The name of the Variant associated to the page. + * @param form The {@link PageForm} containing the information of the Template. * - * @param request The {@link HttpServletRequest} object. - * @param pageId page's Id to link the template - * @param form The {@link PageForm} - * @return + * @return The {@link Response} entity containing the updated {@link PageView} object for the + * specified page. + * + * @throws DotSecurityException The currently logged-in user does not have the necessary + * permissions to perform this action. */ @NoCache @POST @@ -346,18 +352,12 @@ public Response saveLayout(@Context final HttpServletRequest request, final String variantName = UtilMethods.isSet(variantNameParam) ? variantNameParam : VariantAPI.DEFAULT_VARIANT.name(); - Logger.debug(this, String.format("Saving layout: pageId -> %s layout-> %s variantName -> %s", pageId, + Logger.debug(this, () -> String.format("Saving layout: pageId -> %s , layout -> %s , variantName -> %s", pageId, form != null ? form.getLayout() : null, variantName)); - - if (form == null) { - throw new BadRequestException("Layout is required"); - } + checkNotNull(form, BadRequestException.class, "The 'PageForm' JSON data is required"); final InitDataObject auth = webResource.init(request, response, true); final User user = auth.getUser(); - - Response res; - try { HTMLPageAsset page = (HTMLPageAsset) this.pageResourceHelper.getPage(user, pageId, request); @@ -373,21 +373,18 @@ public Response saveLayout(@Context final HttpServletRequest request, response ); - res = Response.ok(new ResponseEntityView(renderedPage)).build(); - - } catch(DoesNotExistException e) { - final String errorMsg = String.format("DoesNotExistException on PageResource.saveLayout, parameters: %s, %s %s: ", + return Response.ok(new ResponseEntityView<>(renderedPage)).build(); + } catch (final DoesNotExistException e) { + final String errorMsg = String.format("DoesNotExistException on PageResource.saveLayout. Parameters: [ %s ], [ %s ], [ %s ]: ", request, pageId, form); Logger.error(this, errorMsg, e); - res = ExceptionMapperUtil.createResponse("", "Unable to find page with Identifier: " + pageId, Response.Status.NOT_FOUND); - } catch (BadRequestException | DotDataException e) { - final String errorMsg = String.format("%s on PageResource.saveLayout, parameters: %s, %s %s: ", + return ExceptionMapperUtil.createResponse("", "Unable to find page with Identifier: " + pageId, Response.Status.NOT_FOUND); + } catch (final BadRequestException | DotDataException e) { + final String errorMsg = String.format("%s on PageResource.saveLayout. Parameters: [ %s ], [ %s ], [ %s ]: ", e.getClass().getCanonicalName(), request, pageId, form); Logger.error(this, errorMsg, e); - res = ExceptionMapperUtil.createResponse(e, Response.Status.BAD_REQUEST); + return ExceptionMapperUtil.createResponse(e, Response.Status.BAD_REQUEST); } - - return res; } /** diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResourceHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResourceHelper.java index d22fdba135c2..8956abcb0aff 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResourceHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResourceHelper.java @@ -2,6 +2,7 @@ import com.dotcms.api.web.HttpServletRequestThreadLocal; import com.dotcms.business.WrapInTransaction; +import com.dotcms.exception.ExceptionUtil; import com.dotcms.mock.request.CachedParameterDecorator; import com.dotcms.mock.request.HttpServletRequestParameterDecoratorWrapper; import com.dotcms.mock.request.LanguageIdParameterDecorator; @@ -48,6 +49,7 @@ import com.dotmarketing.portlets.templates.business.TemplateAPI; import com.dotmarketing.portlets.templates.design.bean.ContainerUUID; import com.dotmarketing.portlets.templates.model.Template; +import com.dotmarketing.util.Config; import com.dotmarketing.util.Logger; import com.dotmarketing.util.PageMode; import com.dotmarketing.util.UtilMethods; @@ -57,6 +59,7 @@ import com.liferay.portal.SystemException; import com.liferay.portal.model.User; import com.liferay.util.StringPool; +import io.vavr.Lazy; import io.vavr.Tuple; import io.vavr.Tuple2; import io.vavr.control.Try; @@ -88,6 +91,9 @@ public class PageResourceHelper implements Serializable { private static final long serialVersionUID = 296763857542258211L; + private static final Lazy DELETE_ORPHANED_CONTENTS_FROM_CONTAINER = + Lazy.of(() -> Config.getBooleanProperty("DELETE_ORPHANED_CONTENTS_FROM_CONTAINER", true)); + private final HostWebAPI hostWebAPI = WebAPILocator.getHostWebAPI(); private final HTMLPageAssetAPI htmlPageAssetAPI = APILocator.getHTMLPageAssetAPI(); private final TemplateAPI templateAPI = APILocator.getTemplateAPI(); @@ -339,11 +345,26 @@ private IHTMLPage createNewVersion(final User user, final String pageInode, return APILocator.getHTMLPageAssetAPI().fromContentlet(checkin); } + /** + * Saves the HTML Page layout -- i.e., the Template -- and its associated Containers and + * Contentlets. + * + * @param page The {@link IHTMLPage} object whose layout will be saved. + * @param user The {@link User} performing this action. + * @param pageForm The {@link PageForm} object containing the new Template's layout + * information. + * + * @return The {@link Template} object that was saved. + * + * @throws DotDataException An error occurred when persisting the changes. + * @throws DotSecurityException The specified User does not have the required permission to + * execute this action. + */ @WrapInTransaction public Template saveTemplate(final IHTMLPage page, final User user, final PageForm pageForm) throws DotDataException, DotSecurityException { - final Host host = getHost(pageForm.getHostId(), user); + final Host site = getSite(pageForm.getSiteId(), user); final User systemUser = userAPI.getSystemUser(); final Template template = checkoutTemplate(page, systemUser, pageForm); @@ -362,14 +383,34 @@ public Template saveTemplate(final IHTMLPage page, final User user, final PageFo template.setModDate(new Date()); // permissions have been updated above - return this.templateAPI.saveTemplate(template, host, APILocator.systemUser(), false); + return this.templateAPI.saveTemplate(template, site, APILocator.systemUser(), false); } catch (final DotDataException | DotSecurityException e) { final String errorMsg = String.format("An error occurred when saving Template '%s' [ %s ]: %s", - template.getTitle(), template.getIdentifier(), e.getMessage()); + template.getTitle(), template.getIdentifier(), ExceptionUtil.getErrorMessage(e)); throw new DotRuntimeException(errorMsg, e); } } + /** + * Updates the Multi-Tree data for a given HTML Page when its Template has changed. + * + *

When the page's Layout changes in terms of adding, removing or relocating Containers, + * this method will update the internal ID of the Containers on the page and will remove any + * orphaned data -- unless the {@code DELETE_ORPHANED_CONTENTS_FROM_CONTAINER} is set + * to{@code false}. Keeping such IDs in order is crucial for the layout to work correctly.

+ * + *

On the other hand, if the {@code DELETE_ORPHANED_CONTENTS_FROM_CONTAINER} property is set + * to {@code false}, existing Contentlets associated to removed Container will be added to it if + * you put it back.

+ * + * @param page The {@link IHTMLPage} whose Multi-Tree data will be updated. + * @param pageForm The {@link PageForm} object containing the new Template's layout + * information. + * + * @throws DotDataException An error occurred when updating the information in the + * database. + * @throws DotSecurityException A user permission error has occurred. + */ @WrapInTransaction protected void updateMultiTrees(final IHTMLPage page, final PageForm pageForm) throws DotDataException, DotSecurityException { @@ -378,10 +419,10 @@ protected void updateMultiTrees(final IHTMLPage page, final PageForm pageForm) t .getPageMultiTrees(page, currentVariantId, false); final String pageIdentifier = page.getIdentifier(); - APILocator.getMultiTreeAPI().deleteMultiTree(pageIdentifier, currentVariantId); + this.multiTreeAPI.deleteMultiTree(pageIdentifier, currentVariantId); final List multiTrees = new ArrayList<>(); - + final boolean deleteOrphanedContents = DELETE_ORPHANED_CONTENTS_FROM_CONTAINER.get(); for (final String containerId : pageContents.rowKeySet()) { int treeOrder = 0; @@ -390,19 +431,22 @@ protected void updateMultiTrees(final IHTMLPage page, final PageForm pageForm) t final Set contents = row.get(uniqueId); if (!contents.isEmpty()) { - final String newUUID = getNewUUID(pageForm, containerId, uniqueId); - - for (final PersonalizedContentlet identifierPersonalization : contents) { - final MultiTree multiTree = MultiTree.personalized( - new MultiTree().setContainer(containerId) - .setContentlet(identifierPersonalization.getContentletId()) - .setInstanceId(newUUID) - .setTreeOrder(treeOrder++) - .setHtmlPage(pageIdentifier) - .setVariantId(currentVariantId), - identifierPersonalization.getPersonalization()); - - multiTrees.add(multiTree); + final String newContainerLayoutID = getNewContainerLayoutID(pageForm, containerId, uniqueId); + // Adding multi-tre records is skipped if (1) deleting orphaned records is enabled + // and (2) the container instance ID equals -1 + if (!deleteOrphanedContents || (!ContainerUUID.UUID_DEFAULT_VALUE.equals(newContainerLayoutID))) { + for (final PersonalizedContentlet identifierPersonalization : contents) { + final MultiTree multiTree = MultiTree.personalized( + new MultiTree().setContainer(containerId) + .setContentlet(identifierPersonalization.getContentletId()) + .setInstanceId(newContainerLayoutID) + .setTreeOrder(treeOrder++) + .setHtmlPage(pageIdentifier) + .setVariantId(currentVariantId), + identifierPersonalization.getPersonalization()); + + multiTrees.add(multiTree); + } } } } @@ -411,35 +455,58 @@ protected void updateMultiTrees(final IHTMLPage page, final PageForm pageForm) t multiTreeAPI.saveMultiTrees(pageIdentifier, currentVariantId, multiTrees); } - private String getNewUUID(final PageForm pageForm, final String containerId, - final String uniqueId) + /** + * Returns the existing instance ID of the specified Container in the HTML Page Layout, or a new + * instance ID in case it was changed because one or more Containers before or after it were + * added, moved or removed. + * + *

The object {@link ContainerUUIDChanged} in the {@link PageForm} parameter indicates + * whether a Container's position was changed, or removed altogether. If that's the case, an + * instance ID of -1 will be returned. Containers whose instance ID start with + * {@link ParseContainer#PARSE_CONTAINER_UUID_PREFIX} will always keep its ID.

+ * + *

Additionally, if the Container instance ID cannot be found in the internal Map that holds + * the updated position of Containers, it means such a Container doesn't exist anymore, so a -1 + * will be returned.

+ * + * @param pageForm The {@link PageForm} object containing the Template's new layout + * information. + * @param containerId The ID of the Container being processed. + * @param uniqueId The currently assigned instance ID of the Container + * + * @return The new instance ID of the Container, or -1 in case it was deleted from the Template. + * + * @throws DotDataException An error occurred when persisting the changes. + * @throws DotSecurityException A user permission error has occurred. + */ + private String getNewContainerLayoutID(final PageForm pageForm, final String containerId, + final String uniqueId) throws DotDataException, DotSecurityException { - - //If we have a FileAssetContainer we may need to search also by path String containerPath = null; final Container foundContainer = APILocator.getContainerAPI() .getWorkingContainerById(containerId, userAPI.getSystemUser(), false); if (foundContainer instanceof FileAssetContainer) { - containerPath = FileAssetContainerUtil.getInstance().getFullPath( - FileAssetContainer.class.cast(foundContainer) - ); + // If we have a FileAssetContainer, we may need to search by path instead + containerPath = FileAssetContainerUtil.getInstance().getFullPath((FileAssetContainer) foundContainer); } if (ContainerUUID.UUID_DEFAULT_VALUE.equals(uniqueId)) { - String newlyContainerUUID = pageForm.getNewlyContainerUUID(containerId); - if (newlyContainerUUID == null && containerPath != null) {//Searching also by path if nothing found - newlyContainerUUID = pageForm.getNewlyContainerUUID(containerPath); + String newContainerInstanceID = pageForm.getNewlyContainerUUID(containerId); + if (newContainerInstanceID == null && containerPath != null) { + // Searching also by Container path -- i.e., Container as File -- if not found + newContainerInstanceID = pageForm.getNewlyContainerUUID(containerPath); } - return newlyContainerUUID != null ? newlyContainerUUID + return newContainerInstanceID != null ? newContainerInstanceID : ContainerUUID.UUID_DEFAULT_VALUE; } if (ParseContainer.isParserContainerUUID(uniqueId)) { return uniqueId; } else { - ContainerUUIDChanged change = pageForm.getChange(containerId, uniqueId); - if (change == null && containerPath != null) {//Searching also by path if nothing found - change = pageForm.getChange(containerPath, uniqueId); + ContainerUUIDChanged change = pageForm.getChangeInContainerInstanceIDs(containerId, uniqueId); + if (change == null && containerPath != null) { + // Searching also by Container path -- i.e., Container as File -- if not found + change = pageForm.getChangeInContainerInstanceIDs(containerPath, uniqueId); } - return change != null ? change.getNew().getUUID() : ContainerUUID.UUID_DEFAULT_VALUE; + return change != null ? change.getNewInfo().getUUID() : ContainerUUID.UUID_DEFAULT_VALUE; } } @@ -472,12 +539,20 @@ private Template checkoutTemplate(final IHTMLPage page, final User user, final P return saveTemplate; } - private Host getHost(final String hostId, final User user) { + /** + * Returns the {@link Host} object for the specified Site Identifier. + * + * @param siteId The Identifier of the Site whose {@link Host} object will be returned. + * @param user The {@link User} performing this action. + * + * @return The {@link Host} object for the specified Site Identifier. + */ + private Host getSite(final String siteId, final User user) { try { - return UtilMethods.isSet(hostId) ? hostAPI.find(hostId, user, false) : + return UtilMethods.isSet(siteId) ? hostAPI.find(siteId, user, false) : hostWebAPI.getCurrentHost(HttpServletRequestThreadLocal.INSTANCE.getRequest()); - } catch (DotDataException | DotSecurityException | PortalException | SystemException e) { - throw new DotRuntimeException(e); + } catch (final DotDataException | DotSecurityException | PortalException | SystemException e) { + throw new DotRuntimeException(String.format("Could not find Site with ID '%s'", siteId), e); } } diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/templates/design/bean/ContainerUUID.java b/dotCMS/src/main/java/com/dotmarketing/portlets/templates/design/bean/ContainerUUID.java index 6292710f65fd..b979a4e276c9 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/templates/design/bean/ContainerUUID.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/templates/design/bean/ContainerUUID.java @@ -1,13 +1,18 @@ package com.dotmarketing.portlets.templates.design.bean; -import java.io.Serializable; - import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.Serializable; + /** - * Container link with a {@link TemplateLayout}, this have the UUID + * Provides the relationship between a Container and its instance ID in a Template's layout in + * dotCMS. The Container's instance ID is basically the mechanism that allows content authors + * to add the same Container in a Template more than once. + * + * @author Freddy Rodriguez + * @since Jan 11th, 2018 */ public class ContainerUUID implements Serializable{ @@ -18,18 +23,27 @@ public class ContainerUUID implements Serializable{ private final String identifier; private String uuid; + public ContainerUUID(final @JsonProperty("identifier") String containerIdOrPath, + final @JsonProperty("uuid") String containerInstanceID) { - public ContainerUUID(final @JsonProperty("identifier") String containerIdentifier, - final @JsonProperty("uuid") String containerIdOrPath) { - - this.identifier = containerIdentifier; - this.uuid = containerIdOrPath == null ? UUID_DEFAULT_VALUE : containerIdOrPath; + this.identifier = containerIdOrPath; + this.uuid = containerInstanceID == null ? UUID_DEFAULT_VALUE : containerInstanceID; } + /** + * Returns the Container's identifier, or its file path in case it's a Container as File. + * + * @return The Container's identifier or file path. + */ public String getIdentifier() { return identifier; } + /** + * Returns the Container's instance ID in a Template's layout. + * + * @return The Container's instance ID. + */ public String getUUID() { return uuid; } @@ -43,7 +57,13 @@ public String toString() { } } + /** + * Sets the Container's instance ID for a given Template's layout. + * + * @param uuid The Container's instance ID. + */ public void setUuid(final String uuid){ this.uuid = uuid; } + }