diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/StringMatchFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/StringMatchFilterTest.java new file mode 100644 index 00000000000..8ecff8a441b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/StringMatchFilterTest.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.core.filter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link StringMatchFilter}. + */ +class StringMatchFilterTest { + + /** + * Test the normal valid programmatic instantiation of a {@link StringMatchFilter} via its builder. + */ + @Test + void testFilterBuilderOK() { + StringMatchFilter.Builder stringMatchFilterBuilder = StringMatchFilter.newBuilder(); + stringMatchFilterBuilder.setText("foo"); + StringMatchFilter stringMatchFilter = stringMatchFilterBuilder.build(); + assertNotNull(stringMatchFilter, "The filter should not be null."); + assertEquals("foo", stringMatchFilter.getText()); + } + + /** + * Test that if no match-string is set on the builder, the '{@link StringMatchFilter.Builder#build()}' returns + * {@code null}. + */ + @Test + void testFilterBuilderFailsWithNullText() { + StringMatchFilter.Builder stringMatchFilterBuilder = StringMatchFilter.newBuilder(); + Assertions.assertNull(stringMatchFilterBuilder.build()); + } + + /** + * Test that if a {@code null} string is set as a match-pattern, an {@code IllegalArgumentExeption} is thrown. + */ + @Test + @SuppressWarnings({"DataFlowIssue" // invalid null parameter explicitly being tested + }) + void testFilterBuilderFailsWithExceptionOnNullText() { + StringMatchFilter.Builder stringMatchFilterBuilder = StringMatchFilter.newBuilder(); + Assertions.assertThrows(IllegalArgumentException.class, () -> stringMatchFilterBuilder.setText(null)); + } + + /** + * Test that if an empty ({@code ""}) string is set as a match-pattern, an {@code IllegalArgumentException} is thrown. + */ + @Test + void testFilterBuilderFailsWithExceptionOnEmptyText() { + StringMatchFilter.Builder stringMatchFilterBuilder = StringMatchFilter.newBuilder(); + Assertions.assertThrows(IllegalArgumentException.class, () -> stringMatchFilterBuilder.setText("")); + } + + /** + * Test that if a {@link StringMatchFilter} is specified with a 'text' attribute it is correctly instantiated. + * + * @param configuration the configuration + */ + @Test + @LoggerContextSource("log4j2-stringmatchfilter-3153-ok.xml") + void testConfigurationWithTextPOS(final Configuration configuration) { + final Filter filter = configuration.getFilter(); + assertNotNull(filter, "The filter should not be null."); + assertInstanceOf( + StringMatchFilter.class, filter, "Expected a StringMatchFilter, but got: " + filter.getClass()); + assertEquals("FooBar", ((StringMatchFilter) filter).getText()); + } + + /** + * Test that if a {@link StringMatchFilter} is specified without a 'text' attribute it is not instantiated. + * + * @param configuration the configuration + */ + @Test + @LoggerContextSource("log4j2-stringmatchfilter-3153-nok.xml") + void testConfigurationWithTextNEG(final Configuration configuration) { + final Filter filter = configuration.getFilter(); + assertNull(filter, "The filter should be null."); + } +} diff --git a/log4j-core-test/src/test/resources/log4j2-stringmatchfilter-3153-nok.xml b/log4j-core-test/src/test/resources/log4j2-stringmatchfilter-3153-nok.xml new file mode 100644 index 00000000000..099560056d1 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-stringmatchfilter-3153-nok.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-stringmatchfilter-3153-ok.xml b/log4j-core-test/src/test/resources/log4j2-stringmatchfilter-3153-ok.xml new file mode 100644 index 00000000000..598dfaa3025 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-stringmatchfilter-3153-ok.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLifeCycle.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLifeCycle.java index 4149ca99dfe..c51053fb222 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLifeCycle.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLifeCycle.java @@ -47,21 +47,24 @@ protected static org.apache.logging.log4j.Logger getStatusLogger() { private volatile LifeCycle.State state = LifeCycle.State.INITIALIZED; - protected boolean equalsImpl(final Object obj) { - if (this == obj) { + /** + * Indicates whether some other object is "equal to" this one. + * @param other the other object to compare to + * @return {@code true} if this object is the same as the obj argument; {@code false} otherwise. + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + protected boolean equalsImpl(final Object other) { + // identity check - fast exit + if (this == other) { return true; } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { + // exact class check + if (other == null || this.getClass() != other.getClass()) { return false; } - final LifeCycle other = (LifeCycle) obj; - if (state != other.getState()) { - return false; - } - return true; + // field check + final AbstractLifeCycle that = (AbstractLifeCycle) other; + return (this.state == that.state); } @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java index d9b3d1dbf78..10abc544104 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java @@ -110,25 +110,29 @@ protected AbstractFilter(final Result onMatch, final Result onMismatch) { this.onMismatch = onMismatch == null ? Result.DENY : onMismatch; } + /** + * Constructor which obtains its parameterization from the given builder. + * @param builder the builder + */ + protected AbstractFilter(final AbstractFilterBuilder builder) { + this(builder.getOnMatch(), builder.getOnMismatch()); + } + + /** {@inheritDoc} */ @Override - protected boolean equalsImpl(final Object obj) { - if (this == obj) { + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + protected boolean equalsImpl(final Object other) { + // identity check - fast exit + if (this == other) { return true; } - if (!super.equalsImpl(obj)) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final AbstractFilter other = (AbstractFilter) obj; - if (onMatch != other.onMatch) { - return false; - } - if (onMismatch != other.onMismatch) { + // type check and superclass + if (other == null || this.getClass() != other.getClass()) { return false; } - return true; + // field equality + final AbstractFilter that = (AbstractFilter) other; + return super.equalsImpl(that) && this.onMatch == that.onMatch && this.onMismatch == that.onMismatch; } /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StringMatchFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StringMatchFilter.java index 51ee3b2a201..1d4742c5fcc 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StringMatchFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StringMatchFilter.java @@ -16,6 +16,7 @@ */ package org.apache.logging.log4j.core.filter; +import java.util.Objects; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.Filter; @@ -26,232 +27,551 @@ import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.util.Assert; +import org.apache.logging.log4j.plugins.validation.constraints.Required; import org.apache.logging.log4j.util.PerformanceSensitive; +import org.apache.logging.log4j.util.Strings; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** - * This filter returns the onMatch result if the message in the event matches the specified text - * exactly. + * This filter returns the {@code onMatch} result if the formatted message contains the + * configured "{@code text}" value; otherwise, it returns the {@code onMismatch} result. + *

+ * The text comparison is case-sensitive. + *

*/ @Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@NullMarked @Plugin @PerformanceSensitive("allocation") public final class StringMatchFilter extends AbstractFilter { - public static final String ATTR_MATCH = "match"; + /** The string match text. */ private final String text; - private StringMatchFilter(final String text, final Result onMatch, final Result onMismatch) { - super(onMatch, onMismatch); - this.text = text; + /** + * Constructs a new string-match filter instance. + * + * @param builder the builder implementation + * @throws IllegalArgumentException if the {@code text} argument is {@code null} or blank + */ + private StringMatchFilter(final Builder builder) { + + super(builder); + + if (Strings.isNotEmpty(builder.text)) { + this.text = builder.text; + } else { + throw new IllegalArgumentException("The 'text' argument must not be null or empty."); + } } - @Override - public Result filter( - final Logger logger, final Level level, final Marker marker, final String msg, final Object... params) { - return filter(logger.getMessageFactory().newMessage(msg, params).getFormattedMessage()); + /** + * Returns the string-filter match text + * @return the match text + */ + public String getText() { + return this.text; } + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation on the given event's formatted message. + *

+ * + * @throws NullPointerException if the given {@code event} is {@code null} + */ @Override - public Result filter( - final Logger logger, final Level level, final Marker marker, final Object msg, final Throwable t) { - return filter(logger.getMessageFactory().newMessage(msg).getFormattedMessage()); + public Result filter(final LogEvent event) { + Objects.requireNonNull(event, "The 'event' argument must not be null."); + return filter(event.getMessage().getFormattedMessage()); } + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against the given message formatted with the + * given parameters. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

+ *

+ * + * @throws NullPointerException if the {@code logger} argument is {@code null} + */ @Override public Result filter( - final Logger logger, final Level level, final Marker marker, final Message msg, final Throwable t) { - return filter(msg.getFormattedMessage()); + final Logger logger, + final @Nullable Level level, + final @Nullable Marker marker, + final @Nullable String msg, + final @Nullable Object @Nullable ... params) { + Objects.requireNonNull(logger, "The 'logger' argument must not be null."); + return filter(logger.getMessageFactory().newMessage(msg, params).getFormattedMessage()); } + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against a new message the logger's message-factory to create a new {@link Message} and perform + * the filter action against this filter's match text. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

+ *

+ * + * @throws NullPointerException if the {@code logger} argument is {@code null} + */ @Override - public Result filter(final LogEvent event) { - return filter(event.getMessage().getFormattedMessage()); + public Result filter( + final Logger logger, + final @Nullable Level level, + final @Nullable Marker marker, + final @Nullable Object message, + final @Nullable Throwable throwable) { + Objects.requireNonNull(logger, "The 'logger' argument must not be null."); + return filter(logger.getMessageFactory().newMessage(message).getFormattedMessage()); } - private Result filter(final String msg) { - return msg.contains(this.text) ? onMatch : onMismatch; + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against the provided {@link Message}'s + * formatted message. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

+ *

+ * + * @param logger the logger or {@code null} (unused) + * @param level the logging level or {@code null} (unused) + * @param marker the marker or {@code null} (unused) + * @param message the message + * @param throwable a throwable or {@code null} (unused) + * @return the filter result + * @throws NullPointerException if the {@code message} argument is {@code null} + */ + @Override + public Result filter( + final @Nullable Logger logger, + final @Nullable Level level, // unused + final @Nullable Marker marker, // unused + final Message message, + final @Nullable Throwable throwable /* unused */) { + Objects.requireNonNull(message, "The 'msg' argument must not be null."); + return filter(message.getFormattedMessage()); } + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against the given message formatted with the + * given parameters. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

+ *

+ * + * @throws NullPointerException if the {@code logger} argument is {@code null} + */ @Override public Result filter( - final Logger logger, final Level level, final Marker marker, final String msg, final Object p0) { - return filter(logger.getMessageFactory().newMessage(msg, p0).getFormattedMessage()); + final Logger logger, + final @Nullable Level level, // unused + final @Nullable Marker marker, // unused + final @Nullable String message, + final @Nullable Object p0) { + Objects.requireNonNull(logger, "The 'logger' argument must not be null."); + return filter(logger.getMessageFactory().newMessage(message, p0).getFormattedMessage()); } + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against the given message formatted with the + * given parameters. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

+ *

+ * + * @throws NullPointerException if the {@code logger} argument is {@code null} + */ @Override public Result filter( final Logger logger, - final Level level, - final Marker marker, - final String msg, - final Object p0, - final Object p1) { + final @Nullable Level level, // unused + final @Nullable Marker marker, // unused + final @Nullable String msg, + final @Nullable Object p0, + final @Nullable Object p1) { + Objects.requireNonNull(logger, "The 'logger' argument must not be null."); return filter(logger.getMessageFactory().newMessage(msg, p0, p1).getFormattedMessage()); } + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against the given message formatted with the + * given parameters. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

+ *

+ * + * @throws NullPointerException if the {@code logger} argument is {@code null} + */ @Override public Result filter( final Logger logger, - final Level level, - final Marker marker, - final String msg, - final Object p0, - final Object p1, - final Object p2) { + final @Nullable Level level, // unused + final @Nullable Marker marker, // unused + final @Nullable String msg, + final @Nullable Object p0, + final @Nullable Object p1, + final @Nullable Object p2) { + Objects.requireNonNull(logger, "The 'logger' argument must not be null."); return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2).getFormattedMessage()); } + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against the given message formatted with the + * given parameters. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

+ *

+ * + * @throws NullPointerException if the {@code logger} argument is {@code null} + */ @Override public Result filter( final Logger logger, - final Level level, - final Marker marker, - final String msg, - final Object p0, - final Object p1, - final Object p2, - final Object p3) { + final @Nullable Level level, // unused + final @Nullable Marker marker, // unused + final @Nullable String msg, + final @Nullable Object p0, + final @Nullable Object p1, + final @Nullable Object p2, + final @Nullable Object p3) { + Objects.requireNonNull(logger, "The 'logger' argument must not be null."); return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3).getFormattedMessage()); } + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against the given message formatted with the + * given parameters. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

+ *

+ * + * @throws NullPointerException if the {@code logger} argument is {@code null} + */ @Override public Result filter( final Logger logger, - final Level level, - final Marker marker, - final String msg, - final Object p0, - final Object p1, - final Object p2, - final Object p3, - final Object p4) { + final @Nullable Level level, // unused + final @Nullable Marker marker, // unused + final @Nullable String msg, + final @Nullable Object p0, + final @Nullable Object p1, + final @Nullable Object p2, + final @Nullable Object p3, + final @Nullable Object p4) { + Objects.requireNonNull(logger, "The 'logger' argument must not be null."); return filter( logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3, p4).getFormattedMessage()); } + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against the given message formatted with the + * given parameters. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

+ *

+ * + * @throws NullPointerException if the {@code logger} argument is {@code null} + */ @Override public Result filter( final Logger logger, - final Level level, - final Marker marker, - final String msg, - final Object p0, - final Object p1, - final Object p2, - final Object p3, - final Object p4, - final Object p5) { + final @Nullable Level level, + final @Nullable Marker marker, + final @Nullable String msg, + final @Nullable Object p0, + final @Nullable Object p1, + final @Nullable Object p2, + final @Nullable Object p3, + final @Nullable Object p4, + final @Nullable Object p5) { + Objects.requireNonNull(logger, "The 'logger' argument must not be null."); return filter(logger.getMessageFactory() .newMessage(msg, p0, p1, p2, p3, p4, p5) .getFormattedMessage()); } + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against the given message formatted with the + * given parameters. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

+ *

+ * + * @throws NullPointerException if the {@code logger} argument is {@code null} + */ @Override public Result filter( final Logger logger, - final Level level, - final Marker marker, - final String msg, - final Object p0, - final Object p1, - final Object p2, - final Object p3, - final Object p4, - final Object p5, - final Object p6) { + final @Nullable Level level, + final @Nullable Marker marker, + final @Nullable String msg, + final @Nullable Object p0, + final @Nullable Object p1, + final @Nullable Object p2, + final @Nullable Object p3, + final @Nullable Object p4, + final @Nullable Object p5, + final @Nullable Object p6) { + Objects.requireNonNull(logger, "The 'logger' argument must not be null."); return filter(logger.getMessageFactory() .newMessage(msg, p0, p1, p2, p3, p4, p5, p6) .getFormattedMessage()); } + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against the given message formatted with the + * given parameters. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

+ *

+ * + * @throws NullPointerException if the {@code logger} argument is {@code null} + */ @Override public Result filter( final Logger logger, - final Level level, - final Marker marker, - final String msg, - final Object p0, - final Object p1, - final Object p2, - final Object p3, - final Object p4, - final Object p5, - final Object p6, - final Object p7) { + final @Nullable Level level, // unused + final @Nullable Marker marker, // unused + final @Nullable String msg, + final @Nullable Object p0, + final @Nullable Object p1, + final @Nullable Object p2, + final @Nullable Object p3, + final @Nullable Object p4, + final @Nullable Object p5, + final @Nullable Object p6, + final @Nullable Object p7) { return filter(logger.getMessageFactory() .newMessage(msg, p0, p1, p2, p3, p4, p5, p6, p7) .getFormattedMessage()); } + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against the given message formatted with the + * given parameters. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

+ *

+ * + * @throws NullPointerException if the {@code logger} argument is {@code null} + */ @Override public Result filter( final Logger logger, - final Level level, - final Marker marker, - final String msg, - final Object p0, - final Object p1, - final Object p2, - final Object p3, - final Object p4, - final Object p5, - final Object p6, - final Object p7, - final Object p8) { + final @Nullable Level level, // unused + final @Nullable Marker marker, // unused + final @Nullable String msg, + final @Nullable Object p0, + final @Nullable Object p1, + final @Nullable Object p2, + final @Nullable Object p3, + final @Nullable Object p4, + final @Nullable Object p5, + final @Nullable Object p6, + final @Nullable Object p7, + final @Nullable Object p8) { + Objects.requireNonNull(logger, "The 'logger' argument must not be null."); return filter(logger.getMessageFactory() .newMessage(msg, p0, p1, p2, p3, p4, p5, p6, p7, p8) .getFormattedMessage()); } + /** + * {@inheritDoc} + *

+ * This implementation performs the filter evaluation against the given message formatted with the + * given parameters. + *

+ *

+ * The following method arguments are ignored by this filter method implementation: + *

+ *

+ * + * @throws NullPointerException if the {@code logger} argument is {@code null} + */ @Override public Result filter( final Logger logger, - final Level level, - final Marker marker, - final String msg, - final Object p0, - final Object p1, - final Object p2, - final Object p3, - final Object p4, - final Object p5, - final Object p6, - final Object p7, - final Object p8, - final Object p9) { + final @Nullable Level level, // unused + final @Nullable Marker marker, // unused + final @Nullable String msg, + final @Nullable Object p0, + final @Nullable Object p1, + final @Nullable Object p2, + final @Nullable Object p3, + final @Nullable Object p4, + final @Nullable Object p5, + final @Nullable Object p6, + final @Nullable Object p7, + final @Nullable Object p8, + final @Nullable Object p9) { + Objects.requireNonNull(logger, "The 'logger' argument must not be null."); return filter(logger.getMessageFactory() .newMessage(msg, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9) .getFormattedMessage()); } + /** + * Evaluates the filter result for the given message. + *

+ * If the given {@code message} is {@code null}, this method will always return the mismatch result. + *

+ * @param message the message to evaluate + * @return the configured match result if the message contains the string-match filter text; + * otherwise, the configured mismatch result + */ + private Result filter(final @Nullable String message) { + return (message != null && message.contains(this.text)) ? onMatch : onMismatch; + } + + /** {@inheritDoc} */ @Override public String toString() { return text; } + /** + * Creates a new builder instance. + * @return the new builder instance + */ @PluginFactory - public static StringMatchFilter.Builder newBuilder() { - return new StringMatchFilter.Builder(); + public static Builder newBuilder() { + return new Builder(); } + /** A {@link StringMatchFilter} builder implementation. */ public static class Builder extends AbstractFilterBuilder - implements org.apache.logging.log4j.core.util.Builder { + implements org.apache.logging.log4j.plugins.util.Builder { + @PluginBuilderAttribute - private String text = ""; + @Required(message = "No text provided for StringMatchFilter") + private @Nullable String text; + + /** Private constructor. */ + private Builder() { + super(); + } /** * Sets the text to search in event messages. * @param text the text to search in event messages. * @return this instance. */ - public StringMatchFilter.Builder setMatchString(final String text) { - this.text = text; + public Builder setText(final String text) { + this.text = Assert.requireNonEmpty(text, "The 'text' argument must not be null or empty."); return this; } + /** {@inheritDoc} */ @Override - public StringMatchFilter build() { - return new StringMatchFilter(this.text, this.getOnMatch(), this.getOnMismatch()); + public @Nullable StringMatchFilter build() { + + // validate the 'text' attribute + if (this.text == null) { + LOGGER.error("Unable to create StringMatchFilter: The 'text' attribute must be configured."); + return null; + } + + // build with *safety* to not throw unexpected exceptions + try { + return new StringMatchFilter(this); + } catch (final Exception ex) { + LOGGER.error("Unable to create StringMatchFilter: {}", ex.getMessage(), ex); + return null; + } } } } diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/Assert.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/Assert.java index 99d41e7b6ff..d37d6d00e2a 100644 --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/Assert.java +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/Assert.java @@ -18,6 +18,8 @@ import java.util.Collection; import java.util.Map; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * Utility class providing common validation logic. @@ -36,26 +38,23 @@ private Assert() {} * * * @param o value to check for emptiness - * @return true if the value is empty, false otherwise + * @return {@code true} if the value is empty; otherwise, {@code false} * @since 2.8 */ public static boolean isEmpty(final Object o) { if (o == null) { return true; - } - if (o instanceof CharSequence) { - return ((CharSequence) o).length() == 0; - } - if (o.getClass().isArray()) { + } else if (o instanceof CharSequence charSequence) { + return charSequence.isEmpty(); + } else if (o.getClass().isArray()) { return ((Object[]) o).length == 0; + } else if (o instanceof Collection collection) { + return collection.isEmpty(); + } else if (o instanceof Map map) { + return map.isEmpty(); + } else { + return false; } - if (o instanceof Collection) { - return ((Collection) o).isEmpty(); - } - if (o instanceof Map) { - return ((Map) o).isEmpty(); - } - return false; } /** @@ -77,7 +76,7 @@ public static boolean isNonEmpty(final Object o) { * @return the provided value if non-empty * @since 2.8 */ - public static T requireNonEmpty(final T value) { + public static T requireNonEmpty(final @Nullable T value) { return requireNonEmpty(value, ""); } @@ -90,7 +89,7 @@ public static T requireNonEmpty(final T value) { * @return the provided value if non-empty * @since 2.8 */ - public static T requireNonEmpty(final T value, final String message) { + public static @NonNull T requireNonEmpty(final @Nullable T value, final @Nullable String message) { if (isEmpty(value)) { throw new IllegalArgumentException(message); } diff --git a/src/changelog/.3.x.x/3509_fix_StringMatchFilter_Validation.xml b/src/changelog/.3.x.x/3509_fix_StringMatchFilter_Validation.xml new file mode 100644 index 00000000000..897c339bdcc --- /dev/null +++ b/src/changelog/.3.x.x/3509_fix_StringMatchFilter_Validation.xml @@ -0,0 +1,8 @@ + + + + Fix validation in StringMatchFilter. +