Skip to content

Commit

Permalink
Issue 30285 clean up the unique fields table (#30798)
Browse files Browse the repository at this point in the history
### Proposed Changes
* Create a Listener to listen when a Contentlet or a FIeld is deleted
and then we can clean the unique_fields table up


https://github.com/dotCMS/core/pull/30798/files#diff-615b3dd949e41009affc1f2921cfe0c6569a1302deec3469d834f6163d7104a8R138

* Create new Method in the UniqueFieldValidationStrategy to clean up the
table


https://github.com/dotCMS/core/pull/30798/files#diff-445f8d01aa4de058eaaf883e573d17ef1123b1e74a9a98120ac156b78f4c6522R131


https://github.com/dotCMS/core/pull/30798/files#diff-445f8d01aa4de058eaaf883e573d17ef1123b1e74a9a98120ac156b78f4c6522R162

* Create method to update the unique_fields table data when a Contentlet
is publish or unpublish


https://github.com/dotCMS/core/pull/30798/files#diff-445f8d01aa4de058eaaf883e573d17ef1123b1e74a9a98120ac156b78f4c6522R141-R153

* Implemenetd all this new methods in the
DBUniqueFieldValidationStrategy


https://github.com/dotCMS/core/pull/30798/files#diff-445f8d01aa4de058eaaf883e573d17ef1123b1e74a9a98120ac156b78f4c6522R141-R153

---------

Co-authored-by: fabrizzio-dotCMS <[email protected]>
  • Loading branch information
freddyDOTCMS and fabrizzio-dotCMS authored Dec 2, 2024
1 parent 58766dd commit a343172
Show file tree
Hide file tree
Showing 28 changed files with 1,154 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
import com.dotcms.api.system.event.PayloadVerifierFactoryInitializer;
import com.dotcms.api.system.event.SystemEventProcessorFactoryInitializer;
import com.dotcms.business.SystemTableInitializer;
import com.dotcms.cdi.CDIUtils;
import com.dotcms.contenttype.business.ContentTypeInitializer;
import com.dotcms.contenttype.business.uniquefields.UniqueFieldValidationStrategyResolver;
import com.dotcms.contenttype.business.uniquefields.extratable.UniqueFieldsTableCleanerInitializer;
import com.dotcms.rendering.velocity.events.ExceptionHandlersInitializer;
import com.dotcms.system.event.local.business.LocalSystemEventSubscribersInitializer;
import com.dotcms.util.ReflectionUtils;
Expand Down Expand Up @@ -132,6 +135,7 @@ private Set<DotInitializer> getInternalInitializers() {
new DefaultVariantInitializer(),
new SystemTableInitializer(),
new EmbeddingsInitializer(),
CDIUtils.getBeanThrows(UniqueFieldsTableCleanerInitializer.class),
new AnalyticsInitializer()
);
} // getInternalInitializers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -622,8 +622,7 @@ protected void delete(List<Contentlet> contentlets, boolean deleteIdentifier) th
}
if(verInfo.get().getWorkingInode().equals(contentlet.getInode()))
APILocator.getVersionableAPI()
.deleteContentletVersionInfo(contentlet.getIdentifier(),
contentlet.getLanguageId());
.deleteContentletVersionInfoByLanguage(contentlet);
}
delete(contentlet.getInode());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ public void delete(final Field field, final User user) throws DotDataException,
}

CleanUpFieldReferencesJob.triggerCleanUpJob(field, user);
localSystemEventsAPI.notify(new FieldDeletedEvent(field.variable()));
localSystemEventsAPI.notify(new FieldDeletedEvent(field));

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.dotcms.contenttype.model.field.Field;
import com.dotcms.contenttype.model.type.ContentType;
import com.dotcms.util.DotPreconditions;
import com.dotmarketing.beans.VersionInfo;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotSecurityException;
Expand Down Expand Up @@ -102,7 +103,6 @@ void innerValidate(final Contentlet contentlet, final Field field, final Object
default void afterSaved(final Contentlet contentlet, final boolean isNew) throws DotDataException, DotSecurityException {
// Default implementation does nothing
}

default void recalculate(final Field field, final boolean uniquePerSite) throws UniqueFieldValueDuplicatedException {
// Default implementation does nothing
}
Expand All @@ -120,4 +120,46 @@ default void validateField(final Field field) {
}
}

/**
* Clean the Extra unique validation field table after a {@link Contentlet} have been removed.
* We need to remove all the unique values of this {@link Contentlet} and {@link com.dotmarketing.portlets.languagesmanager.model.Language}
* from the extra table.
*
* @param contentlet
*/
default void cleanUp(final Contentlet contentlet, final boolean deleteAllVariant) throws DotDataException {
//Default implementation do nothing
}

