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

Issue 30277 create api factory methods to insert in the unique fields table #30466

Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e0e83bd
bug(apps) fixing link to dotAI portlet (#29556)
wezell Oct 9, 2024
5fff6b7
#30277 API/Factory methods to insert in the unique_fields table
freddyDOTCMS Oct 11, 2024
8a61eaa
#30277 Javadoc
freddyDOTCMS Oct 11, 2024
59ce98b
Merge branch 'main' into issue-30277-Create-API-Factory-methods-to-in…
freddyDOTCMS Oct 11, 2024
4fcf5a7
#30306 Fixing test
freddyDOTCMS Oct 14, 2024
64d7a0e
Merge branch 'main' into issue-30277-Create-API-Factory-methods-to-in…
freddyDOTCMS Oct 14, 2024
23b73e5
Adding MainSuite
freddyDOTCMS Oct 14, 2024
28e0e04
Merge branch 'issue-30277-Create-API-Factory-methods-to-insert-in-the…
freddyDOTCMS Oct 14, 2024
5d0574a
Missing changes
freddyDOTCMS Oct 14, 2024
0403905
Merge branch 'main' into issue-30277-Create-API-Factory-methods-to-in…
freddyDOTCMS Oct 15, 2024
f17ca69
Merge branch 'main' into issue-30277-Create-API-Factory-methods-to-in…
freddyDOTCMS Oct 15, 2024
504efaa
#30277 Feedback
freddyDOTCMS Oct 15, 2024
0f40ac6
Merge branch 'issue-30277-Create-API-Factory-methods-to-insert-in-the…
freddyDOTCMS Oct 15, 2024
bc8ee9f
#30277 Feedback
freddyDOTCMS Oct 15, 2024
a19069c
#302077 Including CDI
freddyDOTCMS Oct 15, 2024
3c7fa5c
#302077 Renamong to API
freddyDOTCMS Oct 15, 2024
0aa9ced
feedback
freddyDOTCMS Oct 16, 2024
abb53a6
Merge branch 'main' into issue-30277-Create-API-Factory-methods-to-in…
freddyDOTCMS Oct 16, 2024
93f59f9
#30277 Create Strategy classes
freddyDOTCMS Oct 21, 2024
126c4ee
#30277 Validate when a Contentlet is Updated o saved
freddyDOTCMS Oct 21, 2024
df2e213
merge
freddyDOTCMS Oct 21, 2024
4069133
#30279 Fixing test
freddyDOTCMS Oct 25, 2024
7330a78
#30279 Fixing test
freddyDOTCMS Oct 25, 2024
9f50a0f
Merge remote-tracking branch 'origin/master' into issue-30277-Create-…
freddyDOTCMS Oct 25, 2024
5bdcb6e
merge
freddyDOTCMS Oct 25, 2024
1f4a1b1
Fixing test
freddyDOTCMS Oct 28, 2024
7381cc9
Merge branch 'main' into issue-30277-Create-API-Factory-methods-to-in…
freddyDOTCMS Oct 28, 2024
0d0fe1b
Removing unneeded changes
freddyDOTCMS Oct 28, 2024
555b48d
Merge branch 'issue-30277-Create-API-Factory-methods-to-insert-in-the…
freddyDOTCMS Oct 28, 2024
ef18a38
Fixing test
freddyDOTCMS Oct 28, 2024
127a558
Fixing test
freddyDOTCMS Oct 28, 2024
b286781
Fixing test
freddyDOTCMS Oct 28, 2024
16b4d06
Feedback
freddyDOTCMS Oct 28, 2024
c5c8cfe
feedback
freddyDOTCMS Oct 29, 2024
2739eee
Merge branch 'main' into issue-30277-Create-API-Factory-methods-to-in…
freddyDOTCMS Oct 29, 2024
d029714
feedback
freddyDOTCMS Oct 29, 2024
1dc8a23
Merge branch 'issue-30277-Create-API-Factory-methods-to-insert-in-the…
freddyDOTCMS Oct 29, 2024
f2996ec
feedback
freddyDOTCMS Oct 30, 2024
b01fc21
Merge branch 'main' into issue-30277-Create-API-Factory-methods-to-in…
freddyDOTCMS Oct 30, 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 @@ -12,11 +12,9 @@
import com.dotcms.content.elasticsearch.business.event.ContentletPublishEvent;
import com.dotcms.content.elasticsearch.business.field.FieldHandlerStrategyFactory;
import com.dotcms.content.elasticsearch.constants.ESMappingConstants;
import com.dotcms.content.elasticsearch.util.ESUtils;
import com.dotcms.content.elasticsearch.util.PaginationUtil;
import com.dotcms.contenttype.business.BaseTypeToContentTypeStrategy;
import com.dotcms.contenttype.business.BaseTypeToContentTypeStrategyResolver;
import com.dotcms.contenttype.business.ContentTypeAPI;
import com.dotcms.contenttype.business.*;
import com.dotcms.contenttype.business.uniquefields.UniqueFieldValidationStrategyResolver;
import com.dotcms.contenttype.exception.NotFoundInDbException;
import com.dotcms.contenttype.model.field.BinaryField;
import com.dotcms.contenttype.model.field.CategoryField;
Expand Down Expand Up @@ -236,6 +234,8 @@
*/
public class ESContentletAPIImpl implements ContentletAPI {

private static Lazy<Boolean> ENABLED_UNIQUE_FIELDS_DATA_BASE_VALIDATION = Lazy.of(() ->
jcastro-dotcms marked this conversation as resolved.
Show resolved Hide resolved
Config.getBooleanProperty("ENABLED_UNIQUE_FIELDS_DATA_BASE_VALIDATION", false));
dsilvam marked this conversation as resolved.
Show resolved Hide resolved
private static final String CAN_T_CHANGE_STATE_OF_CHECKED_OUT_CONTENT = "Can't change state of checked out content or where inode is not set. Use Search or Find then use method";
private static final String CANT_GET_LOCK_ON_CONTENT = "Only the CMS Admin or the user who locked the contentlet can lock/unlock it";
private static final String FAILED_TO_DELETE_UNARCHIVED_CONTENT = "Failed to delete unarchived content. Content must be archived first before it can be deleted.";
Expand Down Expand Up @@ -277,6 +277,7 @@ public class ESContentletAPIImpl implements ContentletAPI {
private final BaseTypeToContentTypeStrategyResolver baseTypeToContentTypeStrategyResolver =
BaseTypeToContentTypeStrategyResolver.getInstance();


public enum QueryType {
search, suggest, moreLike, Facets
}
Expand All @@ -286,6 +287,15 @@ public enum QueryType {
private static final Supplier<String> ND_SUPPLIER = () -> "N/D";
private final ElasticReadOnlyCommand elasticReadOnlyCommand;

public static boolean getEnabledUniqueFieldsDataBaseValidation() {
return ENABLED_UNIQUE_FIELDS_DATA_BASE_VALIDATION.get();
}

@VisibleForTesting
public static void setEnabledUniqueFieldsDataBaseValidation(final boolean newValue) {
ENABLED_UNIQUE_FIELDS_DATA_BASE_VALIDATION = Lazy.of(() -> newValue);
}

/**
* Default class constructor.
*/
Expand All @@ -311,7 +321,6 @@ public ESContentletAPIImpl() {
this(ElasticReadOnlyCommand.getInstance());
}


@Override
public SearchResponse esSearchRaw(String esQuery, boolean live, User user,
boolean respectFrontendRoles) throws DotSecurityException, DotDataException {
Expand Down Expand Up @@ -5516,6 +5525,11 @@ private Contentlet internalCheckin(Contentlet contentlet,
contentlet = contentFactory.save(contentlet);
}

if (hasUniqueField(contentType)) {
UniqueFieldValidationStrategyResolver.INSTANCE.get().afterSaved(contentlet, isNewContent);
}


contentlet.setIndexPolicy(indexPolicy);
contentlet.setIndexPolicyDependencies(indexPolicyDependencies);

Expand Down Expand Up @@ -5645,6 +5659,10 @@ private Contentlet internalCheckin(Contentlet contentlet,
return contentlet;
}

private static boolean hasUniqueField(ContentType contentType) {
return contentType.fields().stream().anyMatch(field -> field.unique());
}

private boolean shouldRemoveOldHostCache(Contentlet contentlet, String oldHostId) {
return contentlet.getBoolProperty(Contentlet.TO_BE_PUBLISH) &&
contentlet.isVanityUrl() &&
Expand Down Expand Up @@ -7632,98 +7650,16 @@ public void validateContentlet(final Contentlet contentlet, final List<Category>

// validate unique
if (field.isUnique()) {
final boolean isDataTypeNumber =
field.getDataType().contains(DataTypes.INTEGER.toString())
|| field.getDataType().contains(DataTypes.FLOAT.toString());
try {
final StringBuilder buffy = new StringBuilder(UUIDGenerator.generateUuid());
buffy.append(" +structureInode:" + contentlet.getContentTypeId());
if (UtilMethods.isSet(contentlet.getIdentifier())) {
buffy.append(" -(identifier:" + contentlet.getIdentifier() + ")");
}

buffy.append(" +languageId:" + contentlet.getLanguageId());

if (getUniquePerSiteConfig(field)) {
if (!UtilMethods.isSet(contentlet.getHost())) {
populateHost(contentlet);
}

buffy.append(" +conHost:" + contentlet.getHost());
}

buffy.append(" +").append(contentlet.getContentType().variable())
.append(StringPool.PERIOD)
.append(field.getVelocityVarName()).append(ESUtils.SHA_256)
.append(StringPool.COLON)
.append(ESUtils.sha256(contentlet.getContentType().variable()
+ StringPool.PERIOD + field.getVelocityVarName(), fieldValue,
contentlet.getLanguageId()));

final List<ContentletSearch> contentlets = new ArrayList<>();
try {
contentlets.addAll(
searchIndex(buffy.toString() + " +working:true", -1, 0, "inode",
APILocator.getUserAPI().getSystemUser(), false));
contentlets.addAll(
searchIndex(buffy.toString() + " +live:true", -1, 0, "inode",
APILocator.getUserAPI().getSystemUser(), false));
} catch (final Exception e) {
final String errorMsg =
"Unique field [" + field.getVelocityVarName() + "] with value '" +
fieldValue + "' could not be validated: " + e.getMessage();
Logger.warn(this, errorMsg, e);
throw new DotContentletValidationException(errorMsg, e);
}
int size = contentlets.size();
if (size > 0 && !hasError) {
boolean unique = true;
for (final ContentletSearch contentletSearch : contentlets) {
final Contentlet uniqueContent = contentFactory.find(
contentletSearch.getInode());
if (null == uniqueContent) {
final String errorMsg = String.format(
"Unique field [%s] could not be validated, as " +
"unique content Inode '%s' was not found. ES Index might need to be reindexed.",
field.getVelocityVarName(), contentletSearch.getInode());
Logger.warn(this, errorMsg);
throw new DotContentletValidationException(errorMsg);
}
final Map<String, Object> uniqueContentMap = uniqueContent.getMap();
final Object obj = uniqueContentMap.get(field.getVelocityVarName());
if ((isDataTypeNumber && Objects.equals(fieldValue, obj)) ||
(!isDataTypeNumber && ((String) obj).equalsIgnoreCase(
((String) fieldValue)))) {
unique = false;
break;
}

}
if (!unique) {
if (UtilMethods.isSet(contentlet.getIdentifier())) {
Iterator<ContentletSearch> contentletsIter = contentlets.iterator();
while (contentletsIter.hasNext()) {
ContentletSearch cont = contentletsIter.next();
if (!contentlet.getIdentifier()
.equalsIgnoreCase(cont.getIdentifier())) {
cve.addUniqueField(field);
hasError = true;
Logger.warn(this,
getUniqueFieldErrorMessage(field, fieldValue,
cont));
break;
}
}
} else {
cve.addUniqueField(field);
hasError = true;
Logger.warn(this, getUniqueFieldErrorMessage(field, fieldValue,
contentlets.get(0)));
break;
}
}
}
} catch (final DotDataException | DotSecurityException e) {
try {
UniqueFieldValidationStrategyResolver.INSTANCE.get().validate(contentlet,
LegacyFieldTransformer.from(field));
} catch (UniqueFieldValueDuplicatedException e) {
cve.addUniqueField(field);
hasError = true;
Logger.warn(this, getUniqueFieldErrorMessage(field, fieldValue,
UtilMethods.isSet(e.getContentlets()) ? e.getContentlets().get(0) : "UnKnow"));
freddyDOTCMS marked this conversation as resolved.
Show resolved Hide resolved
} catch (DotDataException | DotSecurityException e) {
Logger.warn(this, "Unable to get contentlets for Content Type: "
+ contentlet.getContentType().name(), e);
}
Expand Down Expand Up @@ -7799,11 +7735,11 @@ private boolean isIgnorableField(final com.dotcms.contenttype.model.field.Field
}

private String getUniqueFieldErrorMessage(final Field field, final Object fieldValue,
final ContentletSearch contentletSearch) {
final String contentletID) {

return String.format(
"Value of Field [%s] must be unique. Contents having the same value (%s): %s",
field.getVelocityVarName(), fieldValue, contentletSearch.getIdentifier());
field.getVelocityVarName(), fieldValue, contentletID);
}

private boolean getUniquePerSiteConfig(final Field field) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.dotcms.contenttype.business;

import java.util.List;

/**
* Throw if try to insert a duplicated register in unique_fiedls table
*/
public class UniqueFieldValueDuplicatedException extends Exception{

private List<String> contentletsIDS;

public UniqueFieldValueDuplicatedException(String message) {
super(message);
}

public UniqueFieldValueDuplicatedException(String message, List<String> contentletsIDS) {
super(message);
this.contentletsIDS = contentletsIDS;
}

public List<String> getContentlets() {
return contentletsIDS;
}
}
Loading