Skip to content

Commit 769bce0

Browse files
committed
add scanStr field to PropertiesConfiguratorAction, refactor ResourceAction
Signed-off-by: ceki <[email protected]>
1 parent 6fd0943 commit 769bce0

8 files changed

Lines changed: 258 additions & 41 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<included>
3+
4+
<appender name="LIST" class="ch.qos.logback.core.read.ListAppender"/>
5+
6+
<root level="INFO">
7+
<if condition='p("missing").equals("true")'>
8+
<then>
9+
<appender-ref ref="MISSING" />
10+
</then>
11+
<else>
12+
<appender-ref ref="LIST" />
13+
</else>
14+
</if>
15+
</root>
16+
17+
</included>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<configuration>
2+
3+
<if condition='p("missing").equals("true")'>
4+
<then>
5+
<include optional="true" resource="missing.xml"/>
6+
</then>
7+
<else>
8+
<include resource="src/test/input/joran/collision/included.xml"/>
9+
</else>
10+
</if>
11+
</configuration>

logback-classic/src/main/java/ch/qos/logback/classic/joran/action/PropertiesConfiguratorAction.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616

1717
import ch.qos.logback.classic.model.PropertiesConfiguratorModel;
1818
import ch.qos.logback.core.joran.action.ResourceAction;
19+
import ch.qos.logback.core.joran.spi.SaxEventInterpretationContext;
20+
import ch.qos.logback.core.model.Model;
21+
import org.xml.sax.Attributes;
22+
23+
import static ch.qos.logback.classic.joran.action.ConfigurationAction.SCAN_ATTR;
1924

2025
/**
2126
* Build an {@link PropertiesConfiguratorModel} instance from SAX events.
@@ -25,8 +30,44 @@
2530
*/
2631
public class PropertiesConfiguratorAction extends ResourceAction {
2732

33+
34+
/**
35+
* Creates a new instance of {@link PropertiesConfiguratorModel}.
36+
*
37+
* @return a new {@link PropertiesConfiguratorModel} instance
38+
*/
2839
protected PropertiesConfiguratorModel makeNewResourceModel() {
2940
return new PropertiesConfiguratorModel();
3041
}
3142

32-
}
43+
44+
/**
45+
* Builds a {@link PropertiesConfiguratorModel} instance for the current XML element.
46+
*
47+
* <p>This method extends the parent class behavior by additionally extracting and setting
48+
* the scan attribute value on the returned model.</p>
49+
*
50+
* @param saxEventInterpretationContext the context for interpreting SAX events
51+
* @param localName the name of the XML element being processed
52+
* @param attributes the attributes of the XML element
53+
* @return a configured {@link PropertiesConfiguratorModel} instance
54+
* @throws IllegalStateException if the model returned by the parent class is not a
55+
* {@link PropertiesConfiguratorModel}
56+
*/
57+
@Override
58+
public Model buildCurrentModel(SaxEventInterpretationContext saxEventInterpretationContext, String localName,
59+
Attributes attributes) {
60+
Model model = super.buildCurrentModel(saxEventInterpretationContext, localName, attributes);
61+
62+
if (model instanceof PropertiesConfiguratorModel) {
63+
PropertiesConfiguratorModel propertiesConfiguratorModel = (PropertiesConfiguratorModel) model;
64+
String scanAttribute = attributes.getValue(SCAN_ATTR);
65+
propertiesConfiguratorModel.setScanStr(scanAttribute);
66+
return propertiesConfiguratorModel;
67+
} else {
68+
// this is impossible since makeNewResourceModel() returns a PropertiesConfiguratorModel
69+
throw new IllegalStateException("Model is not of type PropertiesConfiguratorModel");
70+
}
71+
}
72+
}
73+

logback-core/src/main/java/ch/qos/logback/core/joran/action/BaseModelAction.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ public void begin(SaxEventInterpretationContext saxEventInterpretationContext, S
4646
saxEventInterpretationContext.pushModel(currentModel);
4747
}
4848

