From af92be47a940393e11decab0a7ee4cb802d2b5e8 Mon Sep 17 00:00:00 2001 From: Ceki Gulcu Date: Wed, 10 Apr 2024 18:30:23 +0200 Subject: [PATCH] migrate IncludeAction code to IncludeModelHandler, LOGBACK-1746 Signed-off-by: Ceki Gulcu --- .../classic/joran/JoranConfigurator.java | 15 ++ .../test/input/joran/include/included0.xml | 3 + .../test/input/joran/include/topLevel0.xml | 22 ++ .../classic/LoggerContextDeadlockTest.java | 10 +- .../classic/joran/JoranConfiguratorTest.java | 13 +- .../core/joran/GenericXMLConfigurator.java | 10 +- .../core/joran/JoranConfiguratorBase.java | 2 +- .../logback/core/joran/JoranConstants.java | 2 + .../ModelClassToModelHandlerLinkerBase.java | 3 +- .../core/joran/action/IncludeAction.java | 198 +------------- .../qos/logback/core/joran/spi/RuleStore.java | 7 +- .../core/joran/spi/SimpleRuleStore.java | 50 +++- .../model/processor/IncludeModelHandler.java | 244 ++++++++++++++++++ .../processor/ModelInterpretationContext.java | 30 +++ .../core/joran/action/IncludeActionTest.java | 2 + .../core/joran/spi/SimpleRuleStoreTest.java | 49 ++-- 16 files changed, 433 insertions(+), 227 deletions(-) create mode 100644 logback-classic/src/test/input/joran/include/included0.xml create mode 100644 logback-classic/src/test/input/joran/include/topLevel0.xml create mode 100644 logback-core/src/main/java/ch/qos/logback/core/model/processor/IncludeModelHandler.java diff --git a/logback-classic/src/main/java/ch/qos/logback/classic/joran/JoranConfigurator.java b/logback-classic/src/main/java/ch/qos/logback/classic/joran/JoranConfigurator.java index 9e5559c3a1..e07141bf1a 100644 --- a/logback-classic/src/main/java/ch/qos/logback/classic/joran/JoranConfigurator.java +++ b/logback-classic/src/main/java/ch/qos/logback/classic/joran/JoranConfigurator.java @@ -26,6 +26,7 @@ import ch.qos.logback.classic.model.processor.ConfigurationModelHandlerFull; import ch.qos.logback.classic.model.processor.LogbackClassicDefaultNestedComponentRules; import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.joran.GenericXMLConfigurator; import ch.qos.logback.core.joran.JoranConfiguratorBase; import ch.qos.logback.core.joran.action.AppenderRefAction; import ch.qos.logback.core.joran.action.IncludeAction; @@ -34,6 +35,7 @@ import ch.qos.logback.core.joran.spi.RuleStore; import ch.qos.logback.core.model.Model; import ch.qos.logback.core.model.processor.DefaultProcessor; +import ch.qos.logback.core.model.processor.ModelInterpretationContext; /** * JoranConfigurator class adds rules specific to logback-classic. @@ -42,6 +44,8 @@ */ public class JoranConfigurator extends JoranConfiguratorBase { + + @Override public void addElementSelectorAndActionAssociations(RuleStore rs) { // add parent rules @@ -81,6 +85,17 @@ protected void addDefaultNestedComponentRegistryRules(DefaultNestedComponentRegi LogbackClassicDefaultNestedComponentRules.addDefaultNestedComponentRegistryRules(registry); } + private JoranConfigurator makeAnotherInstance() { + JoranConfigurator jc = new JoranConfigurator(); + jc.setContext(context); + return jc; + } + + public void buildModelInterpretationContext() { + super.buildModelInterpretationContext(); + this.modelInterpretationContext.setConfiguratorSupplier( () -> this.makeAnotherInstance() ); + } + @Override protected void addModelHandlerAssociations(DefaultProcessor defaultProcessor) { ModelClassToModelHandlerLinker m = new ModelClassToModelHandlerLinker(context); diff --git a/logback-classic/src/test/input/joran/include/included0.xml b/logback-classic/src/test/input/joran/include/included0.xml new file mode 100644 index 0000000000..8da6d4cd12 --- /dev/null +++ b/logback-classic/src/test/input/joran/include/included0.xml @@ -0,0 +1,3 @@ + + + diff --git a/logback-classic/src/test/input/joran/include/topLevel0.xml b/logback-classic/src/test/input/joran/include/topLevel0.xml new file mode 100644 index 0000000000..6c5c23f2a8 --- /dev/null +++ b/logback-classic/src/test/input/joran/include/topLevel0.xml @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/logback-classic/src/test/java/ch/qos/logback/classic/LoggerContextDeadlockTest.java b/logback-classic/src/test/java/ch/qos/logback/classic/LoggerContextDeadlockTest.java index d5908fd598..04462a68c4 100644 --- a/logback-classic/src/test/java/ch/qos/logback/classic/LoggerContextDeadlockTest.java +++ b/logback-classic/src/test/java/ch/qos/logback/classic/LoggerContextDeadlockTest.java @@ -27,24 +27,28 @@ public class LoggerContextDeadlockTest { LoggerContext loggerContext = new LoggerContext(); - JoranConfigurator jc = new JoranConfigurator(); + GetLoggerThread getLoggerThread = new GetLoggerThread(loggerContext); @BeforeEach public void setUp() throws Exception { - jc.setContext(loggerContext); + } @AfterEach public void tearDown() throws Exception { } + // LBCLASSIC_81 + // LOGBACK-394 @Test @Timeout(value = 20, unit= TimeUnit.SECONDS) - public void testLBCLASSIC_81() throws JoranException { + public void test_LOGBACK_394() throws JoranException { getLoggerThread.start(); for (int i = 0; i < 500; i++) { + JoranConfigurator jc = new JoranConfigurator(); + jc.setContext(loggerContext); ByteArrayInputStream baos = new ByteArrayInputStream( "".getBytes()); jc.doConfigure(baos); diff --git a/logback-classic/src/test/java/ch/qos/logback/classic/joran/JoranConfiguratorTest.java b/logback-classic/src/test/java/ch/qos/logback/classic/joran/JoranConfiguratorTest.java index b16c191389..eb51efecb8 100644 --- a/logback-classic/src/test/java/ch/qos/logback/classic/joran/JoranConfiguratorTest.java +++ b/logback-classic/src/test/java/ch/qos/logback/classic/joran/JoranConfiguratorTest.java @@ -46,6 +46,7 @@ import ch.qos.logback.core.testUtil.StringListAppender; import ch.qos.logback.core.util.CachingDateFormatter; import ch.qos.logback.core.util.StatusPrinter; +import ch.qos.logback.core.util.StatusPrinter2; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.slf4j.MDC; @@ -73,6 +74,7 @@ public class JoranConfiguratorTest { LoggerContext loggerContext = new LoggerContext(); Logger logger = loggerContext.getLogger(this.getClass().getName()); Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + StatusPrinter2 statusPrinter2 = new StatusPrinter2(); StatusChecker checker = new StatusChecker(loggerContext); int diff = RandomUtil.getPositiveInt(); @@ -669,7 +671,16 @@ public void kvp() throws JoranException { assertTrue(slAppender.strList.get(2).contains("null=\"" + kvpNullKey.value + "\" " + msg)); assertTrue(slAppender.strList.get(3).contains(kvpNullValue.key + "=\"null\" " + msg)); } - + + + @Test + public void inclusionWithVariables() throws JoranException { + configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "include/topLevel0.xml"); + + Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + statusPrinter2.print(loggerContext); + assertEquals(Level.ERROR, root.getLevel()); + } // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=46697 @Test diff --git a/logback-core/src/main/java/ch/qos/logback/core/joran/GenericXMLConfigurator.java b/logback-core/src/main/java/ch/qos/logback/core/joran/GenericXMLConfigurator.java index a425489c6f..e0c66d388c 100755 --- a/logback-core/src/main/java/ch/qos/logback/core/joran/GenericXMLConfigurator.java +++ b/logback-core/src/main/java/ch/qos/logback/core/joran/GenericXMLConfigurator.java @@ -51,6 +51,7 @@ public abstract class GenericXMLConfigurator extends ContextAwareBase { public ModelInterpretationContext getModelInterpretationContext() { return this.modelInterpretationContext; } + private RuleStore ruleStore; public final void doConfigure(URL url) throws JoranException { InputStream in = null; @@ -135,7 +136,7 @@ protected ElementPath initialElementPath() { } protected void buildSaxEventInterpreter(List saxEvents) { - RuleStore rs = new SimpleRuleStore(context); + RuleStore rs = getRuleStore(); addElementSelectorAndActionAssociations(rs); this.saxEventInterpreter = new SaxEventInterpreter(context, rs, initialElementPath(), saxEvents); SaxEventInterpretationContext interpretationContext = saxEventInterpreter.getSaxEventInterpretationContext(); @@ -143,6 +144,13 @@ protected void buildSaxEventInterpreter(List saxEvents) { setImplicitRuleSupplier(saxEventInterpreter); } + public RuleStore getRuleStore() { + if(this.ruleStore == null) { + this.ruleStore = new SimpleRuleStore(context); + } + return this.ruleStore; + } + protected void buildModelInterpretationContext() { this.modelInterpretationContext = new ModelInterpretationContext(context); addDefaultNestedComponentRegistryRules(modelInterpretationContext.getDefaultNestedComponentRegistry()); diff --git a/logback-core/src/main/java/ch/qos/logback/core/joran/JoranConfiguratorBase.java b/logback-core/src/main/java/ch/qos/logback/core/joran/JoranConfiguratorBase.java index 170245d19b..70af926061 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/joran/JoranConfiguratorBase.java +++ b/logback-core/src/main/java/ch/qos/logback/core/joran/JoranConfiguratorBase.java @@ -138,7 +138,7 @@ protected void addModelHandlerAssociations(DefaultProcessor defaultProcessor) { defaultProcessor.addHandler(EventEvaluatorModel.class, EventEvaluatorModelHandler::makeInstance); defaultProcessor.addHandler(DefineModel.class, DefineModelHandler::makeInstance); - defaultProcessor.addHandler(IncludeModel.class, NOPModelHandler::makeInstance); + defaultProcessor.addHandler(IncludeModel.class, IncludeModelHandler::makeInstance); defaultProcessor.addHandler(ParamModel.class, ParamModelHandler::makeInstance); diff --git a/logback-core/src/main/java/ch/qos/logback/core/joran/JoranConstants.java b/logback-core/src/main/java/ch/qos/logback/core/joran/JoranConstants.java index 6830447d2b..f0b53c08a3 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/joran/JoranConstants.java +++ b/logback-core/src/main/java/ch/qos/logback/core/joran/JoranConstants.java @@ -22,6 +22,8 @@ */ public abstract class JoranConstants { public static final String INCLUDED_TAG = "included"; + public static final String CONFIGURATION_TAG = "configuration"; + public static final String INCLUDE_TAG = "include"; public static final String APPENDER_TAG = "appender"; diff --git a/logback-core/src/main/java/ch/qos/logback/core/joran/ModelClassToModelHandlerLinkerBase.java b/logback-core/src/main/java/ch/qos/logback/core/joran/ModelClassToModelHandlerLinkerBase.java index 8f3544a814..cc032db93e 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/joran/ModelClassToModelHandlerLinkerBase.java +++ b/logback-core/src/main/java/ch/qos/logback/core/joran/ModelClassToModelHandlerLinkerBase.java @@ -36,6 +36,7 @@ import ch.qos.logback.core.model.processor.EventEvaluatorModelHandler; import ch.qos.logback.core.model.processor.ImplicitModelHandler; import ch.qos.logback.core.model.processor.ImportModelHandler; +import ch.qos.logback.core.model.processor.IncludeModelHandler; import ch.qos.logback.core.model.processor.NOPModelHandler; import ch.qos.logback.core.model.processor.PropertyModelHandler; import ch.qos.logback.core.model.processor.SequenceNumberGeneratorModelHandler; @@ -74,7 +75,7 @@ public void link(DefaultProcessor defaultProcessor) { defaultProcessor.addHandler(EventEvaluatorModel.class, EventEvaluatorModelHandler::makeInstance); defaultProcessor.addHandler(DefineModel.class, DefineModelHandler::makeInstance); - defaultProcessor.addHandler(IncludeModel.class, NOPModelHandler::makeInstance); + defaultProcessor.addHandler(IncludeModel.class, IncludeModelHandler::makeInstance); defaultProcessor.addHandler(ParamModel.class, ParamModelHandler::makeInstance); diff --git a/logback-core/src/main/java/ch/qos/logback/core/joran/action/IncludeAction.java b/logback-core/src/main/java/ch/qos/logback/core/joran/action/IncludeAction.java index 375434b284..b8d2c4b9a6 100755 --- a/logback-core/src/main/java/ch/qos/logback/core/joran/action/IncludeAction.java +++ b/logback-core/src/main/java/ch/qos/logback/core/joran/action/IncludeAction.java @@ -48,223 +48,37 @@ public class IncludeAction extends Action { private static final String RESOURCE_ATTR = "resource"; private static final String OPTIONAL_ATTR = "optional"; - private String attributeInUse; - private boolean optional; - Model parentModel; IncludeModel includeModel; boolean inError = false; @Override - public void begin(SaxEventInterpretationContext ec, String name, Attributes attributes) throws ActionException { + public void begin(SaxEventInterpretationContext seic, String tagName, Attributes attributes) throws ActionException { - parentModel = null; - includeModel = null; - - SaxEventRecorder recorder = new SaxEventRecorder(context); - String optionalStr = attributes.getValue(OPTIONAL_ATTR); - - createModelForAlternateUse(ec, name, attributes, optionalStr); - - - this.attributeInUse = null; - this.optional = OptionHelper.toBoolean(optionalStr, false); - - if (!checkAttributes(attributes)) { - inError = true; - return; - } - - InputStream in = getInputStream(ec, attributes); - - try { - if (in != null) { - parseAndRecord(in, recorder); - // remove the tag from the beginning and from the end - trimHeadAndTail(recorder); - // offset = 2, because we need to get past this element as well as the end - // element - ec.getSaxEventInterpreter().getEventPlayer().addEventsDynamically(recorder.getSaxEventList(), 2); - } - } catch (JoranException je) { - addError("Error while parsing " + attributeInUse, je); - } finally { - close(in); - } - - } - - // model created for later use, not necessarily for configuration purposes. - private void createModelForAlternateUse(SaxEventInterpretationContext seic, String name, Attributes attributes, - String optionalStr) { this.includeModel = new IncludeModel(); this.includeModel.setOptional(optionalStr); - fillInIncludeModelAttributes(includeModel, name, attributes); + fillInIncludeModelAttributes(includeModel, tagName, attributes); if (!seic.isModelStackEmpty()) { parentModel = seic.peekModel(); } final int lineNumber = getLineNumber(seic); this.includeModel.setLineNumber(lineNumber); - seic.pushModel(includeModel); + seic.pushModel(this.includeModel); } - private void fillInIncludeModelAttributes(IncludeModel includeModel, String name, Attributes attributes) { - this.includeModel.setTag(name); + private void fillInIncludeModelAttributes(IncludeModel includeModel, String tagName, Attributes attributes) { + this.includeModel.setTag(tagName); String fileAttribute = attributes.getValue(FILE_ATTR); String urlAttribute = attributes.getValue(URL_ATTR); String resourceAttribute = attributes.getValue(RESOURCE_ATTR); - + this.includeModel.setFile(fileAttribute); this.includeModel.setUrl(urlAttribute); this.includeModel.setResource(resourceAttribute); - } - void close(InputStream in) { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - } - } - } - - private boolean checkAttributes(Attributes attributes) { - String fileAttribute = attributes.getValue(FILE_ATTR); - String urlAttribute = attributes.getValue(URL_ATTR); - String resourceAttribute = attributes.getValue(RESOURCE_ATTR); - - int count = 0; - - if (!OptionHelper.isNullOrEmptyOrAllSpaces(fileAttribute)) { - count++; - } - if (!OptionHelper.isNullOrEmptyOrAllSpaces(urlAttribute)) { - count++; - } - if (!OptionHelper.isNullOrEmptyOrAllSpaces(resourceAttribute)) { - count++; - } - - if (count == 0) { - addError("One of \"path\", \"resource\" or \"url\" attributes must be set."); - return false; - } else if (count > 1) { - addError("Only one of \"file\", \"url\" or \"resource\" attributes should be set."); - return false; - } else if (count == 1) { - return true; - } - throw new IllegalStateException("Count value [" + count + "] is not expected"); - } - - URL attributeToURL(String urlAttribute) { - try { - return new URL(urlAttribute); - } catch (MalformedURLException mue) { - String errMsg = "URL [" + urlAttribute + "] is not well formed."; - addError(errMsg, mue); - return null; - } - } - - InputStream openURL(URL url) { - try { - return url.openStream(); - } catch (IOException e) { - optionalWarning("Failed to open [" + url.toString() + "]"); - return null; - } - } - - URL resourceAsURL(String resourceAttribute) { - URL url = Loader.getResourceBySelfClassLoader(resourceAttribute); - if (url == null) { - optionalWarning("Could not find resource corresponding to [" + resourceAttribute + "]"); - return null; - } else - return url; - } - - private void optionalWarning(String msg) { - if (!optional) { - addWarn(msg); - } - } - - URL filePathAsURL(String path) { - URI uri = new File(path).toURI(); - try { - return uri.toURL(); - } catch (MalformedURLException e) { - // impossible to get here - e.printStackTrace(); - return null; - } - } - - URL getInputURL(SaxEventInterpretationContext ec, Attributes attributes) { - String fileAttribute = attributes.getValue(FILE_ATTR); - String urlAttribute = attributes.getValue(URL_ATTR); - String resourceAttribute = attributes.getValue(RESOURCE_ATTR); - - if (!OptionHelper.isNullOrEmptyOrAllSpaces(fileAttribute)) { - this.attributeInUse = ec.subst(fileAttribute); - return filePathAsURL(attributeInUse); - } - - if (!OptionHelper.isNullOrEmptyOrAllSpaces(urlAttribute)) { - this.attributeInUse = ec.subst(urlAttribute); - return attributeToURL(attributeInUse); - } - - if (!OptionHelper.isNullOrEmptyOrAllSpaces(resourceAttribute)) { - this.attributeInUse = ec.subst(resourceAttribute); - return resourceAsURL(attributeInUse); - } - // given previous checkAttributes() check we cannot reach this line - throw new IllegalStateException("A URL stream should have been returned"); - - } - - InputStream getInputStream(SaxEventInterpretationContext ec, Attributes attributes) { - URL inputURL = getInputURL(ec, attributes); - if (inputURL == null) - return null; - - ConfigurationWatchListUtil.addToWatchList(context, inputURL); - return openURL(inputURL); - } - - private void trimHeadAndTail(SaxEventRecorder recorder) { - // Let's remove the two events before - // adding the events to the player. - - // note saxEventList.size() changes over time as events are removed - - List saxEventList = recorder.getSaxEventList(); - - if (saxEventList.size() == 0) { - return; - } - - SaxEvent first = saxEventList.get(0); - if (first != null && first.qName.equalsIgnoreCase(INCLUDED_TAG)) { - saxEventList.remove(0); - } - - SaxEvent last = saxEventList.get(saxEventList.size() - 1); - if (last != null && last.qName.equalsIgnoreCase(INCLUDED_TAG)) { - saxEventList.remove(saxEventList.size() - 1); - } - } - - private void parseAndRecord(InputStream inputSource, SaxEventRecorder recorder) throws JoranException { - recorder.setContext(context); - recorder.recordEvents(inputSource); - } @Override public void end(SaxEventInterpretationContext seic, String name) throws ActionException { diff --git a/logback-core/src/main/java/ch/qos/logback/core/joran/spi/RuleStore.java b/logback-core/src/main/java/ch/qos/logback/core/joran/spi/RuleStore.java index 154a6da645..a858753420 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/joran/spi/RuleStore.java +++ b/logback-core/src/main/java/ch/qos/logback/core/joran/spi/RuleStore.java @@ -45,7 +45,7 @@ public interface RuleStore { * Add a new rule, given by a pattern and an action instance. * * @param elementSelector - * @param action + * @param actionSupplier */ void addRule(ElementSelector elementSelector, Supplier actionSupplier); @@ -58,4 +58,7 @@ public interface RuleStore { Supplier matchActions(ElementPath elementPath); void addTransparentPathPart(String pathPart); -} + + public void addPathPathMapping(String originalName, String modifiedName); + + } diff --git a/logback-core/src/main/java/ch/qos/logback/core/joran/spi/SimpleRuleStore.java b/logback-core/src/main/java/ch/qos/logback/core/joran/spi/SimpleRuleStore.java index 4d12e0108c..563cb47f19 100755 --- a/logback-core/src/main/java/ch/qos/logback/core/joran/spi/SimpleRuleStore.java +++ b/logback-core/src/main/java/ch/qos/logback/core/joran/spi/SimpleRuleStore.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Supplier; import ch.qos.logback.core.Context; @@ -38,11 +39,13 @@ public class SimpleRuleStore extends ContextAwareBase implements RuleStore { HashMap> rules = new HashMap<>(); List transparentPathParts = new ArrayList<>(2); + Map pathPartsMapForRenaming = new HashMap<>(2); public SimpleRuleStore(Context context) { setContext(context); } + public void addTransparentPathPart(String pathPart) { if (pathPart == null) throw new IllegalArgumentException("pathPart cannot be null"); @@ -59,6 +62,18 @@ public void addTransparentPathPart(String pathPart) { } + /** + * Rename path parts. + * + * @param originalName the name before renaming + * @param modifiedName the after renaming + * @since 1.5.5 + */ + @Override + public void addPathPathMapping(String originalName, String modifiedName) { + pathPartsMapForRenaming.put(originalName, modifiedName); + } + /** * Add a new rule, i.e. a pattern, action pair to the rule store. *

@@ -100,10 +115,25 @@ public Supplier matchActions(ElementPath elementPath) { Supplier actionSupplier = internalMatchAction(elementPath); if(actionSupplier != null) { return actionSupplier; - } else { - ElementPath cleanedElementPath = removeTransparentPathParts(elementPath); - return internalMatchAction(cleanedElementPath); } + + actionSupplier = matchActionsWithoutTransparentParts(elementPath); + if(actionSupplier != null) { + return actionSupplier; + } + + return matchActionsWithRenamedParts(elementPath); + + } + + private Supplier matchActionsWithoutTransparentParts(ElementPath elementPath) { + ElementPath cleanedElementPath = removeTransparentPathParts(elementPath); + return internalMatchAction(cleanedElementPath); + } + + private Supplier matchActionsWithRenamedParts(ElementPath elementPath) { + ElementPath renamedElementPath = renamePathParts(elementPath); + return internalMatchAction(renamedElementPath); } private Supplier internalMatchAction(ElementPath elementPath) { @@ -136,6 +166,20 @@ ElementPath removeTransparentPathParts(ElementPath originalElementPath) { } + + ElementPath renamePathParts(ElementPath originalElementPath) { + + List result = new ArrayList<>(originalElementPath.partList.size()); + + for (String part : originalElementPath.partList) { + String modifiedName = pathPartsMapForRenaming.getOrDefault(part, part); + result.add(modifiedName); + } + + return new ElementPath(result); + } + + Supplier fullPathMatch(ElementPath elementPath) { for (ElementSelector selector : rules.keySet()) { if (selector.fullPathMatch(elementPath)) diff --git a/logback-core/src/main/java/ch/qos/logback/core/model/processor/IncludeModelHandler.java b/logback-core/src/main/java/ch/qos/logback/core/model/processor/IncludeModelHandler.java new file mode 100644 index 0000000000..2baedadb8c --- /dev/null +++ b/logback-core/src/main/java/ch/qos/logback/core/model/processor/IncludeModelHandler.java @@ -0,0 +1,244 @@ +/* + * Logback: the reliable, generic, fast and flexible logging framework. + * Copyright (C) 1999-2024, QOS.ch. All rights reserved. + * + * This program and the accompanying materials are dual-licensed under + * either the terms of the Eclipse Public License v1.0 as published by + * the Eclipse Foundation + * + * or (per the licensee's choosing) + * + * under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation. + */ + +package ch.qos.logback.core.model.processor; + +import ch.qos.logback.core.Context; +import ch.qos.logback.core.joran.GenericXMLConfigurator; +import ch.qos.logback.core.joran.event.SaxEvent; +import ch.qos.logback.core.joran.event.SaxEventRecorder; +import ch.qos.logback.core.joran.spi.JoranException; +import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil; +import ch.qos.logback.core.model.IncludeModel; +import ch.qos.logback.core.model.Model; +import ch.qos.logback.core.spi.ErrorCodes; +import ch.qos.logback.core.util.Loader; +import ch.qos.logback.core.util.OptionHelper; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.List; +import java.util.function.Supplier; + +import static ch.qos.logback.core.joran.JoranConstants.CONFIGURATION_TAG; +import static ch.qos.logback.core.joran.JoranConstants.INCLUDED_TAG; + +/** + * @since 1.5.5 + */ +public class IncludeModelHandler extends ModelHandlerBase { + boolean inError = false; + private String attributeInUse; + private boolean optional; + + public IncludeModelHandler(Context context) { + super(context); + } + + static public IncludeModelHandler makeInstance(Context context, ModelInterpretationContext mic) { + return new IncludeModelHandler(context); + } + + @Override + protected Class getSupportedModelClass() { + return IncludeModel.class; + } + + @Override + public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException { + IncludeModel includeModel = (IncludeModel) model; + + this.optional = OptionHelper.toBoolean(includeModel.getOptional(), false); + + if (!checkAttributes(includeModel)) { + inError = true; + return; + } + + InputStream in = getInputStream(mic, includeModel); + SaxEventRecorder recorder = null; + + try { + recorder = populateSaxEventRecorder(in); + + List saxEvents = recorder.getSaxEventList(); + if (saxEvents.isEmpty()) { + addWarn("Empty sax event list"); + return; + } + + //trimHeadAndTail(saxEvents); + + Supplier jcSupplier = mic.getConfiguratorSupplier(); + if (jcSupplier == null) { + addError("null configurator supplier. Abandoning inclusion of [" + attributeInUse + "]"); + inError = true; + return; + } + + GenericXMLConfigurator genericXMLConfigurator = jcSupplier.get(); + genericXMLConfigurator.getRuleStore().addPathPathMapping(INCLUDED_TAG, CONFIGURATION_TAG); + + Model modelFromIncludedFile = genericXMLConfigurator.buildModelFromSaxEventList(recorder.getSaxEventList()); + if (modelFromIncludedFile == null) { + addError(ErrorCodes.EMPTY_MODEL_STACK); + return; + } + + includeModel.getSubModels().addAll(modelFromIncludedFile.getSubModels()); + + } catch (JoranException e) { + inError = true; + addError("Error processing XML data in [" + attributeInUse + "]", e); + } + } + + public SaxEventRecorder populateSaxEventRecorder(final InputStream inputStream) throws JoranException { + SaxEventRecorder recorder = new SaxEventRecorder(context); + recorder.recordEvents(inputStream); + return recorder; + } + + private void trimHeadAndTail( List saxEventList) { + // Let's remove the two events before + // adding the events to the player. + + // note saxEventList.size() changes over time as events are removed + + if (saxEventList.size() == 0) { + return; + } + + SaxEvent first = saxEventList.get(0); + if (first != null && first.qName.equalsIgnoreCase(INCLUDED_TAG)) { + saxEventList.remove(0); + } + + SaxEvent last = saxEventList.get(saxEventList.size() - 1); + if (last != null && last.qName.equalsIgnoreCase(INCLUDED_TAG)) { + saxEventList.remove(saxEventList.size() - 1); + } + } + + InputStream getInputStream(ModelInterpretationContext mic, IncludeModel includeModel) { + URL inputURL = getInputURL(mic, includeModel); + if (inputURL == null) + return null; + + ConfigurationWatchListUtil.addToWatchList(context, inputURL); + return openURL(inputURL); + } + + InputStream openURL(URL url) { + try { + return url.openStream(); + } catch (IOException e) { + optionalWarning("Failed to open [" + url.toString() + "]"); + return null; + } + } + + private boolean checkAttributes(IncludeModel includeModel) { + String fileAttribute = includeModel.getFile(); + String urlAttribute = includeModel.getUrl(); + String resourceAttribute = includeModel.getResource(); + + int count = 0; + + if (!OptionHelper.isNullOrEmptyOrAllSpaces(fileAttribute)) { + count++; + } + if (!OptionHelper.isNullOrEmptyOrAllSpaces(urlAttribute)) { + count++; + } + if (!OptionHelper.isNullOrEmptyOrAllSpaces(resourceAttribute)) { + count++; + } + + if (count == 0) { + addError("One of \"path\", \"resource\" or \"url\" attributes must be set."); + return false; + } else if (count > 1) { + addError("Only one of \"file\", \"url\" or \"resource\" attributes should be set."); + return false; + } else if (count == 1) { + return true; + } + throw new IllegalStateException("Count value [" + count + "] is not expected"); + } + + URL getInputURL(ModelInterpretationContext mic, IncludeModel includeModel) { + String fileAttribute = includeModel.getFile(); + String urlAttribute = includeModel.getUrl(); + String resourceAttribute = includeModel.getResource(); + + if (!OptionHelper.isNullOrEmptyOrAllSpaces(fileAttribute)) { + this.attributeInUse = mic.subst(fileAttribute); + return filePathAsURL(attributeInUse); + } + + if (!OptionHelper.isNullOrEmptyOrAllSpaces(urlAttribute)) { + this.attributeInUse = mic.subst(urlAttribute); + return attributeToURL(attributeInUse); + } + + if (!OptionHelper.isNullOrEmptyOrAllSpaces(resourceAttribute)) { + this.attributeInUse = mic.subst(resourceAttribute); + return resourceAsURL(attributeInUse); + } + // given preceding checkAttributes() check we cannot reach this line + throw new IllegalStateException("A URL stream should have been returned at this stage"); + + } + + URL filePathAsURL(String path) { + URI uri = new File(path).toURI(); + try { + return uri.toURL(); + } catch (MalformedURLException e) { + // impossible to get here + e.printStackTrace(); + return null; + } + } + + URL attributeToURL(String urlAttribute) { + try { + return new URL(urlAttribute); + } catch (MalformedURLException mue) { + String errMsg = "URL [" + urlAttribute + "] is not well formed."; + addError(errMsg, mue); + return null; + } + } + + URL resourceAsURL(String resourceAttribute) { + URL url = Loader.getResourceBySelfClassLoader(resourceAttribute); + if (url == null) { + optionalWarning("Could not find resource corresponding to [" + resourceAttribute + "]"); + return null; + } else + return url; + } + + private void optionalWarning(String msg) { + if (!optional) { + addWarn(msg); + } + } +} diff --git a/logback-core/src/main/java/ch/qos/logback/core/model/processor/ModelInterpretationContext.java b/logback-core/src/main/java/ch/qos/logback/core/model/processor/ModelInterpretationContext.java index 5e52e8d523..e8c44438fb 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/model/processor/ModelInterpretationContext.java +++ b/logback-core/src/main/java/ch/qos/logback/core/model/processor/ModelInterpretationContext.java @@ -20,9 +20,12 @@ import java.util.Map; import java.util.Properties; import java.util.Stack; +import java.util.function.Supplier; import ch.qos.logback.core.Appender; import ch.qos.logback.core.Context; +import ch.qos.logback.core.joran.GenericXMLConfigurator; +import ch.qos.logback.core.joran.JoranConfiguratorBase; import ch.qos.logback.core.joran.JoranConstants; import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry; import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache; @@ -38,6 +41,16 @@ public class ModelInterpretationContext extends ContextAwareBase implements Cont Stack objectStack; Stack modelStack; + /** + * A supplier of JoranConfigurator instances. + * + * May be null. + * + * @since 1.5.5 + */ + Supplier configuratorSupplier; + + Map objectMap; protected VariableSubstitutionsHelper variableSubstitutionsHelper; protected Map importMap; @@ -265,4 +278,21 @@ public String getImport(String stem) { else return result; } + + /** + * Returns a supplier of {@link GenericXMLConfigurator} instance. The returned value may be null. + * + * @return a supplier of {@link GenericXMLConfigurator} instance, may be null + */ + public Supplier getConfiguratorSupplier() { + return this.configuratorSupplier; + } + + /** + * + * @param configuratorSupplier + */ + public void setConfiguratorSupplier(Supplier configuratorSupplier) { + this.configuratorSupplier = configuratorSupplier; + } } diff --git a/logback-core/src/test/java/ch/qos/logback/core/joran/action/IncludeActionTest.java b/logback-core/src/test/java/ch/qos/logback/core/joran/action/IncludeActionTest.java index 5cbe613ce5..e84e6ee124 100755 --- a/logback-core/src/test/java/ch/qos/logback/core/joran/action/IncludeActionTest.java +++ b/logback-core/src/test/java/ch/qos/logback/core/joran/action/IncludeActionTest.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.xml.sax.SAXParseException; @@ -48,6 +49,7 @@ import ch.qos.logback.core.status.testUtil.StatusChecker; import ch.qos.logback.core.util.StatusPrinter; +@Disabled public class IncludeActionTest { final static String INCLUDE_KEY = "includeKey"; diff --git a/logback-core/src/test/java/ch/qos/logback/core/joran/spi/SimpleRuleStoreTest.java b/logback-core/src/test/java/ch/qos/logback/core/joran/spi/SimpleRuleStoreTest.java index 29ce531739..3afc726e41 100644 --- a/logback-core/src/test/java/ch/qos/logback/core/joran/spi/SimpleRuleStoreTest.java +++ b/logback-core/src/test/java/ch/qos/logback/core/joran/spi/SimpleRuleStoreTest.java @@ -1,15 +1,13 @@ /** - * Logback: the reliable, generic, fast and flexible logging framework. - * Copyright (C) 1999-2015, QOS.ch. All rights reserved. + * Logback: the reliable, generic, fast and flexible logging framework. Copyright (C) 1999-2015, QOS.ch. All rights + * reserved. * - * This program and the accompanying materials are dual-licensed under - * either the terms of the Eclipse Public License v1.0 as published by - * the Eclipse Foundation + * This program and the accompanying materials are dual-licensed under either the terms of the Eclipse Public License + * v1.0 as published by the Eclipse Foundation * - * or (per the licensee's choosing) + * or (per the licensee's choosing) * - * under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation. + * under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation. */ package ch.qos.logback.core.joran.spi; @@ -30,9 +28,9 @@ /** * Test SimpleRuleStore for various explicit rule combinations. - * + * * We also test that explicit patterns are case-sensitive. - * + * * @author Ceki Gülcü */ public class SimpleRuleStoreTest { @@ -175,7 +173,7 @@ public void testPrefixSuffixInteraction1() throws Exception { assertNotNull(r); Action ra = r.get(); - + assertTrue(ra instanceof XAction); XAction xaction = (XAction) ra; assertEquals(3, xaction.id); @@ -197,12 +195,6 @@ public void testPrefixSuffixInteraction2() throws Exception { @Test public void withTransparentParts() throws Exception { -// srs.addRule(new ElementSelector("x"), new TopElementAction()); -// srs.addRule(new ElementSelector("x/stack"), new StackAction()); -// srs.addRule(new ElementSelector("x/property"), new PropertyAction()); -// srs.addRule(new ElementSelector("*/if"), new IfAction()); -// srs.addRule(new ElementSelector("*/if/then"), new ThenAction()); -// srs.addRule(new ElementSelector("*/if/else"), new ElseAction()); srs.addTransparentPathPart("if"); srs.addTransparentPathPart("then"); @@ -211,28 +203,39 @@ public void withTransparentParts() throws Exception { { ElementPath ep = new ElementPath("x/if/then/if"); ElementPath witness = new ElementPath("x/"); - + ElementPath cleanedEP = srs.removeTransparentPathParts(ep); assertEquals(witness, cleanedEP); } - + { ElementPath ep = new ElementPath("x/if/then/stack"); ElementPath witness = new ElementPath("x/stack"); - + ElementPath cleanedEP = srs.removeTransparentPathParts(ep); assertEquals(witness, cleanedEP); } - + { ElementPath ep = new ElementPath("x/if/then/if/else/stack"); ElementPath witness = new ElementPath("x/stack"); - + ElementPath cleanedEP = srs.removeTransparentPathParts(ep); assertEquals(witness, cleanedEP); } - + } + + @Test + public void withRenamedParts() throws Exception { + srs.addPathPathMapping("included", "configure"); + { + ElementPath ep = new ElementPath("included/a/b"); + ElementPath witness = new ElementPath("configure/a/b"); + + ElementPath renamedEP = srs.renamePathParts(ep); + assertEquals(witness, renamedEP); + } } class XAction extends Action {