Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#30285 Fixing several unique fields database validation bugs #30872

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2487771
Cleaning up after delete a Contentlet
freddyDOTCMS Nov 22, 2024
cee645c
#30285 Clean uo unique_fields table after delete Field, Content Type …
freddyDOTCMS Nov 27, 2024
8c77f8f
Merge branch 'main' into issue-30285-Clean-up-the-unique_fields-table
freddyDOTCMS Nov 27, 2024
67548a0
Javadoc
freddyDOTCMS Nov 27, 2024
588d724
Merge branch 'main' into issue-30285-Clean-up-the-unique_fields-table
freddyDOTCMS Nov 27, 2024
46e9587
FIxing test
freddyDOTCMS Nov 27, 2024
6986b08
Merge branch 'issue-30285-Clean-up-the-unique_fields-table' of https:…
freddyDOTCMS Nov 27, 2024
7b6b1d7
shooting arrow
freddyDOTCMS Nov 28, 2024
f5745b8
Merge branch 'main' into issue-30285-Clean-up-the-unique_fields-table
freddyDOTCMS Nov 28, 2024
4728374
Fixing CDI
freddyDOTCMS Nov 28, 2024
9f0fdb5
Merge branch 'issue-30285-Clean-up-the-unique_fields-table' of https:…
freddyDOTCMS Nov 28, 2024
04335af
shooting arrow
freddyDOTCMS Nov 28, 2024
6e4f4fb
#30285 move weld initialization
fabrizzio-dotCMS Nov 28, 2024
c383ed1
#30285 Weld needs to initialize for UnitTest for injection is spreading
fabrizzio-dotCMS Nov 29, 2024
4616992
Merge branch 'main' of https://github.com/dotCMS/core into issue-3028…
freddyDOTCMS Nov 29, 2024
a448063
Merge remote-tracking branch 'origin/issue-30285-Clean-up-the-unique_…
freddyDOTCMS Nov 29, 2024
2dc6a3a
Fixing test
freddyDOTCMS Nov 29, 2024
44e8e93
Feedback
freddyDOTCMS Nov 29, 2024
39f313a
#30815 Populate the unique_fields table when the Dtabase validation i…
freddyDOTCMS Dec 4, 2024
58a3177
#30815 Popualte the unique_fields table after the Databse validation …
freddyDOTCMS Dec 5, 2024
d3e9a1d
merge
freddyDOTCMS Dec 5, 2024
e9b63b7
Javadoc
freddyDOTCMS Dec 5, 2024
927ee01
Merge branch 'main' into issue-30815-Populate-the-unique_fields-table…
freddyDOTCMS Dec 5, 2024
f26f07b
feedback
freddyDOTCMS Dec 5, 2024
1c2ab94
Merge branch 'issue-30815-Populate-the-unique_fields-table-when-the-D…
freddyDOTCMS Dec 5, 2024
87b8735
#30285 Fixing several unique fields database validation bugs
freddyDOTCMS Dec 6, 2024
f20c831
Javadoc
freddyDOTCMS Dec 6, 2024
d411e3c
merge
freddyDOTCMS Dec 6, 2024
f11c39e
feedback
freddyDOTCMS Dec 6, 2024
9555011
fixing test
freddyDOTCMS Dec 6, 2024
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 @@ -191,7 +191,7 @@ private void transactionalDelete(ContentType type) throws DotDataException {
Logger.error(ContentType.class, e.getMessage(), e);
throw new BaseRuntimeInternationalizationException(e);
}
HibernateUtil.addCommitListener(() -> localSystemEventsAPI.notify(new ContentTypeDeletedEvent(type.variable())));
HibernateUtil.addCommitListener(() -> localSystemEventsAPI.notify(new ContentTypeDeletedEvent(type)));
}

