diff --git a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/impl/AcInstallationServiceImpl.java b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/impl/AcInstallationServiceImpl.java index 1448187e..d63e3bb7 100644 --- a/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/impl/AcInstallationServiceImpl.java +++ b/accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/impl/AcInstallationServiceImpl.java @@ -230,7 +230,7 @@ public JobExecutionResult process(Job job, JobExecutionContext context) { boolean isLogVerbose = Boolean.parseBoolean(jobProperties.getOrDefault(JOB_PROPERTY_IS_LOG_VERBOSE, "false").toString()); asyncInstallLog.attachMessageListener((logLevel, message) -> { if (isLogVerbose || logLevel != InstallationLogLevel.TRACE) { - context.log(logLevel.toString() + ": " + message); + context.log(quoteForMessageFormat(logLevel.toString() + ": " + message)); } }); asyncInstallLog.attachFinishListener(isSuccess -> @@ -255,6 +255,14 @@ public JobExecutionResult process(Job job, JobExecutionContext context) { return result; } + /** + * Quotes the given string so that it can be safely used as literal argument to {@link java.text.MessageFormat} without being interpreted + * @return the quoted string + */ + static String quoteForMessageFormat(String text) { + return "'" + text.replace("'", "''") + "'"; + } + @Override public boolean attachLogListener(String jobId, BiConsumer messageListener, Consumer finishListener) { return false; diff --git a/accesscontroltool-bundle/src/test/java/biz/netcentric/cq/tools/actool/impl/AcInstallationServiceImplTest.java b/accesscontroltool-bundle/src/test/java/biz/netcentric/cq/tools/actool/impl/AcInstallationServiceImplTest.java index b4ccc136..fdb474c4 100644 --- a/accesscontroltool-bundle/src/test/java/biz/netcentric/cq/tools/actool/impl/AcInstallationServiceImplTest.java +++ b/accesscontroltool-bundle/src/test/java/biz/netcentric/cq/tools/actool/impl/AcInstallationServiceImplTest.java @@ -19,11 +19,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; +import java.text.MessageFormat; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.function.UnaryOperator; import javax.jcr.RepositoryException; @@ -180,4 +182,16 @@ public Object answer(InvocationOnMock invocation) throws Throwable { AcInstallationServiceImpl acInstallationServiceImpl2 = new AcInstallationServiceImpl(); acInstallationServiceImpl2.process(job, jobContext); } + + @Test + void testQuoteForMessageFormat() { + assertQuotedTextForMessageFormat("dev=d 19:41:29.927: Global DEF Statement: ENV_KEY_MAPPING={dev=d, stage=s, int=i, prod=p} 19:41:29.928: ", AcInstallationServiceImpl::quoteForMessageFormat); + assertQuotedTextForMessageFormat("This is a test' with apostrophe and {0} braces", AcInstallationServiceImpl::quoteForMessageFormat); + } + + static void assertQuotedTextForMessageFormat(String text, UnaryOperator quoteFunction) { + String quotedText = quoteFunction.apply(text); + String output = MessageFormat.format(quotedText, new Object[0]); + assertEquals(text, output); + } }