49+
/**
50+
* Builds and returns a Model instance for the current XML element being processed.
51+
*
52+
* <p>This method is called during the begin phase of XML processing to create a Model
53+
* object that represents the current element. The returned model will be configured with
54+
* the element's tag name, line number, and will be pushed onto the model stack.</p>
55+
*
56+
* @param interpretationContext the context for interpreting SAX events, providing access to
57+
* the model stack and other interpretation state
58+
* @param name the name of the XML element being processed
59+
* @param attributes the attributes of the XML element
60+
* @return a new Model instance representing the current XML element
61+
*/
4962
abstract protected Model buildCurrentModel(SaxEventInterpretationContext interpretationContext, String name,
5063
Attributes attributes);
5164

logback-core/src/main/java/ch/qos/logback/core/joran/action/IncludeAction.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*/
2525
public class IncludeAction extends ResourceAction {
2626

27+
@Override
2728
protected ResourceModel makeNewResourceModel() {
2829
return new IncludeModel();
2930
}

logback-core/src/main/java/ch/qos/logback/core/joran/action/PreconditionValidator.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,24 @@ public PreconditionValidator validateRefAttribute() {
6666
return validateGivenAttribute(JoranConstants.REF_ATTRIBUTE);
6767
}
6868

69+
public PreconditionValidator validateOneAndOnlyOneAttributeProvided(String... names) {
70+
int validCount = 0;
71+
for(String name : names) {
72+
boolean invalid = isInvalidAttribute(name);
73+
if(!invalid) {
74+
validCount++;
75+
}
76+
}
77+
if(validCount == 1) {
78+
this.valid = true;
79+
} else {
80+
this.valid = false;
81+
addError("Element [" + tag + "] should have at least one of [" + names + "] as an attribute, near line "
82+
+ Action.getLineNumber(seic));
83+
}
84+
return this;
85+
}
86+
6987
public boolean isInvalidAttribute(String attributeName) {
7088
String attributeValue = attributes.getValue(attributeName);
7189
return OptionHelper.isNullOrEmptyOrAllSpaces(attributeValue);

logback-core/src/main/java/ch/qos/logback/core/joran/action/ResourceAction.java

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
package ch.qos.logback.core.joran.action;
1616

17-
import ch.qos.logback.core.joran.spi.ActionException;
1817
import ch.qos.logback.core.joran.spi.SaxEventInterpretationContext;
1918
import ch.qos.logback.core.model.Model;
2019
import ch.qos.logback.core.model.ResourceModel;
@@ -25,62 +24,42 @@
2524
*
2625
* @since 1.5.8
2726
*/
28-
abstract public class ResourceAction extends Action {
27+
abstract public class ResourceAction extends BaseModelAction {
2928

3029
private static final String FILE_ATTR = "file";
3130
private static final String URL_ATTR = "url";
3231
private static final String RESOURCE_ATTR = "resource";
3332
private static final String OPTIONAL_ATTR = "optional";
3433

35-
Model parentModel;
36-
ResourceModel resourceModel;
37-
boolean inError = false;
3834

3935
abstract protected ResourceModel makeNewResourceModel();
4036

4137
@Override
42-
public void begin(SaxEventInterpretationContext seic, String tagName, Attributes attributes) throws ActionException {
43-
String optionalStr = attributes.getValue(OPTIONAL_ATTR);
38+
protected boolean validPreconditions(SaxEventInterpretationContext intercon, String name, Attributes attributes) {
39+
PreconditionValidator pv = new PreconditionValidator(this, intercon, name, attributes);
40+
pv.validateOneAndOnlyOneAttributeProvided(FILE_ATTR, URL_ATTR, RESOURCE_ATTR);
41+
return pv.isValid();
42+
}
4443

45-
this.resourceModel = makeNewResourceModel();
46-
this.resourceModel.setOptional(optionalStr);
47-
fillInIncludeModelAttributes(resourceModel, tagName, attributes);
48-
if (!seic.isModelStackEmpty()) {
49-
parentModel = seic.peekModel();
50-
}
51-
final int lineNumber = getLineNumber(seic);
52-
this.resourceModel.setLineNumber(lineNumber);
53-
seic.pushModel(this.resourceModel);
44+
@Override
45+
protected Model buildCurrentModel(SaxEventInterpretationContext interpretationContext, String localName,
46+
Attributes attributes) {
47+
ResourceModel resourceModel = makeNewResourceModel();
48+
fillInIncludeModelAttributes(resourceModel, localName, attributes);
49+
return resourceModel;
5450
}
5551

52+
5653
private void fillInIncludeModelAttributes(ResourceModel resourceModel, String tagName, Attributes attributes) {
57-
this.resourceModel.setTag(tagName);
54+
resourceModel.setTag(tagName);
5855
String fileAttribute = attributes.getValue(FILE_ATTR);
5956
String urlAttribute = attributes.getValue(URL_ATTR);
6057
String resourceAttribute = attributes.getValue(RESOURCE_ATTR);
61-
62-
this.resourceModel.setFile(fileAttribute);
63-
this.resourceModel.setUrl(urlAttribute);
64-
this.resourceModel.setResource(resourceAttribute);
58+
String optionalAttribute = attributes.getValue(OPTIONAL_ATTR);
59+
resourceModel.setFile(fileAttribute);
60+
resourceModel.setUrl(urlAttribute);
61+
resourceModel.setResource(resourceAttribute);
62+
resourceModel.setOptional(optionalAttribute);
6563
}
6664

67-
@Override
68-
public void end(SaxEventInterpretationContext seic, String name) throws ActionException {
69-
if(inError)
70-
return;
71-
72-
Model m = seic.peekModel();
73-
74-
if (m != resourceModel) {
75-
addWarn("The object at the of the stack is not the model [" + resourceModel.idString()
76-
+ "] pushed earlier.");
77-
addWarn("This is wholly unexpected.");
78-
}
79-
80-
// do not pop nor add to parent if there is no parent
81-
if (parentModel != null) {
82-
parentModel.addSubModel(resourceModel);
83-
seic.popModel();
84-
}
85-
}
8665
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* Logback: the reliable, generic, fast and flexible logging framework.
3+
* Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4+
*
5+
* This program and the accompanying materials are dual-licensed under
6+
* either the terms of the Eclipse Public License v2.0 as published by
7+
* the Eclipse Foundation
8+
*
9+
* or (per the licensee's choosing)
10+
*
11+
* under the terms of the GNU Lesser General Public License version 2.1
12+
* as published by the Free Software Foundation.
13+
*/
14+
package ch.qos.logback.core.joran.action;
15+
16+
import org.junit.jupiter.api.Assertions;
17+
import org.junit.jupiter.api.BeforeEach;
18+
import org.junit.jupiter.api.Test;
19+
20+
import ch.qos.logback.core.Context;
21+
import ch.qos.logback.core.ContextBase;
22+
import ch.qos.logback.core.joran.spi.SaxEventInterpretationContext;
23+
24+
/**
25+
* Test {@link PreconditionValidator}.
26+
*/
27+
public class PreconditionValidatorTest {
28+
29+
IncludeAction includeAction = new IncludeAction();
30+
Context context;
31+
SaxEventInterpretationContext interpretationContext;
32+
DummyAttributes attributes;
33+
String tagName = "testTag";
34+
35+
@BeforeEach
36+
public void setUp() {
37+
context = new ContextBase();
38+
includeAction.setContext(context);
39+
interpretationContext = new SaxEventInterpretationContext(context, null);
40+
attributes = new DummyAttributes();
41+
}
42+
43+
@Test
44+
public void validateOneAndOnlyOneAttributeProvided_withExactlyOneValidAttribute() {
45+
attributes.setValue("file", "test.txt");
46+
47+
PreconditionValidator validator = new PreconditionValidator(includeAction, interpretationContext, tagName, attributes);
48+
validator.validateOneAndOnlyOneAttributeProvided("file", "url", "resource");
49+
50+
Assertions.assertTrue(validator.isValid());
51+
}
52+
53+
@Test
54+
public void validateOneAndOnlyOneAttributeProvided_withTwoValidAttributes() {
55+
attributes.setValue("file", "test.txt");
56+
attributes.setValue("url", "http://example.com");
57+
58+
PreconditionValidator validator = new PreconditionValidator(includeAction, interpretationContext, tagName, attributes);
59+
validator.validateOneAndOnlyOneAttributeProvided("file", "url", "resource");
60+
61+
Assertions.assertFalse(validator.isValid());
62+
Assertions.assertEquals(1, context.getStatusManager().getCount());
63+
}
64+
65+
@Test
66+
public void validateOneAndOnlyOneAttributeProvided_withNoValidAttributes() {
67+
PreconditionValidator validator = new PreconditionValidator(includeAction, interpretationContext, tagName, attributes);
68+
validator.validateOneAndOnlyOneAttributeProvided("file", "url", "resource");
69+
70+
Assertions.assertFalse(validator.isValid());
71+
Assertions.assertEquals(1, context.getStatusManager().getCount());
72+
}
73+
74+
@Test
75+
public void validateOneAndOnlyOneAttributeProvided_withEmptyStringAttribute() {
76+
attributes.setValue("file", "");
77+
78+
PreconditionValidator validator = new PreconditionValidator(includeAction, interpretationContext, tagName, attributes);
79+
validator.validateOneAndOnlyOneAttributeProvided("file", "url", "resource");
80+
81+
Assertions.assertFalse(validator.isValid());
82+
}
83+
84+
@Test
85+
public void validateOneAndOnlyOneAttributeProvided_withWhitespaceOnlyAttribute() {
86+
attributes.setValue("file", " ");
87+
88+
PreconditionValidator validator = new PreconditionValidator(includeAction, interpretationContext, tagName, attributes);
89+
validator.validateOneAndOnlyOneAttributeProvided("file", "url", "resource");
90+
91+
Assertions.assertFalse(validator.isValid());
92+
}
93+
94+
@Test
95+
public void validateOneAndOnlyOneAttributeProvided_withOneValidAndOneInvalidAttribute() {
96+
attributes.setValue("file", "test.txt");
97+
attributes.setValue("url", "");
98+
99+
PreconditionValidator validator = new PreconditionValidator(includeAction, interpretationContext, tagName, attributes);
100+
validator.validateOneAndOnlyOneAttributeProvided("file", "url", "resource");
101+
102+
Assertions.assertTrue(validator.isValid());
103+
}
104+
105+
@Test
106+
public void validateOneAndOnlyOneAttributeProvided_withThreeValidAttributes() {
107+
attributes.setValue("file", "test.txt");
108+
attributes.setValue("url", "http://example.com");
109+
attributes.setValue("resource", "config.xml");
110+
111+
PreconditionValidator validator = new PreconditionValidator(includeAction, interpretationContext, tagName, attributes);
112+
validator.validateOneAndOnlyOneAttributeProvided("file", "url", "resource");
113+
114+
Assertions.assertFalse(validator.isValid());
115+
Assertions.assertEquals(1, context.getStatusManager().getCount());
116+
}
117+
118+
@Test
119+
public void validateOneAndOnlyOneAttributeProvided_withSingleAttributeOption() {
120+
attributes.setValue("name", "testName");
121+
122+
PreconditionValidator validator = new PreconditionValidator(includeAction, interpretationContext, tagName, attributes);
123+
validator.validateOneAndOnlyOneAttributeProvided("name");
124+
125+
Assertions.assertTrue(validator.isValid());
126+
}
127+
128+
@Test
129+
public void validateOneAndOnlyOneAttributeProvided_withNullAttribute() {
130+
attributes.setValue("file", null);
131+
132+
PreconditionValidator validator = new PreconditionValidator(includeAction, interpretationContext, tagName, attributes);
133+
validator.validateOneAndOnlyOneAttributeProvided("file", "url");
134+
135+
Assertions.assertFalse(validator.isValid());
136+
}
137+
}

0 commit comments

Comments
 (0)