/**
Expand Down Expand Up @@ -341,7 +341,7 @@ private void disposeSourceThenFireContentDelete( final ContentType source, final

HibernateUtil.addCommitListener(() -> {
//Notify the system events API that the content type has been deleted, so it can take care of the WF clean up
localSystemEventsAPI.notify(new ContentTypeDeletedEvent(source.variable()));
localSystemEventsAPI.notify(new ContentTypeDeletedEvent(source));
//By default, the deletion process takes placed within job
Logger.info(this, String.format(" Content type (%s) will be deleted asynchronously using Quartz Job.", source.name()));
if(asyncDeleteWithJob) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ void broadcastEvents(final ContentType type, final User user) {
throw new BaseRuntimeInternationalizationException(e);
}
final LocalSystemEventsAPI localSystemEventsAPI = APILocator.getLocalSystemEventsAPI();
localSystemEventsAPI.notify(new ContentTypeDeletedEvent(type.variable()));
localSystemEventsAPI.notify(new ContentTypeDeletedEvent(type));
notifyContentTypeDestroyed(type);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,15 @@ default void cleanUp(final Field field) throws DotDataException {
//Default implementation do nothing
}

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
@Dependent
public class UniqueFieldsValidationInitializer implements DotInitializer {

private UniqueFieldDataBaseUtil uniqueFieldDataBaseUtil;
private DotDatabaseMetaData dotDatabaseMetaData;
private final UniqueFieldDataBaseUtil uniqueFieldDataBaseUtil;
private final DotDatabaseMetaData dotDatabaseMetaData;

@Inject
public UniqueFieldsValidationInitializer(final UniqueFieldDataBaseUtil uniqueFieldDataBaseUtil){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@
import java.util.stream.Collectors;

import static com.dotcms.content.elasticsearch.business.ESContentletAPIImpl.UNIQUE_PER_SITE_FIELD_VARIABLE_NAME;
import static com.dotcms.contenttype.business.uniquefields.extratable.UniqueFieldCriteria.CONTENTLET_IDS_ATTR;
import static com.dotcms.contenttype.business.uniquefields.extratable.UniqueFieldCriteria.UNIQUE_PER_SITE_ATTR;
import static com.dotcms.contenttype.business.uniquefields.extratable.UniqueFieldCriteria.*;
import static com.dotmarketing.util.Constants.DONT_RESPECT_FRONT_END_ROLES;

/**
Expand Down Expand Up @@ -111,10 +110,40 @@ private static boolean isContentletBeingUpdated(final Contentlet contentlet) {
*/
@SuppressWarnings("unchecked")
private void cleanUniqueFieldsUp(final Contentlet contentlet, final Field field) throws DotDataException {
Optional<Map<String, Object>> uniqueFieldOptional = uniqueFieldDataBaseUtil.get(contentlet, field);
List<Map<String, Object>> uniqueFields = uniqueFieldDataBaseUtil.get(contentlet, field);

if (UtilMethods.isSet(uniqueFields)) {
final List<Map<String, Object>> workingUniqueFields = uniqueFields.stream()
.filter(uniqueValue -> Boolean.FALSE.equals(getSupportingValues(uniqueValue).get("live")))
.collect(Collectors.toList());

if (!workingUniqueFields.isEmpty()) {
workingUniqueFields.forEach(uniqueField -> cleanUniqueFieldUp(contentlet.getIdentifier(), uniqueField));
} else {
uniqueFields.stream()
.filter(uniqueValue -> Boolean.TRUE.equals(getSupportingValues(uniqueValue).get("live")))
.limit(1)
.findFirst()
.ifPresent(uniqueFieldValue -> {
final Map<String, Object> supportingValues = getSupportingValues(uniqueFieldValue);
final String oldUniqueValue = supportingValues.get(FIELD_VALUE_ATTR).toString();
final String newUniqueValue = contentlet.getStringProperty(field.variable());

if (oldUniqueValue.equals(newUniqueValue)) {
cleanUniqueFieldUp(contentlet.getIdentifier(), uniqueFieldValue);
}
});
}


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

private static Map<String, Object> getSupportingValues(Map<String, Object> uniqueField) {
try {
return JsonUtil.getJsonFromString(uniqueField.get("supporting_values").toString());
} catch (IOException e) {
throw new DotRuntimeException(e);
}
}

Expand Down Expand Up @@ -271,12 +300,18 @@ public void cleanUp(final Field field) throws DotDataException {
uniqueFieldDataBaseUtil.delete(field);
}

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

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

if (hasUniqueField(contentlet.getContentType())) {
uniqueFieldDataBaseUtil.removeLive(contentlet);
uniqueFieldDataBaseUtil.setLive(contentlet, true);
}
} catch (DotDataException | DotSecurityException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.dotcms.business.WrapInTransaction;

import com.dotcms.contenttype.model.field.Field;
import com.dotcms.contenttype.model.type.ContentType;
import com.dotmarketing.common.db.DotConnect;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotRuntimeException;
Expand Down Expand Up @@ -62,7 +63,6 @@ public class UniqueFieldDataBaseUtil {
"WHERE supporting_values->'" + CONTENTLET_IDS_ATTR + "' @> ?::jsonb " +
"AND supporting_values->>'" + VARIANT_ATTR + "' = ? " +
"AND (supporting_values->>'"+ LANGUAGE_ID_ATTR + "')::INTEGER = ? " +
"AND (supporting_values->>'" + LIVE_ATTR + "')::BOOLEAN = ? " +
"AND supporting_values->>'" + FIELD_VARIABLE_NAME_ATTR + "' = ?";

private final static String DELETE_UNIQUE_FIELDS_BY_CONTENTLET = "DELETE FROM unique_fields " +
Expand All @@ -89,6 +89,10 @@ public class UniqueFieldDataBaseUtil {
private final static String DELETE_UNIQUE_FIELDS_BY_FIELD = "DELETE FROM unique_fields " +
"WHERE supporting_values->>'" + FIELD_VARIABLE_NAME_ATTR + "' = ?";

private final static String DELETE_UNIQUE_FIELDS_BY_CONTENT_TYPE = "DELETE FROM unique_fields " +
"WHERE supporting_values->>'" + CONTENT_TYPE_ID_ATTR + "' = ?";


private final static String POPULATE_UNIQUE_FIELDS_VALUES_QUERY = "INSERT INTO unique_fields (unique_key_val, supporting_values) " +
"SELECT encode(sha256(CONCAT(content_type_id, field_var_name, language_id, field_value, " +
" CASE WHEN uniquePerSite = 'true' THEN host_id ELSE '' END)::bytea), 'hex') as unique_key_val, " +
Expand Down Expand Up @@ -208,20 +212,13 @@ public void updateContentListWithHash(final String hash, final List<String> cont
* @throws DotDataException If an error occurs when interacting with the database.
*/
@CloseDBIfOpened
public Optional<Map<String, Object>> get(final Contentlet contentlet, final Field field) throws DotDataException {
try {
final List<Map<String, Object>> results = new DotConnect().setSQL(GET_UNIQUE_FIELDS_BY_CONTENTLET)
.addParam("\"" + contentlet.getIdentifier() + "\"")
.addParam(contentlet.getVariantId())
.addParam(contentlet.getLanguageId())
.addParam(contentlet.isLive())
.addParam(field.variable())
.loadObjectResults();

return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
} catch (DotSecurityException e) {
throw new DotRuntimeException(e);
}
public List<Map<String, Object>> get(final Contentlet contentlet, final Field field) throws DotDataException {
return new DotConnect().setSQL(GET_UNIQUE_FIELDS_BY_CONTENTLET)
.addParam("\"" + contentlet.getIdentifier() + "\"")
.addParam(contentlet.getVariantId())
.addParam(contentlet.getLanguageId())
.addParam(field.variable())
.loadObjectResults();
}

/**
Expand Down Expand Up @@ -275,10 +272,10 @@ private static String getUniqueRecalculationQuery(final boolean uniquePerSite) {
}

@CloseDBIfOpened
public List<Map<String, Object>> get(final String contentId, final long languegeId) throws DotDataException {
public List<Map<String, Object>> get(final String contentId, final long languageId) throws DotDataException {
return new DotConnect().setSQL(GET_UNIQUE_FIELDS_BY_CONTENTLET_AND_LANGUAGE)
.addParam("\"" + contentId + "\"")
.addParam(languegeId)
.addParam(languageId)
.loadObjectResults();
}

Expand Down Expand Up @@ -324,6 +321,19 @@ public void delete(final Field field) throws DotDataException {
.loadObjectResults();
}

/**
* Delete all the unique values for a {@link ContentType}
*
* @param contentType
* @throws DotDataException
*/
@WrapInTransaction
public void delete(final ContentType contentType) throws DotDataException {
new DotConnect().setSQL(DELETE_UNIQUE_FIELDS_BY_CONTENT_TYPE)
.addParam(contentType.id())
.loadObjectResults();
}

/**
* Set the supporting_value->live attribute to true to any register with the same Content's id, variant and language
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.dotcms.contenttype.business.uniquefields.extratable;

import com.dotcms.contenttype.business.uniquefields.UniqueFieldValidationStrategyResolver;
import com.dotcms.contenttype.model.event.ContentTypeDeletedEvent;
import com.dotcms.contenttype.model.field.Field;
import com.dotcms.contenttype.model.field.event.FieldDeletedEvent;
import com.dotcms.contenttype.model.type.ContentType;
Expand Down Expand Up @@ -68,7 +69,7 @@ public void cleanUpAfterDeleteContentlet(final DeleteContentletVersionInfoEvent
final ContentType contentType = APILocator.getContentTypeAPI(APILocator.systemUser())
.find(contentlet.getContentTypeId());

boolean hasUniqueField = contentType.fields().stream().anyMatch(Field::unique);
boolean hasUniqueField = hasUniqueField(contentType);

if (hasUniqueField) {
uniqueFieldValidationStrategyResolver.get().cleanUp(contentlet, event.isDeleteAllVariant());
Expand All @@ -78,8 +79,12 @@ public void cleanUpAfterDeleteContentlet(final DeleteContentletVersionInfoEvent
}
}

private static boolean hasUniqueField(ContentType contentType) {
return contentType.fields().stream().anyMatch(Field::unique);
}

/**
* Listen when a Field is deleted and if this ia a Unique Field then delete all the register in
* Listen when a Field is deleted and if this is a Unique Field then delete all the register in
* unique_fields table for this Field
*
* @param event
Expand All @@ -94,4 +99,23 @@ public void cleanUpAfterDeleteUniqueField(final FieldDeletedEvent event) throws
uniqueFieldValidationStrategyResolver.get().cleanUp(deletedField);
}
}

/**
* Listen when a {@link ContentType} is deleted and if this has at least one Unique Field then delete all the register in
* unique_fields table for this {@link ContentType}
*
* @param event
*
* @throws DotDataException
*/
@Subscriber
public void cleanUpAfterDeleteContentType(final ContentTypeDeletedEvent contentTypeDeletedEvent) throws DotDataException {
final ContentType contentType = contentTypeDeletedEvent.getContentType();

boolean hasUniqueField = hasUniqueField(contentType);

if (hasUniqueField) {
uniqueFieldValidationStrategyResolver.get().cleanUp(contentType);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package com.dotcms.contenttype.model.event;

import com.dotcms.contenttype.model.type.ContentType;

public class ContentTypeDeletedEvent {

private final String contentTypeVar;
private final ContentType contentType;

public ContentTypeDeletedEvent(final String contentTypeVar) {
this.contentTypeVar = contentTypeVar;
public ContentTypeDeletedEvent(final ContentType contentType) {
this.contentType = contentType;
}

public String getContentTypeVar() {
return contentTypeVar;
return contentType.variable();
}

public ContentType getContentType() {
return contentType;
}
}
Loading
Loading