From 3977a3bf5c1c4d38aab663a0df05c944bdae74d6 Mon Sep 17 00:00:00 2001 From: Jose Castro Date: Tue, 24 Sep 2024 08:34:03 -0600 Subject: [PATCH] chore(Content) fixes #23184 : Improve the field validation exception log (#30100) ### Proposed Changes * Improving the amount of information printed by the Contentlet Validation process. * Some methods in the call stack were updated so they don't have to print the error message again. And now, we're also logging the name and ID of the Workflow Action that triggered the validation. * The error data was not reduced at all, but just improved they way it's printed to make it easier to read and take less space in the log. When validating a text field in a Contentlet, the error logging went from this: ```log [20/09/24 19:19:36:147 GMT] INFO util.ActivityLogger: class com.dotcms.content.elasticsearch.business.ESContentletAPIImpl: system : Contentlet to be saved: , StartDate: N/D; EndDate: N/D; User:dotcms.org.1; ContentIdentifier: [20/09/24 19:19:36:148 GMT] WARN business.ESContentletAPIImpl: Field [title] is required [20/09/24 19:19:36:149 GMT] WARN business.ESContentletAPIImpl: Contentlet with id:`Unknown/New` and title:`` has invalid / missing field(s). List of non valid fields REQUIRED: title/Title [20/09/24 19:19:36:149 GMT] WARN business.ESContentletAPIImpl: Contentlet with id:`Unknown/New` and title:`` has invalid / missing field(s). List of non valid fields REQUIRED: title/Title [20/09/24 19:19:36:149 GMT] WARN business.ESContentletAPIImpl: com.dotcms.content.elasticsearch.business.ESContentletAPIImpl.validateContentlet(ESContentletAPIImpl.java:7376) [20/09/24 19:19:36:150 GMT] WARN business.ESContentletAPIImpl: Contentlet with id:`Unknown/New` and title:`` has invalid / missing field(s). List of non valid fields REQUIRED: title/Title com.dotmarketing.portlets.contentlet.business.DotContentletValidationException: Contentlet with id:`Unknown/New` and title:`` has invalid / missing field(s). List of non valid fields REQUIRED: title/Title at com.dotcms.content.elasticsearch.business.ESContentletAPIImpl.validateContentlet(ESContentletAPIImpl.java:7376) ~[?:?] at com.dotcms.content.elasticsearch.business.ESContentletAPIImpl.validateContentlet(ESContentletAPIImpl.java:8115) ~[?:?] at com.dotmarketing.portlets.contentlet.business.ContentletAPIInterceptor.validateContentlet(ContentletAPIInterceptor.java:2271) ~[?:?] at com.dotcms.content.elasticsearch.business.ESContentletAPIImpl.internalCheckin(ESContentletAPIImpl.java:5382) ~[?:?] at com.dotcms.content.elasticsearch.business.ESContentletAPIImpl.lambda$checkin$59(ESContentletAPIImpl.java:5001) ~[?:?] at com.dotcms.concurrent.lock.StripedLockImpl.tryLock(StripedLockImpl.java:100) ~[?:?] at com.dotcms.concurrent.lock.StripedLockImpl.tryLock(StripedLockImpl.java:55) ~[?:?] at com.dotcms.concurrent.lock.IdentifierStripedLock.tryLock(IdentifierStripedLock.java:16) ~[?:?] at com.dotcms.content.elasticsearch.business.ESContentletAPIImpl.checkin(ESContentletAPIImpl.java:5000) ~[?:?] at com.dotcms.content.elasticsearch.business.ESContentletAPIImpl.checkin(ESContentletAPIImpl.java:9908) ~[?:?] at com.dotcms.content.elasticsearch.business.ESContentletAPIImpl.checkin(ESContentletAPIImpl.java:9971) ~[?:?] at com.dotmarketing.portlets.contentlet.business.ContentletAPIInterceptor.checkin(ContentletAPIInterceptor.java:195) ~[?:?] at com.dotmarketing.portlets.workflows.actionlet.SaveContentActionlet.executeAction(SaveContentActionlet.java:79) ~[?:?] at com.dotmarketing.portlets.workflows.business.WorkflowAPIImpl.fireWorkflowPostCheckin(WorkflowAPIImpl.java:2424) ~[?:?] at com.dotmarketing.portlets.workflows.business.WorkflowAPIImpl.lambda$fireContentWorkflow$73(WorkflowAPIImpl.java:3382) ~[?:?] at com.dotcms.util.ThreadContextUtil.wrapVoidNoReindex(ThreadContextUtil.java:85) ~[?:?] at com.dotmarketing.portlets.workflows.business.WorkflowAPIImpl.fireContentWorkflow(WorkflowAPIImpl.java:3382) ~[?:?] at com.dotmarketing.portlets.contentlet.business.web.ContentletWebAPIImpl.saveWebAsset(ContentletWebAPIImpl.java:538) ~[?:?] at com.dotmarketing.portlets.contentlet.business.web.ContentletWebAPIImpl.saveContent(ContentletWebAPIImpl.java:161) ~[?:?] at com.dotmarketing.portlets.contentlet.ajax.ContentletAjax.saveContent(ContentletAjax.java:1987) ~[?:?] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[?:?] at com.dotcms.repackage.org.directwebremoting.impl.CreatorModule$1.doFilter(CreatorModule.java:229) ~[?:?] at com.dotcms.repackage.org.directwebremoting.impl.CreatorModule.executeMethod(CreatorModule.java:241) ~[?:?] at com.dotcms.repackage.org.directwebremoting.impl.DefaultRemoter.execute(DefaultRemoter.java:379) ~[?:?] at com.dotcms.repackage.org.directwebremoting.impl.DefaultRemoter.execute(DefaultRemoter.java:332) ~[?:?] at com.dotcms.repackage.org.directwebremoting.dwrp.BaseCallHandler.handle(BaseCallHandler.java:104) ~[?:?] at com.dotcms.repackage.org.directwebremoting.servlet.UrlProcessor.handle(UrlProcessor.java:120) ~[?:?] at com.dotcms.repackage.org.directwebremoting.servlet.DwrServlet.doPost(DwrServlet.java:141) ~[?:?] at com.dotmarketing.servlets.DwrWrapperServlet.doPost(DwrWrapperServlet.java:60) ~[?:?] at javax.servlet.http.HttpServlet.service(HttpServlet.java:555) ~[?:?] at javax.servlet.http.HttpServlet.service(HttpServlet.java:623) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[?:?] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[?:?] at com.dotmarketing.filters.CMSFilter.doFilterInternal(CMSFilter.java:181) ~[?:?] at com.dotmarketing.filters.CMSFilter.doFilter(CMSFilter.java:61) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[?:?] at com.dotcms.filters.interceptor.AbstractWebInterceptorSupportFilter.doFilter(AbstractWebInterceptorSupportFilter.java:90) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[?:?] at com.dotcms.filters.interceptor.AbstractWebInterceptorSupportFilter.doFilter(AbstractWebInterceptorSupportFilter.java:90) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[?:?] at com.dotcms.visitor.filter.servlet.VisitorFilter.doFilter(VisitorFilter.java:81) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[?:?] at com.dotcms.vanityurl.filters.VanityURLFilter.doFilter(VanityURLFilter.java:107) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[?:?] at org.tuckey.web.filters.urlrewrite.RuleChain.handleRewrite(RuleChain.java:176) ~[?:?] at org.tuckey.web.filters.urlrewrite.RuleChain.doRules(RuleChain.java:145) ~[?:?] at org.tuckey.web.filters.urlrewrite.UrlRewriter.processRequest(UrlRewriter.java:92) ~[?:?] at org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:389) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[?:?] at com.dotmarketing.filters.TimeMachineFilter.doFilter(TimeMachineFilter.java:137) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[?:?] at com.dotmarketing.filters.ThreadNameFilter.doFilter(ThreadNameFilter.java:88) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[?:?] at com.dotmarketing.filters.CookiesFilter.doFilter(CookiesFilter.java:53) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[?:?] at com.dotmarketing.filters.CharsetEncodingFilter.doFilter(CharsetEncodingFilter.java:99) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[?:?] at com.dotcms.filters.interceptor.AbstractWebInterceptorSupportFilter.doFilter(AbstractWebInterceptorSupportFilter.java:90) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[?:?] at org.apache.catalina.filters.HttpHeaderSecurityFilter.doFilter(HttpHeaderSecurityFilter.java:129) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[?:?] at com.dotcms.filters.NormalizationFilter.doFilter(NormalizationFilter.java:89) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[?:?] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[?:?] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:168) ~[?:?] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[?:?] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481) ~[?:?] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130) ~[?:?] at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:670) ~[?:?] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[?:?] at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:765) ~[?:?] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[?:?] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) ~[?:?] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390) ~[?:?] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[?:?] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:928) ~[?:?] at org.apache.tomcat.util.net.Nio2Endpoint$SocketProcessor.doRun(Nio2Endpoint.java:1732) ~[?:?] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[?:?] at org.apache.tomcat.util.net.AbstractEndpoint.processSocket(AbstractEndpoint.java:1295) ~[?:?] at org.apache.tomcat.util.net.SecureNio2Channel$HandshakeWriteCompletionHandler.completed(SecureNio2Channel.java:121) ~[?:?] at org.apache.tomcat.util.net.SecureNio2Channel$HandshakeWriteCompletionHandler.completed(SecureNio2Channel.java:114) ~[?:?] at java.base/sun.nio.ch.Invoker.invokeUnchecked(Invoker.java:127) ~[?:?] at java.base/sun.nio.ch.Invoker.invokeDirect(Invoker.java:158) ~[?:?] at java.base/sun.nio.ch.UnixAsynchronousSocketChannelImpl.implWrite(UnixAsynchronousSocketChannelImpl.java:746) ~[?:?] at java.base/sun.nio.ch.AsynchronousSocketChannelImpl.write(AsynchronousSocketChannelImpl.java:383) ~[?:?] at java.base/sun.nio.ch.AsynchronousSocketChannelImpl.write(AsynchronousSocketChannelImpl.java:400) ~[?:?] at org.apache.tomcat.util.net.SecureNio2Channel.handshakeInternal(SecureNio2Channel.java:302) ~[?:?] at org.apache.tomcat.util.net.SecureNio2Channel.handshake(SecureNio2Channel.java:222) ~[?:?] at org.apache.tomcat.util.net.Nio2Endpoint$SocketProcessor.doRun(Nio2Endpoint.java:1709) ~[?:?] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[?:?] at org.apache.tomcat.util.net.AbstractEndpoint.processSocket(AbstractEndpoint.java:1295) ~[?:?] at org.apache.tomcat.util.net.SecureNio2Channel$HandshakeReadCompletionHandler.completed(SecureNio2Channel.java:104) ~[?:?] at org.apache.tomcat.util.net.SecureNio2Channel$HandshakeReadCompletionHandler.completed(SecureNio2Channel.java:97) ~[?:?] at java.base/sun.nio.ch.Invoker.invokeUnchecked(Invoker.java:127) ~[?:?] at java.base/sun.nio.ch.UnixAsynchronousSocketChannelImpl.finishRead(UnixAsynchronousSocketChannelImpl.java:439) ~[?:?] at java.base/sun.nio.ch.UnixAsynchronousSocketChannelImpl.finish(UnixAsynchronousSocketChannelImpl.java:191) ~[?:?] at java.base/sun.nio.ch.UnixAsynchronousSocketChannelImpl.onEvent(UnixAsynchronousSocketChannelImpl.java:213) ~[?:?] at java.base/sun.nio.ch.EPollPort$EventHandlerTask.run(EPollPort.java:306) ~[?:?] at java.base/sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:112) ~[?:?] at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[?:?] at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[?:?] at java.base/java.lang.Thread.run(Thread.java:829) [?:?] [20/09/24 19:19:36:150 GMT] WARN actionlet.SaveContentActionlet: Contentlet with id:`Unknown/New` and title:`` has invalid / missing field(s). List of non valid fields REQUIRED: title/Title [20/09/24 19:19:36:150 GMT] WARN actionlet.SaveContentActionlet: Contentlet with id:`Unknown/New` and title:`` has invalid / missing field(s). List of non valid fields REQUIRED: title/Title [20/09/24 19:19:36:150 GMT] WARN actionlet.SaveContentActionlet: com.dotcms.content.elasticsearch.business.ESContentletAPIImpl.validateContentlet(ESContentletAPIImpl.java:7376) [20/09/24 19:19:36:151 GMT] ERROR business.WorkflowAPIImpl: There was an unexpected error: Contentlet with id:`Unknown/New` and title:`` has invalid / missing field(s). List of non valid fields REQUIRED: title/Title [20/09/24 19:19:36:151 GMT] WARN web.ContentletWebAPIImpl: com.dotmarketing.exception.DotRuntimeException: Contentlet with id:`Unknown/New` and title:`` has invalid / missing field(s). List of non valid fields REQUIRED: title/Title [20/09/24 19:19:36:151 GMT] WARN web.ContentletWebAPIImpl: Contentlet with id:`Unknown/New` and title:`` has invalid / missing field(s). List of non valid fields REQUIRED: title/Title ``` To this: ```log 13:27:43.015 INFO util.ActivityLogger - class com.dotcms.content.elasticsearch.business.ESContentletAPIImpl: system : Contentlet to be saved: , StartDate: N/D; EndDate: N/D; User:dotcms.org.1; ContentIdentifier: 13:27:43.016 WARN business.ESContentletAPIImpl - Field [title] is required 13:27:43.016 WARN business.ESContentletAPIImpl - Contentlet with ID 'Unknown/New' [''] has invalid/missing field(s). - Fields: [REQUIRED]: Title (title) 13:27:43.016 WARN business.ESContentletAPIImpl - com.dotcms.content.elasticsearch.business.ESContentletAPIImpl.validateContentlet(ESContentletAPIImpl.java:7382) 13:27:43.016 ERROR business.WorkflowAPIImpl - Failed to fire Workflow Action 'Publish' [b9d89c80-3d88-4311-8365-187323c96436]: Contentlet with ID 'Unknown/New' [''] has invalid/missing field(s). - Fields: [REQUIRED]: Title (title) ``` ``` --- .../business/ESContentletAPIImpl.java | 125 ++++++++++-------- .../DotContentletValidationException.java | 88 ++++++------ .../business/web/ContentletWebAPIImpl.java | 18 ++- .../actionlet/SaveContentActionlet.java | 21 +-- .../workflows/business/WorkflowAPIImpl.java | 32 +++-- .../java/com/dotmarketing/util/Logger.java | 48 +++---- .../business/ESContentletAPIImplTest.java | 47 ++++--- .../ContentResourceV1.postman_collection.json | 61 ++------- 8 files changed, 220 insertions(+), 220 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java b/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java index 56280884e8fc..8253b98a295e 100644 --- a/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java +++ b/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java @@ -1,11 +1,5 @@ package com.dotcms.content.elasticsearch.business; -import static com.dotcms.exception.ExceptionUtil.bubbleUpException; -import static com.dotcms.exception.ExceptionUtil.getLocalizedMessageOrDefault; -import static com.dotmarketing.business.PermissionAPI.PERMISSION_CAN_ADD_CHILDREN; -import static com.dotmarketing.portlets.contentlet.model.Contentlet.URL_MAP_FOR_CONTENT_KEY; -import static com.dotmarketing.portlets.personas.business.PersonaAPI.DEFAULT_PERSONA_NAME_KEY; - import com.dotcms.api.system.event.ContentletSystemEventUtil; import com.dotcms.api.web.HttpServletRequestThreadLocal; import com.dotcms.business.CloseDBIfOpened; @@ -26,12 +20,16 @@ import com.dotcms.contenttype.exception.NotFoundInDbException; import com.dotcms.contenttype.model.field.BinaryField; import com.dotcms.contenttype.model.field.CategoryField; +import com.dotcms.contenttype.model.field.ColumnField; import com.dotcms.contenttype.model.field.ConstantField; import com.dotcms.contenttype.model.field.DataTypes; import com.dotcms.contenttype.model.field.FieldVariable; import com.dotcms.contenttype.model.field.HostFolderField; import com.dotcms.contenttype.model.field.JSONField; +import com.dotcms.contenttype.model.field.LineDividerField; import com.dotcms.contenttype.model.field.RelationshipField; +import com.dotcms.contenttype.model.field.RowField; +import com.dotcms.contenttype.model.field.TabDividerField; import com.dotcms.contenttype.model.field.TagField; import com.dotcms.contenttype.model.type.BaseContentType; import com.dotcms.contenttype.model.type.ContentType; @@ -129,7 +127,6 @@ import com.dotmarketing.portlets.languagesmanager.model.Language; import com.dotmarketing.portlets.links.model.Link; import com.dotmarketing.portlets.personas.model.Persona; -import com.dotmarketing.portlets.structure.business.FieldAPI; import com.dotmarketing.portlets.structure.model.ContentletRelationships; import com.dotmarketing.portlets.structure.model.ContentletRelationships.ContentletRelationshipRecords; import com.dotmarketing.portlets.structure.model.Field; @@ -184,6 +181,18 @@ import io.vavr.Lazy; import io.vavr.Tuple2; import io.vavr.control.Try; +import org.apache.commons.beanutils.BeanUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.elasticsearch.action.search.SearchPhaseExecutionException; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.SearchHits; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.activation.MimeType; +import javax.servlet.http.HttpServletRequest; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; @@ -211,17 +220,12 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.activation.MimeType; -import javax.servlet.http.HttpServletRequest; -import org.apache.commons.beanutils.BeanUtils; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; -import org.elasticsearch.action.search.SearchPhaseExecutionException; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.SearchHits; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; + +import static com.dotcms.exception.ExceptionUtil.bubbleUpException; +import static com.dotcms.exception.ExceptionUtil.getLocalizedMessageOrDefault; +import static com.dotmarketing.business.PermissionAPI.PERMISSION_CAN_ADD_CHILDREN; +import static com.dotmarketing.portlets.contentlet.model.Contentlet.URL_MAP_FOR_CONTENT_KEY; +import static com.dotmarketing.portlets.personas.business.PersonaAPI.DEFAULT_PERSONA_NAME_KEY; /** * Implementation class for the {@link ContentletAPI} interface. @@ -245,7 +249,6 @@ public class ESContentletAPIImpl implements ContentletAPI { private final PermissionAPI permissionAPI; private final CategoryAPI categoryAPI; private final RelationshipAPI relationshipAPI; - private final FieldAPI fieldAPI; private final LanguageAPI languageAPI; private final ReindexQueueAPI reindexQueueAPI; private final TagAPI tagAPI; @@ -281,7 +284,7 @@ public enum QueryType { ; private static final Supplier ND_SUPPLIER = () -> "N/D"; - private ElasticReadOnlyCommand elasticReadOnlyCommand; + private final ElasticReadOnlyCommand elasticReadOnlyCommand; /** * Default class constructor. @@ -289,7 +292,6 @@ public enum QueryType { @VisibleForTesting public ESContentletAPIImpl(final ElasticReadOnlyCommand readOnlyCommand) { indexAPI = new ContentletIndexAPIImpl(); - fieldAPI = APILocator.getFieldAPI(); contentFactory = new ESContentFactoryImpl(); permissionAPI = APILocator.getPermissionAPI(); categoryAPI = APILocator.getCategoryAPI(); @@ -4988,9 +4990,8 @@ private Contentlet checkin(final Contentlet contentletIn, generateSystemEvent); if (workflowContentletOpt.isPresent()) { - Logger.info(this, - "A Workflow has been ran instead of checkin the contentlet: " + + "A Workflow has been run instead of checking in Contentlet with ID: " + workflowContentletOpt.get().getIdentifier()); return workflowContentletOpt.get(); } @@ -5001,14 +5002,19 @@ private Contentlet checkin(final Contentlet contentletIn, contentletIn, contentRelationships, categories, user, respectFrontendRoles, createNewVersion ) - ); // end synchronized block + ); } catch (final Throwable t) { - Logger.warn(getClass(), t.getMessage(), t); + if (!(t instanceof DotContentletValidationException)) { + Logger.warn(this, ExceptionUtil.getErrorMessage(t), t); + } bubbleUpException(t); } //This way no matter how many times the contentlet reference that we're passing in gets override. //On the way back this updates the original contentlet references updating the new inode and identifier + if (null == contentletOut) { + throw new DotDataException("Checked in contentlet is null. This must never happen!"); + } contentletIn.setIdentifier(contentletOut.getIdentifier()); contentletIn.setInode(contentletOut.getInode()); @@ -7376,18 +7382,20 @@ public void validateContentlet(final Contentlet contentlet, final List } boolean hasError = false; final DotContentletValidationException cve = new DotContentletValidationException( - String.format( - "Contentlet with id:`%s` and title:`%s` has invalid / missing field(s).", + String.format("Contentlet with ID '%s' ['%s'] has invalid/missing field(s).", contentIdentifier, contentlet.getTitle()) ); final List fields = FieldsCache.getFieldsByStructureInode(contentTypeId); final Map contentletMap = contentlet.getMap(); final Set nullValueProperties = contentlet.getNullProperties(); for (final Field field : fields) { - final Object fieldValue = (nullValueProperties.contains(field.getVelocityVarName()) - ? null : contentletMap.get(field.getVelocityVarName())); final com.dotcms.contenttype.model.field.Field newField = LegacyFieldTransformer.from( field); + if (isIgnorableField(newField)) { + continue; + } + final Object fieldValue = (nullValueProperties.contains(field.getVelocityVarName()) + ? null : contentletMap.get(field.getVelocityVarName())); // Validate Field Type if (fieldValue != null) { if (isFieldTypeString(field) && !(newField instanceof JSONField)) { @@ -7531,7 +7539,7 @@ public void validateContentlet(final Contentlet contentlet, final List } else if (field.getFieldType().equals(FieldType.RELATIONSHIP.toString())) { continue; } else if (field.getFieldType().equals(Field.FieldType.CATEGORY.toString())) { - if (cats == null || cats.size() == 0) { + if (UtilMethods.isNotSet(cats)) { cve.addRequiredField(field); hasError = true; Logger.warn(this, "Category Field [" + field.getVelocityVarName() @@ -7548,11 +7556,8 @@ public void validateContentlet(final Contentlet contentlet, final List boolean found = false; for (Category cat : childrenCats) { for (Category passedCat : cats) { - try { - if (passedCat.getInode().equalsIgnoreCase(cat.getInode())) { - found = true; - } - } catch (NumberFormatException e) { + if (passedCat.getInode().equalsIgnoreCase(cat.getInode())) { + found = true; } } } @@ -7564,13 +7569,13 @@ public void validateContentlet(final Contentlet contentlet, final List continue; } } - } catch (DotDataException | DotSecurityException e) { + } catch (final DotDataException | DotSecurityException e) { + cve.addRequiredField(field); + hasError = true; Logger.warn(this, - "Unable to validate a category field [" + field.getVelocityVarName() - + "]", e); - throw new DotContentletValidationException( - "Unable to validate a category field: " - + field.getVelocityVarName(), e); + "Unable to validate category field [" + field.getVelocityVarName() + + "]: " + ExceptionUtil.getErrorMessage(e), e); + continue; } } else if (field.getFieldType().equals(Field.FieldType.HOST_OR_FOLDER.toString())) { if (!UtilMethods.isSet(contentlet.getHost()) && !UtilMethods.isSet( @@ -7614,7 +7619,7 @@ public void validateContentlet(final Contentlet contentlet, final List } else if (field.getFieldType().equals(Field.FieldType.WYSIWYG.toString())) { if (fieldValue instanceof String) { String s = (String) fieldValue; - if (s.trim().toLowerCase().equals("
")) { + if (s.trim().equalsIgnoreCase("
")) { cve.addRequiredField(field); hasError = true; Logger.warn(this, "WYSIWYG Field [" + field.getVelocityVarName() @@ -7632,7 +7637,7 @@ public void validateContentlet(final Contentlet contentlet, final List || field.getDataType().contains(DataTypes.FLOAT.toString()); try { final StringBuilder buffy = new StringBuilder(UUIDGenerator.generateUuid()); - buffy.append(" +structureInode:" + contentlet.getStructureInode()); + buffy.append(" +structureInode:" + contentlet.getContentTypeId()); if (UtilMethods.isSet(contentlet.getIdentifier())) { buffy.append(" -(identifier:" + contentlet.getIdentifier() + ")"); } @@ -7672,7 +7677,7 @@ public void validateContentlet(final Contentlet contentlet, final List } int size = contentlets.size(); if (size > 0 && !hasError) { - Boolean unique = true; + boolean unique = true; for (final ContentletSearch contentletSearch : contentlets) { final Contentlet uniqueContent = contentFactory.find( contentletSearch.getInode()); @@ -7686,7 +7691,7 @@ public void validateContentlet(final Contentlet contentlet, final List } final Map uniqueContentMap = uniqueContent.getMap(); final Object obj = uniqueContentMap.get(field.getVelocityVarName()); - if ((isDataTypeNumber && fieldValue.equals(obj)) || + if ((isDataTypeNumber && Objects.equals(fieldValue, obj)) || (!isDataTypeNumber && ((String) obj).equalsIgnoreCase( ((String) fieldValue)))) { unique = false; @@ -7698,7 +7703,7 @@ public void validateContentlet(final Contentlet contentlet, final List if (UtilMethods.isSet(contentlet.getIdentifier())) { Iterator contentletsIter = contentlets.iterator(); while (contentletsIter.hasNext()) { - ContentletSearch cont = (ContentletSearch) contentletsIter.next(); + ContentletSearch cont = contentletsIter.next(); if (!contentlet.getIdentifier() .equalsIgnoreCase(cont.getIdentifier())) { cve.addUniqueField(field); @@ -7718,9 +7723,9 @@ public void validateContentlet(final Contentlet contentlet, final List } } } - } catch (DotDataException | DotSecurityException e) { + } catch (final DotDataException | DotSecurityException e) { Logger.warn(this, "Unable to get contentlets for Content Type: " - + contentlet.getStructure().getName(), e); + + contentlet.getContentType().name(), e); } } @@ -7728,10 +7733,9 @@ public void validateContentlet(final Contentlet contentlet, final List String dataType = (field.getFieldContentlet() != null) ? field.getFieldContentlet() .replaceAll("[0-9]*", "") : ""; if (UtilMethods.isSet(fieldValue) && dataType.equals("text")) { - String s = ""; try { - s = (String) fieldValue; - } catch (Exception e) { + final String stringValue = (String) fieldValue; + } catch (final Exception e) { Logger.warn(this, "Unable to get string value from text field [" + field.getVelocityVarName() + "] in contentlet", e); @@ -7772,19 +7776,28 @@ public void validateContentlet(final Contentlet contentlet, final List } } } - // validate binary if (isFieldTypeBinary(field)) { - this.validateBinary(File.class.cast(fieldValue), field.getVelocityVarName(), field, - contentType); + this.validateBinary((File) fieldValue, field.getVelocityVarName(), field, contentType); } - } if (hasError) { throw cve; } } + /** + * Determines whether Contentlet Validation process can safely ignore the specified field. + * + * @param newField The {@link com.dotcms.contenttype.model.field.Field} that might be safely + * ignored. + * + * @return If the filed can be ignored, returns {@code true}. + */ + private boolean isIgnorableField(final com.dotcms.contenttype.model.field.Field newField) { + return newField instanceof RowField || newField instanceof ColumnField || newField instanceof TabDividerField || newField instanceof LineDividerField; + } + private String getUniqueFieldErrorMessage(final Field field, final Object fieldValue, final ContentletSearch contentletSearch) { @@ -8218,7 +8231,7 @@ private void validateRelationships(final Contentlet contentlet, // a parent... if (relationship.getCardinality() == RELATIONSHIP_CARDINALITY.ONE_TO_MANY.ordinal() - && relatedContents.size() > 0 + && !relatedContents.isEmpty() && !relatedContents.get(0).getIdentifier() .equals(contentlet.getIdentifier())) { final StringBuilder error = new StringBuilder(); diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/DotContentletValidationException.java b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/DotContentletValidationException.java index 909c83f0f9a6..15e4d309c670 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/DotContentletValidationException.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/DotContentletValidationException.java @@ -1,17 +1,20 @@ package com.dotmarketing.portlets.contentlet.business; +import com.dotmarketing.portlets.contentlet.model.Contentlet; +import com.dotmarketing.portlets.structure.model.Field; +import com.dotmarketing.portlets.structure.model.Relationship; import com.dotmarketing.util.UtilMethods; -import com.liferay.util.StringPool; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; + +import static com.liferay.util.StringPool.BLANK; +import static com.liferay.util.StringPool.SPACE; -import com.dotmarketing.portlets.contentlet.model.Contentlet; -import com.dotmarketing.portlets.structure.model.Field; -import com.dotmarketing.portlets.structure.model.Relationship; /** * Used for throwing contentlet validation problems * @author Jason Tesser @@ -30,8 +33,8 @@ public class DotContentletValidationException extends DotContentletStateExceptio public final static String VALIDATION_FAILED_BAD_CARDINALITY = "badCar"; private static final long serialVersionUID = 1L; - private Map> notValidFields = new HashMap<>(); - private Map>> notValidRelationships = new HashMap<>(); + private final Map> notValidFields = new HashMap<>(); + private final Map>> notValidRelationships = new HashMap<>(); /** * Used for throwing contentlet validation problems @@ -219,59 +222,66 @@ public String toString() { return toString(true); } - public String toString(boolean parentException) { + /** + * Returns a string representation of the list of errors found during the validation of a + * new or existing Contentlet. + * + * @param parentException If the parent exception error message must be included as well, set + * this to {@code true}. + * + * @return The list of validation errors, if any. + */ + public String toString(final boolean parentException) { final StringBuilder builder = new StringBuilder(); if (parentException) { - builder.append(super.toString()).append("\n"); + builder.append(super.toString()); } //Print the Field errors Set keys = notValidFields.keySet(); - if (keys.size() > 0) { - builder.append("List of non valid fields\n"); - for (String key : keys) { - builder.append(key.toUpperCase()).append(": "); - List fields = notValidFields.get(key); + if (!keys.isEmpty()) { + builder.append("Fields: "); + for (final String key : keys) { + builder.append("[").append(key.toUpperCase()).append("]").append(": "); + final List fields = notValidFields.get(key); for (int i = 0; i < fields.size(); i++) { - Field field = fields.get(i); - + final Field field = fields.get(i); if (i > 0) { builder.append(", "); } - - builder.append(field.getVelocityVarName()).append("/") - .append(field.getFieldName()); + builder.append(field.getFieldName()).append(" (") + .append(field.getVelocityVarName()).append(")"); } - builder.append("\n"); + builder.append(SPACE); } } //Print the Relationship errors keys = notValidRelationships.keySet(); - if (keys.size() > 0) { - builder.append("List of non valid relationships\n"); - for (String key : keys) { - builder.append(key.toUpperCase()).append(": "); - Map> relationshipContentlets = notValidRelationships - .get(key); - for (Entry> relationship : relationshipContentlets - .entrySet()) { - builder.append(relationship.getKey().getRelationTypeValue()).append(", "); + if (!keys.isEmpty()) { + builder.append(UtilMethods.isSet(notValidFields.keySet()) ? " / " : BLANK).append("Relationships: "); + int i = 0; + for (final String key : keys) { + builder.append("[").append(key.toUpperCase()).append("]").append(": "); + final Map> relationshipContentlets = notValidRelationships.get(key); + for (final Entry> relationship : relationshipContentlets.entrySet()) { + builder.append(relationship.getKey().getRelationTypeValue()); } - builder.append("\n"); + if (i > 0) { + builder.append(", "); + } + builder.append(SPACE); + i++; } } - builder.append("\n"); - return builder.toString(); + return builder.toString().trim(); } @Override public String getMessage() { - final String toString = toString(false); - - if (UtilMethods.isSet(toString)) { - return super.getMessage() + "\n" + toString; - } else { - return super.getMessage(); - } + final String toString = this.toString(false); + return UtilMethods.isSet(toString) + ? super.getMessage() + " - " + toString + : super.getMessage(); } + } diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/web/ContentletWebAPIImpl.java b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/web/ContentletWebAPIImpl.java index 96483c4fbe9e..a516801d886e 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/web/ContentletWebAPIImpl.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/web/ContentletWebAPIImpl.java @@ -8,6 +8,7 @@ import com.dotcms.contenttype.model.type.ContentType; import com.dotcms.contenttype.model.type.PageContentType; import com.dotcms.contenttype.transform.contenttype.StructureTransformer; +import com.dotcms.exception.ExceptionUtil; import com.dotcms.publisher.environment.bean.Environment; import com.dotcms.repackage.javax.portlet.WindowState; import com.dotcms.repackage.org.directwebremoting.WebContextFactory; @@ -909,12 +910,19 @@ private Contentlet populateContent(Map contentletFormData, return contentlet; } - private void handleException(final Exception ae) { - - if(!(ae instanceof DotContentletValidationException) && !(ae instanceof DotLanguageException)){ - Logger.warnAndDebug(this.getClass(), ae.toString(), ae); + /** + * Prints more or less details of the specified Exception depending on its original cause. For + * more verbose exceptions such as {@link DotContentletValidationException} or + * {@link DotLanguageException}, all details have already made it to the log, so those are not + * required. + * + * @param exception The exception to handle. + */ + private void handleException(final Exception exception) { + if (ExceptionUtil.causedBy(exception, DotContentletValidationException.class) || ExceptionUtil.causedBy(exception, DotLanguageException.class)) { + Logger.debug(this, exception.toString(), exception); } else { - Logger.debug(this, ae.toString(), ae); + Logger.warnAndDebug(this.getClass(), exception.toString(), exception); } } diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/actionlet/SaveContentActionlet.java b/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/actionlet/SaveContentActionlet.java index c86b897dca7c..458c6bfe8e41 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/actionlet/SaveContentActionlet.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/actionlet/SaveContentActionlet.java @@ -1,6 +1,7 @@ package com.dotmarketing.portlets.workflows.actionlet; import com.dotcms.business.WrapInTransaction; +import com.dotcms.exception.ExceptionUtil; import com.dotmarketing.business.APILocator; import com.dotmarketing.exception.DotDataException; import com.dotmarketing.exception.DotSecurityException; @@ -8,12 +9,16 @@ import com.dotmarketing.portlets.contentlet.business.DotContentletValidationException; import com.dotmarketing.portlets.contentlet.model.Contentlet; import com.dotmarketing.portlets.contentlet.model.ContentletDependencies; -import com.dotmarketing.portlets.workflows.model.*; +import com.dotmarketing.portlets.workflows.model.WorkflowActionClassParameter; +import com.dotmarketing.portlets.workflows.model.WorkflowActionFailureException; +import com.dotmarketing.portlets.workflows.model.WorkflowActionletParameter; +import com.dotmarketing.portlets.workflows.model.WorkflowProcessor; +import com.dotmarketing.portlets.workflows.model.WorkflowStep; import com.dotmarketing.util.Logger; import com.dotmarketing.util.UtilMethods; import com.google.common.annotations.VisibleForTesting; - import com.liferay.portal.model.User; + import java.util.List; import java.util.Map; @@ -86,13 +91,11 @@ public void executeAction(final WorkflowProcessor processor, Logger.debug(this, ()->"content version already saved for the contentlet: " + contentlet.getIdentifier()); - } catch (Exception e) { - if (e instanceof DotContentletValidationException){ - Logger.warnAndDebug(this.getClass(),e.getMessage(),e); - } else{ - Logger.error(this.getClass(),e.getMessage(),e); - } - throw new WorkflowActionFailureException(e.getMessage(),e); + } catch (final Exception e) { + if (!(e instanceof DotContentletValidationException)) { + Logger.error(this.getClass(), ExceptionUtil.getErrorMessage(e), e); + } + throw new WorkflowActionFailureException(ExceptionUtil.getErrorMessage(e), e); } } diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/business/WorkflowAPIImpl.java b/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/business/WorkflowAPIImpl.java index 9b4769f49af1..6e1d50fdbd9c 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/business/WorkflowAPIImpl.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/business/WorkflowAPIImpl.java @@ -1,7 +1,5 @@ package com.dotmarketing.portlets.workflows.business; -import static com.dotmarketing.portlets.contentlet.util.ContentletUtil.isHost; - import com.dotcms.ai.workflow.DotEmbeddingsActionlet; import com.dotcms.ai.workflow.OpenAIAutoTagActionlet; import com.dotcms.ai.workflow.OpenAIContentPromptActionlet; @@ -140,6 +138,12 @@ import com.liferay.portal.model.User; import com.liferay.util.StringPool; import io.vavr.control.Try; +import org.apache.commons.lang.time.StopWatch; +import org.apache.commons.lang3.concurrent.ConcurrentUtils; +import org.elasticsearch.search.query.QueryPhaseExecutionException; +import org.osgi.framework.BundleContext; + +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -168,11 +172,8 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.IntStream; -import javax.annotation.Nullable; -import org.apache.commons.lang.time.StopWatch; -import org.apache.commons.lang3.concurrent.ConcurrentUtils; -import org.elasticsearch.search.query.QueryPhaseExecutionException; -import org.osgi.framework.BundleContext; + +import static com.dotmarketing.portlets.contentlet.util.ContentletUtil.isHost; /** * Implementation class for {@link WorkflowAPI}. @@ -2402,12 +2403,10 @@ private WorkflowProcessor fireWorkflowPreCheckin(final Contentlet contentlet, fi @WrapInTransaction @Override public void fireWorkflowPostCheckin(final WorkflowProcessor processor) throws DotDataException,DotWorkflowException{ - try{ if(!processor.inProcess()){ return; } - processor.getContentlet().setActionId(processor.getAction().getId()); final List actionClasses = processor.getActionClasses(); @@ -2417,7 +2416,7 @@ public void fireWorkflowPostCheckin(final WorkflowProcessor processor) throws Do final WorkFlowActionlet actionlet = actionClass.getActionlet(); final Map params = findParamsForActionClass(actionClass); if (processor.isRunningBulk() && actionlet instanceof BatchAction) { - final BatchAction batchable = BatchAction.class.cast(actionlet); + final BatchAction batchable = (BatchAction) actionlet; batchable.preBatchAction(processor, actionClass, params); //gather data to run in batch later } else { @@ -2445,14 +2444,13 @@ public void fireWorkflowPostCheckin(final WorkflowProcessor processor) throws Do Logger.info(this, "Added contentlet to the index at the end of the workflow execution, dependencies: " + includeDependencies); } } - } catch(Exception e) { - - /* Show a more descriptive error of what caused an issue here */ - Logger.error(WorkflowAPIImpl.class, "There was an unexpected error: " + e.getMessage()); - Logger.debug(WorkflowAPIImpl.class, e.getMessage(), e); - throw new DotWorkflowException(e.getMessage(), e); + } catch (final Exception e) { + final String errorMsg = String.format("Failed to fire Workflow Action '%s' [%s]: %s", + processor.getAction().getName(), processor.getAction().getId(), ExceptionUtil.getErrorMessage(e)); + Logger.error(WorkflowAPIImpl.class, errorMsg); + Logger.debug(WorkflowAPIImpl.class, errorMsg, e); + throw new DotWorkflowException(ExceptionUtil.getErrorMessage(e), e); } finally { - // not matters what we need to reindex in deferred the index just in case. if (UtilMethods.isSet(processor.getContentlet()) && UtilMethods.isSet(processor.getContentlet().getIdentifier())) { diff --git a/dotCMS/src/main/java/com/dotmarketing/util/Logger.java b/dotCMS/src/main/java/com/dotmarketing/util/Logger.java index 6a26004ce18b..f7c112e3485e 100644 --- a/dotCMS/src/main/java/com/dotmarketing/util/Logger.java +++ b/dotCMS/src/main/java/com/dotmarketing/util/Logger.java @@ -5,28 +5,29 @@ */ package com.dotmarketing.util; -import java.io.File; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import com.dotcms.rest.api.v1.system.logger.ChangeLoggerLevelEvent; -import com.liferay.util.StringPool; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.LogManager; -import org.apache.velocity.servlet.VelocityServlet; -import org.apache.velocity.tools.view.tools.ViewTool; import com.dotcms.business.expiring.ExpiringMap; import com.dotcms.business.expiring.ExpiringMapBuilder; +import com.dotcms.exception.ExceptionUtil; +import com.dotcms.rest.api.v1.system.logger.ChangeLoggerLevelEvent; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.RemovalCause; import com.github.benmanes.caffeine.cache.RemovalListener; import com.google.common.base.Objects; +import com.liferay.util.StringPool; import io.vavr.Lazy; import io.vavr.control.Try; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.velocity.servlet.VelocityServlet; +import org.apache.velocity.tools.view.tools.ViewTool; + +import java.io.File; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author David Torres @@ -293,20 +294,21 @@ public static void warnAndDebug(Class cl, String message, Throwable ex) { * @param message * @param ex */ - public static void warnAndDebug(String clazz, String message, Throwable ex) { - org.apache.logging.log4j.Logger logger = loadLogger(clazz); + public static void warnAndDebug(final String clazz, final String message, final Throwable ex) { + final org.apache.logging.log4j.Logger logger = loadLogger(clazz); + logger.warn(message); try { - logger.warn(message); - // we don't want to eat the real message - EVER - logger.warn(ex.getMessage()); - try { + // Never swallow the original message, EVER! Unless it's the same as the current one + if (!message.equalsIgnoreCase(ex.getMessage())) { + logger.warn(ex.getMessage()); + } + if (UtilMethods.isSet(ex.getStackTrace()) && null != ex.getStackTrace()[0]) { logger.warn(ex.getStackTrace()[0]); - } catch (Throwable t) { - logger.debug(() -> t); } logger.debug(() -> message, ex); - } catch (java.lang.IllegalStateException e) { - ex.printStackTrace(); + } catch (final IllegalStateException e) { + logger.warn(String.format("Failed to log warnAndDebug message: %s", + ExceptionUtil.getErrorMessage(e))); } } diff --git a/dotcms-integration/src/test/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImplTest.java b/dotcms-integration/src/test/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImplTest.java index 80c5322c4aeb..15e215ab21ee 100644 --- a/dotcms-integration/src/test/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImplTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImplTest.java @@ -19,7 +19,23 @@ import com.dotcms.contenttype.model.type.SimpleContentType; import com.dotcms.contenttype.model.type.VanityUrlContentType; import com.dotcms.contenttype.transform.contenttype.StructureTransformer; -import com.dotcms.datagen.*; +import com.dotcms.datagen.ContentTypeDataGen; +import com.dotcms.datagen.ContentletDataGen; +import com.dotcms.datagen.ExperimentDataGen; +import com.dotcms.datagen.FieldDataGen; +import com.dotcms.datagen.FieldRelationshipDataGen; +import com.dotcms.datagen.FieldVariableDataGen; +import com.dotcms.datagen.FolderDataGen; +import com.dotcms.datagen.HTMLPageDataGen; +import com.dotcms.datagen.LanguageDataGen; +import com.dotcms.datagen.RoleDataGen; +import com.dotcms.datagen.SiteDataGen; +import com.dotcms.datagen.TemplateDataGen; +import com.dotcms.datagen.TestDataUtils; +import com.dotcms.datagen.TestUserUtils; +import com.dotcms.datagen.UserDataGen; +import com.dotcms.datagen.VanityUrlDataGen; +import com.dotcms.datagen.VariantDataGen; import com.dotcms.experiments.model.Experiment; import com.dotcms.mock.response.MockHttpStatusAndHeadersResponse; import com.dotcms.rest.api.v1.DotObjectMapperProvider; @@ -111,7 +127,6 @@ import static com.dotcms.datagen.TestDataUtils.getCommentsLikeContentType; import static com.dotcms.datagen.TestDataUtils.getNewsLikeContentType; import static com.dotcms.datagen.TestDataUtils.relateContentTypes; -import static com.dotcms.util.CollectionsUtils.add; import static com.dotcms.util.CollectionsUtils.list; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -1098,10 +1113,9 @@ public void savingFieldWithUniqueFieldInTheSameHost() throws DotDataException, D try { APILocator.getContentletAPI().checkin(contentlet_2, APILocator.systemUser(), false); throw new AssertionError("DotRuntimeException Expected"); - }catch (DotRuntimeException e) { - final String expectedMessage = String.format("Contentlet with id:`Unknown/New` and title:`` has invalid / missing field(s).\n" - + "List of non valid fields\n" - + "UNIQUE: %s/%s\n\n", uniqueTextField.variable(), uniqueTextField.name()); + }catch (final DotRuntimeException e) { + final String expectedMessage = String.format("Contentlet with ID 'Unknown/New' [''] has invalid/missing field(s)." + + " - Fields: [UNIQUE]: %s (%s)", uniqueTextField.name(), uniqueTextField.variable()); assertEquals(expectedMessage, e.getMessage()); } @@ -1155,10 +1169,9 @@ public void savingFieldWithUniqueFieldInTheSameHostUniquePerSiteToFalse() throws try { APILocator.getContentletAPI().checkin(contentlet_2, APILocator.systemUser(), false); throw new AssertionError("DotRuntimeException Expected"); - } catch (DotRuntimeException e) { - final String expectedMessage = String.format("Contentlet with id:`Unknown/New` and title:`` has invalid / missing field(s).\n" - + "List of non valid fields\n" - + "UNIQUE: %s/%s\n\n", uniqueTextField.variable(), uniqueTextField.name()); + } catch (final DotRuntimeException e) { + final String expectedMessage = String.format("Contentlet with ID 'Unknown/New' [''] has invalid/missing field(s)." + + " - Fields: [UNIQUE]: %s (%s)", uniqueTextField.name(), uniqueTextField.variable()); assertEquals(expectedMessage, e.getMessage()); } @@ -1271,10 +1284,9 @@ public void savingFieldWithUniqueFieldInDifferentHostUniquePerSiteToFalse() thro try { APILocator.getContentletAPI().checkin(contentlet_2, APILocator.systemUser(), false); throw new AssertionError("DotRuntimeException Expected"); - } catch (DotRuntimeException e) { - final String expectedMessage = String.format("Contentlet with id:`Unknown/New` and title:`` has invalid / missing field(s).\n" - + "List of non valid fields\n" - + "UNIQUE: %s/%s\n\n", uniqueTextField.variable(), uniqueTextField.name()); + } catch (final DotRuntimeException e) { + final String expectedMessage = String.format("Contentlet with ID 'Unknown/New' [''] has invalid/missing field(s)." + + " - Fields: [UNIQUE]: %s (%s)", uniqueTextField.name(), uniqueTextField.variable()); assertEquals(expectedMessage, e.getMessage()); } @@ -1712,10 +1724,9 @@ public void savingFieldWithUniqueFieldInTheSameHostTakingContentTypeHost() throw try { APILocator.getContentletAPI().checkin(contentlet_2, APILocator.systemUser(), false); throw new AssertionError("DotRuntimeException Expected"); - } catch (DotRuntimeException e) { - final String expectedMessage = String.format("Contentlet with id:`Unknown/New` and title:`` has invalid / missing field(s).\n" - + "List of non valid fields\n" - + "UNIQUE: %s/%s\n\n", uniqueTextField.variable(), uniqueTextField.name()); + } catch (final DotRuntimeException e) { + final String expectedMessage = String.format("Contentlet with ID 'Unknown/New' [''] has invalid/missing field(s)." + + " - Fields: [UNIQUE]: %s (%s)", uniqueTextField.name(), uniqueTextField.variable()); assertEquals(expectedMessage, e.getMessage()); } diff --git a/dotcms-postman/src/main/resources/postman/ContentResourceV1.postman_collection.json b/dotcms-postman/src/main/resources/postman/ContentResourceV1.postman_collection.json index 8d5812eb1d50..6fc4fac28a45 100644 --- a/dotcms-postman/src/main/resources/postman/ContentResourceV1.postman_collection.json +++ b/dotcms-postman/src/main/resources/postman/ContentResourceV1.postman_collection.json @@ -1,11 +1,10 @@ { "info": { - "_postman_id": "ddc34e79-b221-402b-81ef-15ef00a2bd07", + "_postman_id": "432048a5-eb89-43a5-9828-e4d69ff200dc", "name": "ContentResourceV1", "description": "This folder contains a comprehensive set of API requests related to the `ContentResourceV1` API endpoints. These requests cover various operations such as creating, retrieving and updating content. The folder is organized to help developers and testers efficiently validate and interact with the content resource endpoints in the system.\n\n#### Objectives:\n\n1. **Create Content**:\n \n - Test the creation of new content items with valid and invalid data.\n \n - Ensure that the response includes all necessary details for the created content.\n \n2. **Retrieve Content**:\n \n - Validate the retrieval of content items by ID.\n \n - Ensure the response contains accurate and complete content details.\n \n3. **Update Content**:\n \n - Test updating existing content items with valid and invalid data.\n \n - Verify that the response reflects the updated content accurately.\n \n - Ensure that only authorized users can update content.\n \n4. **Error Handling**:\n \n - Verify that the API returns appropriate error messages for invalid requests.\n \n - Ensure the correct HTTP status codes are used for different error scenarios.\n \n5. **Security**:\n \n - Validate that only authorized users can perform operations on the content.\n \n - Ensure that all security protocols are enforced during API interactions.", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "10041132", - "_collection_link": "https://speeding-firefly-555540.postman.co/workspace/dot~b1c5dc44-8278-4b11-92c8-b3cd3fb935d3/collection/10041132-ddc34e79-b221-402b-81ef-15ef00a2bd07?action=share&source=collection_link&creator=10041132" + "_exporter_id": "5403727" }, "item": [ { @@ -201,14 +200,14 @@ "listen": "test", "script": { "exec": [ - "// Validate the errorMessage contains 'has invalid / missing field(s)'", - "pm.test(\"Validate errorMessage contains 'has invalid / missing field(s)'\", function () {", + "pm.test(\"Validate errorMessage contains 'has invalid/missing field(s)'\", function () {", " var jsonData = pm.response.json();", " jsonData.entity.results.forEach((result) => {", " var error = result[Object.keys(result)[0]].errorMessage;", - " pm.expect(error).to.include('has invalid / missing field(s)');", + " pm.expect(error).to.include('has invalid/missing field(s)');", " });", - "});" + "});", + "" ], "type": "text/javascript", "packages": {} @@ -253,15 +252,13 @@ "listen": "test", "script": { "exec": [ - "// Validate the errorMessage contains 'has invalid / missing field(s)'", - "pm.test(\"Validate errorMessage contains 'has invalid / missing field(s)'\", function () {", + "pm.test(\"Validate errorMessage contains 'has invalid/missing field(s)'\", function () {", " var jsonData = pm.response.json();", " jsonData.entity.results.forEach((result) => {", " var error = result[Object.keys(result)[0]].errorMessage;", - " pm.expect(error).to.include('has invalid / missing field(s)');", + " pm.expect(error).to.include('has invalid/missing field(s)');", " });", "});", - "", "" ], "type": "text/javascript", @@ -2533,47 +2530,5 @@ ] } } - ], - "variable": [ - { - "key": "testContentId", - "value": "" - }, - { - "key": "contentletIdentifier", - "value": "" - }, - { - "key": "contentletInode", - "value": "" - }, - { - "key": "parentId1", - "value": "" - }, - { - "key": "parentInode1", - "value": "" - }, - { - "key": "parent1contentlets", - "value": "" - }, - { - "key": "parent2contentlets", - "value": "" - }, - { - "key": "parentId2", - "value": "" - }, - { - "key": "parentInode2", - "value": "" - }, - { - "key": "DEFAULT_CONTENT_TO_DEFAULT_LANGUAGE_ORIGINAL_VALUE", - "value": "" - } ] } \ No newline at end of file