/**
* Method call after publish a {@link Contentlet} it allow the {@link UniqueFieldValidationStrategy} do any extra
* work that it need it.
*
* @param inode Published {@link Contentlet}'s inode
*/
default void afterPublish(final String inode) {
//Default implementation do nothing
}

/**
* Method call after unpublished a {@link Contentlet} it allow thw {@link UniqueFieldValidationStrategy} do any extra
* work that it need it.
*
* @param versionInfo {@link Contentlet}'s {@link VersionInfo} before un publish
*/
default void afterUnpublish(final VersionInfo versionInfo){
//Default implementation do nothing
}

/**
* Method called after delete a Unique {@link Field}, to allow the {@link UniqueFieldValidationStrategy} do any extra
* work that it need it.
*
* @param field deleted field
* @throws DotDataException
*/
default void cleanUp(final Field field) throws DotDataException {
//Default implementation do nothing
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import com.dotcms.exception.ExceptionUtil;
import com.dotcms.util.JsonUtil;
import com.dotmarketing.beans.Host;
import com.dotmarketing.beans.VersionInfo;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.DotStateException;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.exception.DotSecurityException;
Expand Down Expand Up @@ -78,6 +80,7 @@ public void innerValidate(final Contentlet contentlet, final Field field, final
.setContentType(contentType)
.setValue(fieldValue)
.setVariantName(contentlet.getVariantId())
.setLive(isLive(contentlet))
.build();

insertUniqueValue(uniqueFieldCriteria, contentlet.getIdentifier());
Expand All @@ -103,38 +106,41 @@ private static boolean isContentletBeingUpdated(final Contentlet contentlet) {
* is not re-generated as the Contentlet ID is not used in it.</p>
*
* @param contentlet The {@link Contentlet} being updated.
* @param field The {@link Field} representing the Unique Field.
*
* @throws DotDataException An error occurred when interacting with the database.
*/
@SuppressWarnings("unchecked")
private void cleanUniqueFieldsUp(final Contentlet contentlet, final Field field) throws DotDataException {
final Optional<Map<String, Object>> uniqueFieldOptional = uniqueFieldDataBaseUtil.get(contentlet);
private void cleanUniqueFieldsUp(final Contentlet contentlet, final Field field) throws DotDataException {
Optional<Map<String, Object>> uniqueFieldOptional = uniqueFieldDataBaseUtil.get(contentlet, field);

if (uniqueFieldOptional.isPresent()) {
cleanUniqueFieldUp(contentlet.getIdentifier(), uniqueFieldOptional.get());
}
}

private void cleanUniqueFieldUp(final String contentId,
final Map<String, Object> uniqueFields) {
try {
if (uniqueFieldOptional.isPresent()) {
final Map<String, Object> uniqueFields = uniqueFieldOptional.get();

final String hash = uniqueFields.get("unique_key_val").toString();
final PGobject supportingValues = (PGobject) uniqueFields.get("supporting_values");
final Map<String, Object> supportingValuesMap = JsonUtil.getJsonFromString(supportingValues.getValue());
final List<String> contentletIds = (List<String>) supportingValuesMap.get(CONTENTLET_IDS_ATTR);
final String hash = uniqueFields.get("unique_key_val").toString();
final PGobject supportingValues = (PGobject) uniqueFields.get("supporting_values");
final Map<String, Object> supportingValuesMap = JsonUtil.getJsonFromString(supportingValues.getValue());
final List<String> contentletIds = (List<String>) supportingValuesMap.get(CONTENTLET_IDS_ATTR);

if (contentletIds.size() == 1) {
uniqueFieldDataBaseUtil.delete(hash, field.variable());
} else {
contentletIds.remove(contentlet.getIdentifier());
uniqueFieldDataBaseUtil.updateContentListWithHash(hash, contentletIds);
}
if (contentletIds.size() == 1) {
uniqueFieldDataBaseUtil.delete(hash);
} else {
contentletIds.remove(contentId);
uniqueFieldDataBaseUtil.updateContentListWithHash(hash, contentletIds);
}
} catch (final IOException e){
throw new DotDataException(e);
} catch (IOException | DotDataException e){
throw new DotRuntimeException(e);
}
}

@Override
public void afterSaved(final Contentlet contentlet, final boolean isNew) throws DotDataException, DotSecurityException {
if (isNew) {
if (hasUniqueField(contentlet.getContentType()) && isNew) {
final ContentType contentType = APILocator.getContentTypeAPI(APILocator.systemUser())
.find(contentlet.getContentTypeId());

Expand All @@ -159,13 +165,22 @@ public void afterSaved(final Contentlet contentlet, final boolean isNew) throws
.setField(uniqueField)
.setContentType(contentType)
.setValue(fieldValue)
.setLive(isLive(contentlet))
.build();

uniqueFieldDataBaseUtil.updateContentList(uniqueFieldCriteria, contentlet.getIdentifier());
}
}
}

private static boolean isLive(Contentlet contentlet) {
try {
return contentlet.isLive();
} catch (DotDataException | DotSecurityException | DotStateException e) {
return false;
}
}

/**
* Inserts a new unique field value in the database.
*
Expand Down Expand Up @@ -239,4 +254,56 @@ private static boolean isDuplicatedKeyError(final Exception exception) {
"ERROR: duplicate key value violates unique constraint \"unique_fields_pkey\"");
}

@Override
public void cleanUp(final Contentlet contentlet, final boolean deleteAllVariant) throws DotDataException {
if (deleteAllVariant) {
uniqueFieldDataBaseUtil.get(contentlet.getIdentifier(), contentlet.getVariantId()).stream()
.forEach(uniqueFieldValue -> cleanUniqueFieldUp(contentlet.getIdentifier(), uniqueFieldValue));
} else {
uniqueFieldDataBaseUtil.get(contentlet.getIdentifier(), contentlet.getLanguageId()).stream()
.forEach(uniqueFieldValue -> cleanUniqueFieldUp(contentlet.getIdentifier(), uniqueFieldValue));
}

}

@Override
public void cleanUp(final Field field) throws DotDataException {
uniqueFieldDataBaseUtil.delete(field);
}

@Override
public void afterPublish(final String inode) {
try {
final Contentlet contentlet = APILocator.getContentletAPI().find(inode, APILocator.systemUser(), false);

if (hasUniqueField(contentlet.getContentType())) {
uniqueFieldDataBaseUtil.setLive(contentlet, true);
}
} catch (DotDataException | DotSecurityException e) {
throw new RuntimeException(e);
}
}

@Override
public void afterUnpublish(final VersionInfo versionInfo){
try {
final Contentlet liveContentlet = APILocator.getContentletAPI().find(versionInfo.getLiveInode(),
APILocator.systemUser(), false);

if (hasUniqueField(liveContentlet.getContentType())) {
if (versionInfo.getWorkingInode().equals(versionInfo.getLiveInode())) {
uniqueFieldDataBaseUtil.setLive(liveContentlet, false);
} else {
uniqueFieldDataBaseUtil.removeLive(liveContentlet);
}
}
} catch (DotDataException | DotSecurityException e) {
throw new RuntimeException(e);
}
}


private static boolean hasUniqueField(ContentType contentType) {
return contentType.fields().stream().anyMatch(field -> field.unique());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.dotcms.contenttype.business.uniquefields.extratable;


import com.dotcms.api.APIProvider;
import com.dotcms.content.elasticsearch.business.ESContentletAPIImpl;

import com.dotcms.contenttype.model.field.Field;
import com.dotcms.contenttype.model.type.ContentType;
import com.dotmarketing.beans.Host;
Expand Down Expand Up @@ -41,21 +45,26 @@ public class UniqueFieldCriteria {
public static final String CONTENTLET_IDS_ATTR = "contentletIds";
public static final String VARIANT_ATTR = "variant";
public static final String UNIQUE_PER_SITE_ATTR = "uniquePerSite";

public static final String LIVE_ATTR = "live";
private final ContentType contentType;
private final Field field;
private final Object value;
private final Language language;
private final Host site;

private final String variantName;

private boolean isLive;


public UniqueFieldCriteria(final Builder builder) {
this.contentType = builder.contentType;
this.field = builder.field;
this.value = builder.value;
this.language = builder.language;
this.site = builder.site;
this.variantName = builder.variantName;
this.isLive = builder.isLive;
}

/**
Expand All @@ -69,7 +78,8 @@ public Map<String, Object> toMap(){
FIELD_VALUE_ATTR, value.toString(),
LANGUAGE_ID_ATTR, language.getId(),
UNIQUE_PER_SITE_ATTR, isUniqueForSite(contentType.id(), field.variable()),
VARIANT_ATTR, variantName
VARIANT_ATTR, variantName,
LIVE_ATTR, isLive
));

if (site != null) {
Expand Down Expand Up @@ -151,6 +161,7 @@ public static class Builder {
private Language language;
private Host site;
private String variantName;
private boolean isLive;

public Builder setVariantName(final String variantName) {
this.variantName = variantName;
Expand Down Expand Up @@ -195,6 +206,10 @@ public UniqueFieldCriteria build(){
return new UniqueFieldCriteria(this);
}

public Builder setLive(boolean live) {
this.isLive = live;
return this;
}
}

}
Loading

0 comments on commit a343172

Please sign in to comment.