From fb1ba8ff8337fafafff8abf7f49776c2a7f06558 Mon Sep 17 00:00:00 2001 From: "James R. Perkins" Date: Tue, 13 Jun 2023 09:39:14 -0700 Subject: [PATCH] ** Initial commit of adding new log manager. Signed-off-by: James R. Perkins --- core-feature-pack/common/pom.xml | 5 + .../base/org/jboss/as/logging/main/module.xml | 1 + .../org/jboss/as/standalone/main/module.xml | 1 + .../base/org/jboss/logmanager/main/module.xml | 2 +- .../wildfly/core/logmanager/main/module.xml | 37 + .../base/org/wildfly/embedded/main/module.xml | 2 + logging/pom.xml | 11 + .../java/org/jboss/as/logging/Logging.java | 10 + .../jboss/as/logging/LoggingExtension.java | 8 +- .../jboss/as/logging/LoggingOperations.java | 4 +- .../LoggingProfileContextSelector.java | 98 --- .../as/logging/LoggingProfileOperations.java | 7 +- .../as/logging/LoggingResourceDefinition.java | 2 +- .../jboss/as/logging/LoggingSubsystemAdd.java | 2 +- .../AbstractLoggingDeploymentProcessor.java | 2 +- .../LoggingConfigDeploymentProcessor.java | 8 +- .../LoggingDeploymentResourceProcessor.java | 16 +- .../LoggingProfileDeploymentProcessor.java | 9 +- .../as/logging/logging/LoggingLogger.java | 3 +- .../logmanager/ConfigurationPersistence.java | 130 +-- .../logmanager/PropertyConfigurator.java | 511 +++++++++++ .../logmanager/WildFlyLogContextSelector.java | 198 ----- .../logging/AbstractLoggingSubsystemTest.java | 20 +- .../logging/AbstractOperationsTestCase.java | 9 +- .../logging/FormatterOperationsTestCase.java | 9 +- .../LoggingOperationsSubsystemTestCase.java | 11 +- .../LoggingSubsystemRollbackTestCase.java | 20 +- .../as/logging/LoggingSubsystemTestCase.java | 9 +- .../RootSubsystemOperationsTestCase.java | 2 +- logmanager/pom.xml | 53 ++ .../config/AbstractBasicConfiguration.java | 96 +++ .../config/AbstractPropertyConfiguration.java | 588 +++++++++++++ .../jboss/logmanager/config/ConfigAction.java | 39 + .../DelegatingContextConfiguration.java | 195 +++++ .../config/ErrorManagerConfiguration.java | 30 + .../config/ErrorManagerConfigurationImpl.java | 53 ++ .../config/FilterConfiguration.java | 29 + .../config/FilterConfigurationImpl.java | 53 ++ .../config/FormatterConfiguration.java | 30 + .../config/FormatterConfigurationImpl.java | 53 ++ .../config/HandlerConfiguration.java | 177 ++++ .../config/HandlerConfigurationImpl.java | 467 ++++++++++ .../config/HandlerContainingConfigurable.java | 69 ++ .../config/LogContextConfiguration.java | 815 ++++++++++++++++++ .../config/LoggerConfiguration.java | 192 +++++ .../config/LoggerConfigurationImpl.java | 383 ++++++++ .../logmanager/config/NamedConfigurable.java | 35 + .../logmanager/config/ObjectConfigurable.java | 53 ++ .../logmanager/config/ObjectProducer.java | 61 ++ .../logmanager/config/PojoConfiguration.java | 26 + .../config/PojoConfigurationImpl.java | 51 ++ .../config/PropertyConfigurable.java | 159 ++++ .../PropertyLogContextConfiguration.java | 380 ++++++++ .../logmanager/config/ValueExpression.java | 258 ++++++ .../config/ValueExpressionImpl.java | 54 ++ .../logmanager/config/WrappedAction.java | 90 ++ .../EmbeddedLogContextSelector.java | 55 ++ .../core/logmanager/ObjectBuilder.java | 353 ++++++++ .../core/logmanager/ObjectUpdater.java | 201 +++++ .../core/logmanager/ValueResolver.java | 126 +++ .../WildFlyConfiguratorFactory.java | 105 +++ .../logmanager/WildFlyDelayedHandler.java | 114 +++ .../WildFlyLogContextInitializer.java | 53 ++ .../logmanager/WildFlyLogContextSelector.java | 170 ++++ .../WildFlyLogContextSelectorImpl.java | 93 +- .../org.jboss.logmanager.ConfiguratorFactory | 20 + ...org.jboss.logmanager.LogContextInitializer | 20 + pom.xml | 14 +- 68 files changed, 6473 insertions(+), 487 deletions(-) create mode 100644 core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/core/logmanager/main/module.xml delete mode 100644 logging/src/main/java/org/jboss/as/logging/LoggingProfileContextSelector.java create mode 100644 logging/src/main/java/org/jboss/as/logging/logmanager/PropertyConfigurator.java delete mode 100644 logging/src/main/java/org/jboss/as/logging/logmanager/WildFlyLogContextSelector.java create mode 100644 logmanager/pom.xml create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/AbstractBasicConfiguration.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/AbstractPropertyConfiguration.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/ConfigAction.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/DelegatingContextConfiguration.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/ErrorManagerConfiguration.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/ErrorManagerConfigurationImpl.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/FilterConfiguration.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/FilterConfigurationImpl.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/FormatterConfiguration.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/FormatterConfigurationImpl.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/HandlerConfiguration.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/HandlerConfigurationImpl.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/HandlerContainingConfigurable.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/LogContextConfiguration.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/LoggerConfiguration.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/LoggerConfigurationImpl.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/NamedConfigurable.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/ObjectConfigurable.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/ObjectProducer.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/PojoConfiguration.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/PojoConfigurationImpl.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/PropertyConfigurable.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/PropertyLogContextConfiguration.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/ValueExpression.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/ValueExpressionImpl.java create mode 100644 logmanager/src/main/java/org/jboss/logmanager/config/WrappedAction.java create mode 100644 logmanager/src/main/java/org/wildfly/core/logmanager/EmbeddedLogContextSelector.java create mode 100644 logmanager/src/main/java/org/wildfly/core/logmanager/ObjectBuilder.java create mode 100644 logmanager/src/main/java/org/wildfly/core/logmanager/ObjectUpdater.java create mode 100644 logmanager/src/main/java/org/wildfly/core/logmanager/ValueResolver.java create mode 100644 logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyConfiguratorFactory.java create mode 100644 logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyDelayedHandler.java create mode 100644 logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyLogContextInitializer.java create mode 100644 logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyLogContextSelector.java rename {logging/src/main/java/org/jboss/as/logging => logmanager/src/main/java/org/wildfly/core}/logmanager/WildFlyLogContextSelectorImpl.java (63%) create mode 100644 logmanager/src/main/resources/META-INF/services/org.jboss.logmanager.ConfiguratorFactory create mode 100644 logmanager/src/main/resources/META-INF/services/org.jboss.logmanager.LogContextInitializer diff --git a/core-feature-pack/common/pom.xml b/core-feature-pack/common/pom.xml index f00388763fa..27e2600d587 100644 --- a/core-feature-pack/common/pom.xml +++ b/core-feature-pack/common/pom.xml @@ -761,6 +761,11 @@ + + org.wildfly.core + wildfly-logmanager + + org.wildfly.core wildfly-management-client-content diff --git a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/logging/main/module.xml b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/logging/main/module.xml index 2bf44211d94..d1e6fecf9fb 100644 --- a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/logging/main/module.xml +++ b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/logging/main/module.xml @@ -50,6 +50,7 @@ + diff --git a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/standalone/main/module.xml b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/standalone/main/module.xml index 8cdeba25885..50252b4f908 100644 --- a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/standalone/main/module.xml +++ b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/standalone/main/module.xml @@ -41,6 +41,7 @@ + diff --git a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/logmanager/main/module.xml b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/logmanager/main/module.xml index 658884cf1e1..ef9cb21c6a3 100644 --- a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/logmanager/main/module.xml +++ b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/logmanager/main/module.xml @@ -32,7 +32,7 @@ - + diff --git a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/core/logmanager/main/module.xml b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/core/logmanager/main/module.xml new file mode 100644 index 00000000000..46f95da0b36 --- /dev/null +++ b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/core/logmanager/main/module.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/embedded/main/module.xml b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/embedded/main/module.xml index 25f08d17617..dc73d35534f 100644 --- a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/embedded/main/module.xml +++ b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/embedded/main/module.xml @@ -39,6 +39,7 @@ + @@ -49,5 +50,6 @@ + diff --git a/logging/pom.xml b/logging/pom.xml index 81665ec4ddf..ecfc36793aa 100644 --- a/logging/pom.xml +++ b/logging/pom.xml @@ -240,6 +240,12 @@ + + + org.eclipse.parsson + parsson + test + @@ -248,6 +254,11 @@ test + + org.wildfly.core + wildfly-logmanager + + org.glassfish diff --git a/logging/src/main/java/org/jboss/as/logging/Logging.java b/logging/src/main/java/org/jboss/as/logging/Logging.java index c3b4a4234b8..1cc5e067a51 100644 --- a/logging/src/main/java/org/jboss/as/logging/Logging.java +++ b/logging/src/main/java/org/jboss/as/logging/Logging.java @@ -28,6 +28,8 @@ import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.registry.AttributeAccess.Flag; import org.jboss.dmr.ModelNode; +import org.jboss.logmanager.LogContext; +import org.wildfly.core.logmanager.WildFlyLogContextSelector; /** * A set of utilities for the logging subsystem. @@ -40,6 +42,14 @@ public final class Logging { private Logging() { } + // TODO (jrp) we should handle this better + public static LogContext getLogContext(final String profileName) { + if (profileName != null) { + return WildFlyLogContextSelector.getContextSelector().getOrCreateProfile(profileName); + } + return WildFlyLogContextSelector.getContextSelector().getLogContext(); + } + /** * Checks to see within the flags if a restart of any kind is required. * diff --git a/logging/src/main/java/org/jboss/as/logging/LoggingExtension.java b/logging/src/main/java/org/jboss/as/logging/LoggingExtension.java index e73b656911b..5461afa5f6a 100644 --- a/logging/src/main/java/org/jboss/as/logging/LoggingExtension.java +++ b/logging/src/main/java/org/jboss/as/logging/LoggingExtension.java @@ -75,7 +75,7 @@ import org.jboss.as.logging.loggers.LoggerResourceDefinition; import org.jboss.as.logging.loggers.RootLoggerResourceDefinition; import org.jboss.as.logging.logging.LoggingLogger; -import org.jboss.as.logging.logmanager.WildFlyLogContextSelector; +import org.wildfly.core.logmanager.WildFlyLogContextSelector; import org.jboss.as.logging.stdio.LogContextStdioContextSelector; import org.jboss.dmr.ModelNode; import org.jboss.logmanager.LogContext; @@ -163,9 +163,9 @@ public void initialize(final ExtensionContext context) { if (embedded) { // Use the standard WildFlyLogContextSelector if we should wrap the current log context if (getBooleanProperty(EMBEDDED_PROPERTY, true)) { - contextSelector = WildFlyLogContextSelector.Factory.createEmbedded(); + contextSelector = WildFlyLogContextSelector.getEmbeddedContextSelector(); } else { - contextSelector = WildFlyLogContextSelector.Factory.create(); + contextSelector = WildFlyLogContextSelector.getContextSelector(); } } else { @@ -186,7 +186,7 @@ public void initialize(final ExtensionContext context) { throw LoggingLogger.ROOT_LOGGER.extensionNotInitialized(); } } - contextSelector = WildFlyLogContextSelector.Factory.create(); + contextSelector = WildFlyLogContextSelector.getContextSelector(); // Install STDIO context selector StdioContext.setStdioContextSelector(new LogContextStdioContextSelector(StdioContext.getStdioContext())); diff --git a/logging/src/main/java/org/jboss/as/logging/LoggingOperations.java b/logging/src/main/java/org/jboss/as/logging/LoggingOperations.java index 1e551e2e334..f979981eb03 100644 --- a/logging/src/main/java/org/jboss/as/logging/LoggingOperations.java +++ b/logging/src/main/java/org/jboss/as/logging/LoggingOperations.java @@ -92,7 +92,7 @@ private static ConfigurationPersistence getOrCreateConfigurationPersistence(fina final PathAddress address = context.getCurrentAddress(); final ConfigurationPersistence configurationPersistence; if (LoggingProfileOperations.isLoggingProfileAddress(address)) { - final LogContext logContext = LoggingProfileContextSelector.getInstance().getOrCreate(LoggingProfileOperations.getLoggingProfileName(address)); + final LogContext logContext = Logging.getLogContext(LoggingProfileOperations.getLoggingProfileName(address)); configurationPersistence = ConfigurationPersistence.getOrCreateConfigurationPersistence(logContext); } else { configurationPersistence = ConfigurationPersistence.getOrCreateConfigurationPersistence(); @@ -104,7 +104,7 @@ private static ConfigurationPersistence getConfigurationPersistence(final Operat final PathAddress address = context.getCurrentAddress(); final LogContext logContext; if (LoggingProfileOperations.isLoggingProfileAddress(address)) { - logContext = LoggingProfileContextSelector.getInstance().get(LoggingProfileOperations.getLoggingProfileName(address)); + logContext = Logging.getLogContext(LoggingProfileOperations.getLoggingProfileName(address)); } else { logContext = LogContext.getLogContext(); } diff --git a/logging/src/main/java/org/jboss/as/logging/LoggingProfileContextSelector.java b/logging/src/main/java/org/jboss/as/logging/LoggingProfileContextSelector.java deleted file mode 100644 index 9d72839f205..00000000000 --- a/logging/src/main/java/org/jboss/as/logging/LoggingProfileContextSelector.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * JBoss, Home of Professional Open Source. - * Copyright 2012, Red Hat, Inc., and individual contributors - * as indicated by the @author tags. See the copyright.txt file in the - * distribution for a full listing of individual contributors. - * - * This is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. - */ - -package org.jboss.as.logging; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import org.jboss.logmanager.LogContext; - -/** - * @author James R. Perkins - */ -public final class LoggingProfileContextSelector { - private static final LoggingProfileContextSelector INSTANCE = new LoggingProfileContextSelector(); - - private final ConcurrentMap profileContexts = new ConcurrentHashMap<>(); - - private LoggingProfileContextSelector() { - - } - - public static LoggingProfileContextSelector getInstance() { - return INSTANCE; - } - - /** - * Get or create the log context based on the logging profile. - * - * @param loggingProfile the logging profile to get or create the log context for - * - * @return the log context that was found or a new log context - */ - protected LogContext getOrCreate(final String loggingProfile) { - LogContext result = profileContexts.get(loggingProfile); - if (result == null) { - result = LogContext.create(); - final LogContext current = profileContexts.putIfAbsent(loggingProfile, result); - if (current != null) { - result = current; - } - } - return result; - } - - /** - * Returns the log context associated with the logging profile or {@code null} if the logging profile does not have - * an associated log context. - * - * @param loggingProfile the logging profile associated with the log context - * - * @return the log context or {@code null} if the logging profile is not associated with a log context - */ - public LogContext get(final String loggingProfile) { - return profileContexts.get(loggingProfile); - } - - /** - * Checks to see if the logging profile has a log context associated with it. - * - * @param loggingProfile the logging profile to check - * - * @return {@code true} if the logging profile has an associated log context, otherwise {@code false} - */ - public boolean exists(final String loggingProfile) { - return profileContexts.containsKey(loggingProfile); - } - - /** - * Removes the associated log context from the logging profile. - * - * @param loggingProfile the logging profile associated with the log context - * - * @return the log context that was removed or {@code null} if there was no log context associated - */ - public LogContext remove(final String loggingProfile) { - return profileContexts.remove(loggingProfile); - } -} diff --git a/logging/src/main/java/org/jboss/as/logging/LoggingProfileOperations.java b/logging/src/main/java/org/jboss/as/logging/LoggingProfileOperations.java index aba3b9cd407..a935ebe4b90 100644 --- a/logging/src/main/java/org/jboss/as/logging/LoggingProfileOperations.java +++ b/logging/src/main/java/org/jboss/as/logging/LoggingProfileOperations.java @@ -38,6 +38,7 @@ import org.jboss.dmr.ModelNode; import org.jboss.logmanager.LogContext; import org.jboss.logmanager.config.LogContextConfiguration; +import org.wildfly.core.logmanager.WildFlyLogContextSelector; /** * @author James R. Perkins @@ -76,8 +77,8 @@ protected void performRuntime(final OperationContext context, final ModelNode op final PathAddress address = PathAddress.pathAddress(operation.get(ModelDescriptionConstants.OP_ADDR)); // Get the logging profile final String loggingProfile = getLoggingProfileName(address); - final LoggingProfileContextSelector contextSelector = LoggingProfileContextSelector.getInstance(); - final LogContext logContext = contextSelector.get(loggingProfile); + final WildFlyLogContextSelector contextSelector = WildFlyLogContextSelector.getContextSelector(); + final LogContext logContext = contextSelector.getProfileContext(loggingProfile); if (logContext != null) { context.addStep(new OperationStepHandler() { @Override @@ -113,7 +114,7 @@ public void execute(final OperationContext context, final ModelNode operation) { @Override public void handleResult(final ResultAction resultAction, final OperationContext context, final ModelNode operation) { if (resultAction == ResultAction.KEEP) { - contextSelector.remove(loggingProfile); + contextSelector.removeProfileContext(loggingProfile); } else if (configuration != null) { context.revertReloadRequired(); } diff --git a/logging/src/main/java/org/jboss/as/logging/LoggingResourceDefinition.java b/logging/src/main/java/org/jboss/as/logging/LoggingResourceDefinition.java index b2fbf58f859..dd65a3fd177 100644 --- a/logging/src/main/java/org/jboss/as/logging/LoggingResourceDefinition.java +++ b/logging/src/main/java/org/jboss/as/logging/LoggingResourceDefinition.java @@ -65,7 +65,7 @@ import org.jboss.as.controller.transform.description.RejectAttributeChecker; import org.jboss.as.controller.transform.description.ResourceTransformationDescriptionBuilder; import org.jboss.as.logging.logging.LoggingLogger; -import org.jboss.as.logging.logmanager.WildFlyLogContextSelector; +import org.wildfly.core.logmanager.WildFlyLogContextSelector; import org.jboss.as.server.ServerEnvironment; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; diff --git a/logging/src/main/java/org/jboss/as/logging/LoggingSubsystemAdd.java b/logging/src/main/java/org/jboss/as/logging/LoggingSubsystemAdd.java index 6b17698babb..ae6b0d467b6 100644 --- a/logging/src/main/java/org/jboss/as/logging/LoggingSubsystemAdd.java +++ b/logging/src/main/java/org/jboss/as/logging/LoggingSubsystemAdd.java @@ -56,7 +56,7 @@ import org.jboss.as.logging.loggers.RootLoggerResourceDefinition; import org.jboss.as.logging.logging.LoggingLogger; import org.jboss.as.logging.logmanager.ConfigurationPersistence; -import org.jboss.as.logging.logmanager.WildFlyLogContextSelector; +import org.wildfly.core.logmanager.WildFlyLogContextSelector; import org.jboss.as.server.AbstractDeploymentChainStep; import org.jboss.as.server.DeploymentProcessorTarget; import org.jboss.as.server.deployment.Phase; diff --git a/logging/src/main/java/org/jboss/as/logging/deployments/AbstractLoggingDeploymentProcessor.java b/logging/src/main/java/org/jboss/as/logging/deployments/AbstractLoggingDeploymentProcessor.java index c44e7518493..14b0dcfea11 100644 --- a/logging/src/main/java/org/jboss/as/logging/deployments/AbstractLoggingDeploymentProcessor.java +++ b/logging/src/main/java/org/jboss/as/logging/deployments/AbstractLoggingDeploymentProcessor.java @@ -29,7 +29,7 @@ import java.util.List; import org.jboss.as.logging.logging.LoggingLogger; -import org.jboss.as.logging.logmanager.WildFlyLogContextSelector; +import org.wildfly.core.logmanager.WildFlyLogContextSelector; import org.jboss.as.server.deployment.AttachmentKey; import org.jboss.as.server.deployment.Attachments; import org.jboss.as.server.deployment.DeploymentPhaseContext; diff --git a/logging/src/main/java/org/jboss/as/logging/deployments/LoggingConfigDeploymentProcessor.java b/logging/src/main/java/org/jboss/as/logging/deployments/LoggingConfigDeploymentProcessor.java index 1a44fb1acbc..cb825b35d93 100644 --- a/logging/src/main/java/org/jboss/as/logging/deployments/LoggingConfigDeploymentProcessor.java +++ b/logging/src/main/java/org/jboss/as/logging/deployments/LoggingConfigDeploymentProcessor.java @@ -34,7 +34,7 @@ import java.util.Set; import org.jboss.as.logging.logging.LoggingLogger; -import org.jboss.as.logging.logmanager.WildFlyLogContextSelector; +import org.wildfly.core.logmanager.WildFlyLogContextSelector; import org.jboss.as.server.deployment.Attachments; import org.jboss.as.server.deployment.DeploymentPhaseContext; import org.jboss.as.server.deployment.DeploymentUnit; @@ -42,7 +42,7 @@ import org.jboss.as.server.deployment.DeploymentUnitProcessor; import org.jboss.as.server.deployment.module.ResourceRoot; import org.jboss.logmanager.LogContext; -import org.jboss.logmanager.PropertyConfigurator; +import org.jboss.logmanager.config.LogContextConfiguration; import org.jboss.modules.Module; import org.jboss.vfs.VirtualFile; import org.jboss.vfs.VirtualFileFilter; @@ -217,9 +217,7 @@ private LoggingConfigurationService configure(final ResourceRoot root, final Vir final ClassLoader current = WildFlySecurityManager.getCurrentContextClassLoaderPrivileged(); try { WildFlySecurityManager.setCurrentContextClassLoaderPrivileged(classLoader); - final PropertyConfigurator propertyConfigurator = new PropertyConfigurator(logContext); - propertyConfigurator.configure(properties); - return new LoggingConfigurationService(propertyConfigurator.getLogContextConfiguration(), resolveRelativePath(root, configFile)); + return new LoggingConfigurationService(LogContextConfiguration.create(logContext, properties), resolveRelativePath(root, configFile)); } finally { WildFlySecurityManager.setCurrentContextClassLoaderPrivileged(current); } diff --git a/logging/src/main/java/org/jboss/as/logging/deployments/LoggingDeploymentResourceProcessor.java b/logging/src/main/java/org/jboss/as/logging/deployments/LoggingDeploymentResourceProcessor.java index 2b7561b00a1..59dce35e518 100644 --- a/logging/src/main/java/org/jboss/as/logging/deployments/LoggingDeploymentResourceProcessor.java +++ b/logging/src/main/java/org/jboss/as/logging/deployments/LoggingDeploymentResourceProcessor.java @@ -22,7 +22,6 @@ package org.jboss.as.logging.deployments; -import org.jboss.as.logging.CommonAttributes; import org.jboss.as.logging.deployments.resources.LoggingDeploymentResources; import org.jboss.as.server.deployment.AttachmentKey; import org.jboss.as.server.deployment.Attachments; @@ -30,9 +29,6 @@ import org.jboss.as.server.deployment.DeploymentResourceSupport; import org.jboss.as.server.deployment.DeploymentUnit; import org.jboss.as.server.deployment.DeploymentUnitProcessor; -import org.jboss.logmanager.Configurator; -import org.jboss.logmanager.LogContext; -import org.jboss.logmanager.PropertyConfigurator; import org.jboss.logmanager.config.LogContextConfiguration; import org.jboss.modules.Module; import org.wildfly.security.manager.WildFlySecurityManager; @@ -69,14 +65,7 @@ public final void deploy(final DeploymentPhaseContext phaseContext) { final ClassLoader current = WildFlySecurityManager.getCurrentContextClassLoaderPrivileged(); try { WildFlySecurityManager.setCurrentContextClassLoaderPrivileged(module.getClassLoader()); - LogContextConfiguration logContextConfiguration = null; - final LogContext logContext = LogContext.getLogContext(); - final Configurator configurator = logContext.getAttachment(CommonAttributes.ROOT_LOGGER_NAME, Configurator.ATTACHMENT_KEY); - if (configurator instanceof LogContextConfiguration) { - logContextConfiguration = (LogContextConfiguration) configurator; - } else if (configurator instanceof PropertyConfigurator) { - logContextConfiguration = ((PropertyConfigurator) configurator).getLogContextConfiguration(); - } + LogContextConfiguration logContextConfiguration = LogContextConfiguration.getInstance(); loggingConfigurationService = new LoggingConfigurationService(logContextConfiguration, "default"); } finally { WildFlySecurityManager.setCurrentContextClassLoaderPrivileged(current); @@ -87,7 +76,8 @@ public final void deploy(final DeploymentPhaseContext phaseContext) { // Register the resources LoggingDeploymentResources.registerDeploymentResource(deploymentResourceSupport, loggingConfigurationService); phaseContext.getServiceTarget() - .addService(deploymentUnit.getServiceName().append("logging", "configuration"), loggingConfigurationService) + .addService(deploymentUnit.getServiceName() + .append("logging", "configuration"), loggingConfigurationService) .install(); } } diff --git a/logging/src/main/java/org/jboss/as/logging/deployments/LoggingProfileDeploymentProcessor.java b/logging/src/main/java/org/jboss/as/logging/deployments/LoggingProfileDeploymentProcessor.java index 4e4da97184d..42090fda778 100644 --- a/logging/src/main/java/org/jboss/as/logging/deployments/LoggingProfileDeploymentProcessor.java +++ b/logging/src/main/java/org/jboss/as/logging/deployments/LoggingProfileDeploymentProcessor.java @@ -26,9 +26,7 @@ import java.util.jar.Manifest; import org.jboss.as.logging.logging.LoggingLogger; -import org.jboss.as.logging.LoggingProfileContextSelector; import org.jboss.as.logging.logmanager.ConfigurationPersistence; -import org.jboss.as.logging.logmanager.WildFlyLogContextSelector; import org.jboss.as.server.deployment.Attachments; import org.jboss.as.server.deployment.DeploymentPhaseContext; import org.jboss.as.server.deployment.DeploymentUnit; @@ -37,6 +35,7 @@ import org.jboss.as.server.deployment.module.ResourceRoot; import org.jboss.logmanager.LogContext; import org.jboss.modules.Module; +import org.wildfly.core.logmanager.WildFlyLogContextSelector; /** * @author James R. Perkins @@ -56,11 +55,11 @@ protected void processDeployment(final DeploymentPhaseContext phaseContext, fina final String loggingProfile = findLoggingProfile(root); if (loggingProfile != null) { // Get the profile logging context - final LoggingProfileContextSelector loggingProfileContext = LoggingProfileContextSelector.getInstance(); - if (loggingProfileContext.exists(loggingProfile)) { + final WildFlyLogContextSelector loggingProfileContext = WildFlyLogContextSelector.getContextSelector(); + if (loggingProfileContext.profileContextExists(loggingProfile)) { // Get the module final Module module = deploymentUnit.getAttachment(Attachments.MODULE); - final LogContext logContext = loggingProfileContext.get(loggingProfile); + final LogContext logContext = loggingProfileContext.getProfileContext(loggingProfile); LoggingLogger.ROOT_LOGGER.tracef("Registering log context '%s' on '%s' for profile '%s'", logContext, root, loggingProfile); registerLogContext(deploymentUnit, module, logContext); loggingConfigurationService = new LoggingConfigurationService(ConfigurationPersistence.getConfigurationPersistence(logContext), "profile-" + loggingProfile); diff --git a/logging/src/main/java/org/jboss/as/logging/logging/LoggingLogger.java b/logging/src/main/java/org/jboss/as/logging/logging/LoggingLogger.java index b9a0f077500..b4607c76cb9 100644 --- a/logging/src/main/java/org/jboss/as/logging/logging/LoggingLogger.java +++ b/logging/src/main/java/org/jboss/as/logging/logging/LoggingLogger.java @@ -44,7 +44,6 @@ import org.jboss.logging.annotations.Once; import org.jboss.logging.annotations.Transform; import org.jboss.logging.annotations.Transform.TransformType; -import org.jboss.logmanager.Configurator; import org.jboss.logmanager.LogContext; /** @@ -158,7 +157,7 @@ public interface LoggingLogger extends BasicLogger { */ @LogMessage(level = WARN) @Message(id = 13, value = "A configurator class, '%s', is not a known configurator and will be replaced.") - void replacingConfigurator(@Transform(TransformType.GET_CLASS) Configurator c); + void replacingConfigurator(@Transform(TransformType.GET_CLASS) Object c); /** * Logs an error message indicating the {@link org.jboss.logmanager.LogContext log context} associated with the diff --git a/logging/src/main/java/org/jboss/as/logging/logmanager/ConfigurationPersistence.java b/logging/src/main/java/org/jboss/as/logging/logmanager/ConfigurationPersistence.java index 5774e4eab14..83f4bbd0e3e 100644 --- a/logging/src/main/java/org/jboss/as/logging/logmanager/ConfigurationPersistence.java +++ b/logging/src/main/java/org/jboss/as/logging/logmanager/ConfigurationPersistence.java @@ -26,19 +26,13 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.nio.channels.FileLock; import java.nio.charset.StandardCharsets; import java.util.List; import org.jboss.as.controller.OperationContext; -import org.jboss.as.logging.CommonAttributes; import org.jboss.as.logging.logging.LoggingLogger; import org.jboss.as.logging.resolvers.FileResolver; -import org.jboss.logmanager.Configurator; import org.jboss.logmanager.LogContext; -import org.jboss.logmanager.Logger; -import org.jboss.logmanager.PropertyConfigurator; import org.jboss.logmanager.config.ErrorManagerConfiguration; import org.jboss.logmanager.config.FilterConfiguration; import org.jboss.logmanager.config.FormatterConfiguration; @@ -50,27 +44,23 @@ /** * Persists the {@literal logging.properties} file. *

- * Commits any changes remaining on the {@link org.jboss.logmanager.config.LogContextConfiguration} and writes out the + * Commits any changes remaining on the {@link LogContextConfiguration} and writes out the * configuration to the configuration file. * * @author James R. Perkins */ -public class ConfigurationPersistence implements Configurator, LogContextConfiguration { +// TODO (jrp) re-write all of this +public class ConfigurationPersistence extends LogContextConfiguration { private static final Object LOCK = new Object(); private static final String PROPERTIES_FILE = "logging.properties"; private static final byte[] NOTE_MESSAGE = String.format("# Note this file has been generated and will be overwritten if a%n" + "# logging subsystem has been defined in the XML configuration.%n%n").getBytes(StandardCharsets.UTF_8); - private final PropertyConfigurator config; - private final LogContextConfiguration delegate; + private final LogContextConfiguration config; - private ConfigurationPersistence(final LogContext logContext) { - this(new PropertyConfigurator(logContext)); - } - - private ConfigurationPersistence(final PropertyConfigurator config) { + private ConfigurationPersistence(final LogContextConfiguration config) { + super(config); this.config = config; - delegate = config.getLogContextConfiguration(); } /** @@ -90,32 +80,8 @@ public static ConfigurationPersistence getOrCreateConfigurationPersistence() { * @return the property configurator */ public static ConfigurationPersistence getOrCreateConfigurationPersistence(final LogContext logContext) { - final Logger root = logContext.getLogger(CommonAttributes.ROOT_LOGGER_NAME); - final ConfigurationPersistence result; - synchronized (LOCK) { - Configurator configurator = root.getAttachment(Configurator.ATTACHMENT_KEY); - if (configurator == null) { - configurator = new ConfigurationPersistence(logContext); - Configurator existing = root.attachIfAbsent(Configurator.ATTACHMENT_KEY, configurator); - if (existing != null) { - configurator = existing; - } - } - if (configurator instanceof ConfigurationPersistence) { - // We have the correct configurator - result = (ConfigurationPersistence) configurator; - } else if (configurator instanceof PropertyConfigurator) { - // Create a new configurator delegating to the configurator found - result = new ConfigurationPersistence((PropertyConfigurator) configurator); - root.attach(Configurator.ATTACHMENT_KEY, result); - } else { - // An unknown configurator, log a warning and replace - LoggingLogger.ROOT_LOGGER.replacingConfigurator(configurator); - result = new ConfigurationPersistence(logContext); - root.attach(Configurator.ATTACHMENT_KEY, result); - } - } - return result; + // TODO (jrp) fix this + return getConfigurationPersistence(logContext); } /** @@ -128,7 +94,8 @@ public static ConfigurationPersistence getOrCreateConfigurationPersistence(final */ public static ConfigurationPersistence getConfigurationPersistence(final LogContext logContext) { if (logContext == null) return null; - return (ConfigurationPersistence) logContext.getAttachment(CommonAttributes.ROOT_LOGGER_NAME, Configurator.ATTACHMENT_KEY); + // TODO (jrp) fix this + return new ConfigurationPersistence(LogContextConfiguration.getInstance(logContext)); } private static void safeClose(final Closeable closeable) { @@ -139,206 +106,199 @@ private static void safeClose(final Closeable closeable) { } } - @Override - public void configure(final InputStream inputStream) throws IOException { - synchronized (LOCK) { - config.configure(inputStream); - } - } - @Override public LogContext getLogContext() { synchronized (LOCK) { - return delegate.getLogContext(); + return config.getLogContext(); } } @Override public LoggerConfiguration addLoggerConfiguration(final String loggerName) { synchronized (LOCK) { - return delegate.addLoggerConfiguration(loggerName); + return config.addLoggerConfiguration(loggerName); } } @Override public boolean removeLoggerConfiguration(final String loggerName) { synchronized (LOCK) { - return delegate.removeLoggerConfiguration(loggerName); + return config.removeLoggerConfiguration(loggerName); } } @Override public LoggerConfiguration getLoggerConfiguration(final String loggerName) { synchronized (LOCK) { - return delegate.getLoggerConfiguration(loggerName); + return config.getLoggerConfiguration(loggerName); } } @Override public List getLoggerNames() { synchronized (LOCK) { - return delegate.getLoggerNames(); + return config.getLoggerNames(); } } @Override public HandlerConfiguration addHandlerConfiguration(final String moduleName, final String className, final String handlerName, final String... constructorProperties) { synchronized (LOCK) { - return delegate.addHandlerConfiguration(moduleName, className, handlerName, constructorProperties); + return config.addHandlerConfiguration(moduleName, className, handlerName, constructorProperties); } } @Override public boolean removeHandlerConfiguration(final String handlerName) { synchronized (LOCK) { - return delegate.removeHandlerConfiguration(handlerName); + return config.removeHandlerConfiguration(handlerName); } } @Override public HandlerConfiguration getHandlerConfiguration(final String handlerName) { synchronized (LOCK) { - return delegate.getHandlerConfiguration(handlerName); + return config.getHandlerConfiguration(handlerName); } } @Override public List getHandlerNames() { synchronized (LOCK) { - return delegate.getHandlerNames(); + return config.getHandlerNames(); } } @Override public FormatterConfiguration addFormatterConfiguration(final String moduleName, final String className, final String formatterName, final String... constructorProperties) { synchronized (LOCK) { - return delegate.addFormatterConfiguration(moduleName, className, formatterName, constructorProperties); + return config.addFormatterConfiguration(moduleName, className, formatterName, constructorProperties); } } @Override public boolean removeFormatterConfiguration(final String formatterName) { synchronized (LOCK) { - return delegate.removeFormatterConfiguration(formatterName); + return config.removeFormatterConfiguration(formatterName); } } @Override public FormatterConfiguration getFormatterConfiguration(final String formatterName) { synchronized (LOCK) { - return delegate.getFormatterConfiguration(formatterName); + return config.getFormatterConfiguration(formatterName); } } @Override public List getFormatterNames() { synchronized (LOCK) { - return delegate.getFormatterNames(); + return config.getFormatterNames(); } } @Override public FilterConfiguration addFilterConfiguration(final String moduleName, final String className, final String filterName, final String... constructorProperties) { synchronized (LOCK) { - return delegate.addFilterConfiguration(moduleName, className, filterName, constructorProperties); + return config.addFilterConfiguration(moduleName, className, filterName, constructorProperties); } } @Override public boolean removeFilterConfiguration(final String filterName) { synchronized (LOCK) { - return delegate.removeFilterConfiguration(filterName); + return config.removeFilterConfiguration(filterName); } } @Override public FilterConfiguration getFilterConfiguration(final String filterName) { synchronized (LOCK) { - return delegate.getFilterConfiguration(filterName); + return config.getFilterConfiguration(filterName); } } @Override public List getFilterNames() { synchronized (LOCK) { - return delegate.getFilterNames(); + return config.getFilterNames(); } } @Override public ErrorManagerConfiguration addErrorManagerConfiguration(final String moduleName, final String className, final String errorManagerName, final String... constructorProperties) { synchronized (LOCK) { - return delegate.addErrorManagerConfiguration(moduleName, className, errorManagerName, constructorProperties); + return config.addErrorManagerConfiguration(moduleName, className, errorManagerName, constructorProperties); } } @Override public boolean removeErrorManagerConfiguration(final String errorManagerName) { synchronized (LOCK) { - return delegate.removeErrorManagerConfiguration(errorManagerName); + return config.removeErrorManagerConfiguration(errorManagerName); } } @Override public ErrorManagerConfiguration getErrorManagerConfiguration(final String errorManagerName) { synchronized (LOCK) { - return delegate.getErrorManagerConfiguration(errorManagerName); + return config.getErrorManagerConfiguration(errorManagerName); } } @Override public List getErrorManagerNames() { synchronized (LOCK) { - return delegate.getErrorManagerNames(); + return config.getErrorManagerNames(); } } @Override public void prepare() { synchronized (LOCK) { - delegate.prepare(); + config.prepare(); } } @Override public PojoConfiguration addPojoConfiguration(final String moduleName, final String className, final String pojoName, final String... constructorProperties) { synchronized (LOCK) { - return delegate.addPojoConfiguration(moduleName, className, pojoName, constructorProperties); + return config.addPojoConfiguration(moduleName, className, pojoName, constructorProperties); } } @Override public boolean removePojoConfiguration(final String pojoName) { synchronized (LOCK) { - return delegate.removePojoConfiguration(pojoName); + return config.removePojoConfiguration(pojoName); } } @Override public PojoConfiguration getPojoConfiguration(final String pojoName) { synchronized (LOCK) { - return delegate.getPojoConfiguration(pojoName); + return config.getPojoConfiguration(pojoName); } } @Override public List getPojoNames() { synchronized (LOCK) { - return delegate.getPojoNames(); + return config.getPojoNames(); } } @Override public void commit() { synchronized (LOCK) { - delegate.commit(); + config.commit(); } } @Override public void forget() { synchronized (LOCK) { - delegate.forget(); + config.forget(); } } @@ -399,17 +359,9 @@ public void writeConfiguration(final OperationContext context) { FileOutputStream out = null; try { out = new FileOutputStream(configFile); - final FileLock lock = out.getChannel().lock(); - try { - out.write(NOTE_MESSAGE); - config.writeConfiguration(out); - } finally { - // The write should close the stream which would release the lock this check ensures the - // lock will be released - if (lock.isValid()) { - lock.release(); - } - } + out.write(NOTE_MESSAGE); + final PropertyConfigurator config = new PropertyConfigurator(this.config); + config.writeConfiguration(out, false); LoggingLogger.ROOT_LOGGER.tracef("Logging configuration file '%s' successfully written.", configFile.getAbsolutePath()); } catch (IOException e) { throw LoggingLogger.ROOT_LOGGER.failedToWriteConfigurationFile(e, configFile); diff --git a/logging/src/main/java/org/jboss/as/logging/logmanager/PropertyConfigurator.java b/logging/src/main/java/org/jboss/as/logging/logmanager/PropertyConfigurator.java new file mode 100644 index 00000000000..be7ea521757 --- /dev/null +++ b/logging/src/main/java/org/jboss/as/logging/logmanager/PropertyConfigurator.java @@ -0,0 +1,511 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.as.logging.logmanager; + +import java.io.BufferedWriter; +import java.io.Closeable; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.jboss.logmanager.StandardOutputStreams; +import org.jboss.logmanager.config.ErrorManagerConfiguration; +import org.jboss.logmanager.config.FilterConfiguration; +import org.jboss.logmanager.config.FormatterConfiguration; +import org.jboss.logmanager.config.HandlerConfiguration; +import org.jboss.logmanager.config.LogContextConfiguration; +import org.jboss.logmanager.config.LoggerConfiguration; +import org.jboss.logmanager.config.PojoConfiguration; +import org.jboss.logmanager.config.PropertyConfigurable; + +/** + * @author James R. Perkins + */ +// TODO (jrp) this should be static and private +public class PropertyConfigurator { + private static final String NEW_LINE = System.lineSeparator(); + + private final LogContextConfiguration config; + + public PropertyConfigurator(final LogContextConfiguration config) { + this.config = config; + } + + /** + * Writes the current configuration to the output stream. + * + * Note: the output stream will be closed. + * + * @param outputStream the output stream to write to. + * @param writeExpressions {@code true} if expressions should be written, {@code false} if the resolved value should + * be written + * + * @throws IOException if an error occurs while writing the configuration. + */ + public void writeConfiguration(final OutputStream outputStream, final boolean writeExpressions) throws IOException { + try { + final BufferedWriter out = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)); + try { + final Set implicitHandlers = new HashSet(); + final Set implicitFilters = new HashSet(); + final Set implicitFormatters = new HashSet(); + final Set implicitErrorManagers = new HashSet(); + final List loggerNames = config.getLoggerNames(); + writePropertyComment(out, "Additional loggers to configure (the root logger is always configured)"); + writeProperty(out, "loggers", toCsvString(loggerNames)); + final LoggerConfiguration rootLogger = config.getLoggerConfiguration(""); + writeLoggerConfiguration(out, rootLogger, implicitHandlers, implicitFilters, writeExpressions); + // Remove the root loggers + loggerNames.remove(""); + for (String loggerName : loggerNames) { + writeLoggerConfiguration(out, config.getLoggerConfiguration(loggerName), implicitHandlers, implicitFilters, writeExpressions); + } + final List allHandlerNames = config.getHandlerNames(); + final List explicitHandlerNames = new ArrayList(allHandlerNames); + explicitHandlerNames.removeAll(implicitHandlers); + if (!explicitHandlerNames.isEmpty()) { + writePropertyComment(out, "Additional handlers to configure"); + writeProperty(out, "handlers", toCsvString(explicitHandlerNames)); + out.write(NEW_LINE); + } + for (String handlerName : allHandlerNames) { + writeHandlerConfiguration(out, config.getHandlerConfiguration(handlerName), implicitHandlers, implicitFilters, + implicitFormatters, implicitErrorManagers, writeExpressions); + } + final List allFilterNames = config.getFilterNames(); + final List explicitFilterNames = new ArrayList(allFilterNames); + explicitFilterNames.removeAll(implicitFilters); + if (!explicitFilterNames.isEmpty()) { + writePropertyComment(out, "Additional filters to configure"); + writeProperty(out, "filters", toCsvString(explicitFilterNames)); + out.write(NEW_LINE); + } + for (String filterName : allFilterNames) { + writeFilterConfiguration(out, config.getFilterConfiguration(filterName), writeExpressions); + } + final List allFormatterNames = config.getFormatterNames(); + final ArrayList explicitFormatterNames = new ArrayList(allFormatterNames); + explicitFormatterNames.removeAll(implicitFormatters); + if (!explicitFormatterNames.isEmpty()) { + writePropertyComment(out, "Additional formatters to configure"); + writeProperty(out, "formatters", toCsvString(explicitFormatterNames)); + out.write(NEW_LINE); + } + for (String formatterName : allFormatterNames) { + writeFormatterConfiguration(out, config.getFormatterConfiguration(formatterName), writeExpressions); + } + final List allErrorManagerNames = config.getErrorManagerNames(); + final ArrayList explicitErrorManagerNames = new ArrayList(allErrorManagerNames); + explicitErrorManagerNames.removeAll(implicitErrorManagers); + if (!explicitErrorManagerNames.isEmpty()) { + writePropertyComment(out, "Additional errorManagers to configure"); + writeProperty(out, "errorManagers", toCsvString(explicitErrorManagerNames)); + out.write(NEW_LINE); + } + for (String errorManagerName : allErrorManagerNames) { + writeErrorManagerConfiguration(out, config.getErrorManagerConfiguration(errorManagerName), writeExpressions); + } + + // Write POJO configurations + final List pojoNames = config.getPojoNames(); + if (!pojoNames.isEmpty()) { + writePropertyComment(out, "POJOs to configure"); + writeProperty(out, "pojos", toCsvString(pojoNames)); + for (String pojoName : pojoNames) { + writePojoConfiguration(out, config.getPojoConfiguration(pojoName), writeExpressions); + } + } + + out.flush(); + out.close(); + } finally { + safeClose(out); + } + outputStream.close(); + } finally { + safeClose(outputStream); + } + } + + private void writeLoggerConfiguration(final Writer out, final LoggerConfiguration logger, + final Set implicitHandlers, final Set implicitFilters, + final boolean writeExpressions) throws IOException { + if (logger != null) { + out.write(NEW_LINE); + final String name = logger.getName(); + final String prefix = name.isEmpty() ? "logger." : "logger." + name + "."; + final String level = (writeExpressions ? logger.getLevelValueExpression().getValue() : logger.getLevel()); + if (level != null) { + writeProperty(out, prefix, "level", level); + } + final String filterName = (writeExpressions ? logger.getFilterValueExpression() + .getValue() : logger.getFilter()); + if (filterName != null) { + writeProperty(out, prefix, "filter", filterName); + implicitFilters.add(logger.getFilter()); + } + final Boolean useParentHandlers = logger.getUseParentHandlers(); + final String useParentHandlersValue = (writeExpressions ? logger.getUseParentHandlersValueExpression() + .getValue() : + useParentHandlers == null ? null : useParentHandlers.toString()); + if (useParentHandlersValue != null) { + writeProperty(out, prefix, "useParentHandlers", useParentHandlersValue); + } + final List handlerNames = new ArrayList(); + for (String handlerName : logger.getHandlerNames()) { + if (config.getHandlerNames().contains(handlerName)) { + implicitHandlers.add(handlerName); + handlerNames.add(handlerName); + } else { + printError("Handler %s is not defined and will not be written to the configuration for logger %s%n", handlerName, (name.isEmpty() ? "ROOT" : name)); + } + } + if (!handlerNames.isEmpty()) { + writeProperty(out, prefix, "handlers", toCsvString(handlerNames)); + } + } + } + + private void writeHandlerConfiguration(final Writer out, final HandlerConfiguration handler, + final Set implicitHandlers, final Set implicitFilters, + final Set implicitFormatters, final Set implicitErrorManagers, + final boolean writeExpressions) throws IOException { + if (handler != null) { + out.write(NEW_LINE); + final String name = handler.getName(); + final String prefix = "handler." + name + "."; + final String className = handler.getClassName(); + writeProperty(out, "handler.", name, className); + final String moduleName = handler.getModuleName(); + if (moduleName != null) { + writeProperty(out, prefix, "module", moduleName); + } + final String level = (writeExpressions ? handler.getLevelValueExpression().getValue() : handler.getLevel()); + if (level != null) { + writeProperty(out, prefix, "level", level); + } + final String encoding = (writeExpressions ? handler.getEncodingValueExpression() + .getValue() : handler.getEncoding()); + if (encoding != null) { + writeProperty(out, prefix, "encoding", encoding); + } + final String filter = (writeExpressions ? handler.getFilterValueExpression() + .getValue() : handler.getFilter()); + if (filter != null) { + writeProperty(out, prefix, "filter", filter); + implicitFilters.add(handler.getFilter()); + } + final String formatterName = (writeExpressions ? handler.getFormatterNameValueExpression() + .getValue() : handler.getFormatterName()); + if (formatterName != null) { + // Make sure the formatter exists + if (config.getFormatterNames().contains(handler.getFormatterName())) { + writeProperty(out, prefix, "formatter", formatterName); + implicitFormatters.add(handler.getFormatterName()); + } else { + printError("Formatter %s is not defined and will not be written to the configuration for handler %s%n", formatterName, name); + } + } + final String errorManagerName = (writeExpressions ? handler.getErrorManagerNameValueExpression() + .getValue() : handler.getErrorManagerName()); + if (errorManagerName != null) { + // Make sure the error manager exists + if (config.getErrorManagerNames().contains(handler.getErrorManagerName())) { + writeProperty(out, prefix, "errorManager", errorManagerName); + implicitErrorManagers.add(handler.getErrorManagerName()); + } else { + printError("Error manager %s is not defined and will not be written to the configuration for handler %s%n", errorManagerName, name); + } + } + final List handlerNames = new ArrayList(); + for (String handlerName : handler.getHandlerNames()) { + if (config.getHandlerNames().contains(handlerName)) { + implicitHandlers.add(handlerName); + handlerNames.add(handlerName); + } else { + printError("Handler %s is not defined and will not be written to the configuration for handler %s%n", handlerName, name); + } + } + if (!handlerNames.isEmpty()) { + writeProperty(out, prefix, "handlers", toCsvString(handlerNames)); + } + final List postConfigurationMethods = handler.getPostConfigurationMethods(); + if (!postConfigurationMethods.isEmpty()) { + writeProperty(out, prefix, "postConfiguration", toCsvString(postConfigurationMethods)); + } + writeProperties(out, prefix, handler, writeExpressions); + } + } + + private static void writeFilterConfiguration(final Writer out, final FilterConfiguration filter, final boolean writeExpressions) throws IOException { + if (filter != null) { + out.write(NEW_LINE); + final String name = filter.getName(); + final String prefix = "filter." + name + "."; + final String className = filter.getClassName(); + writeProperty(out, "filter.", name, className); + final String moduleName = filter.getModuleName(); + if (moduleName != null) { + writeProperty(out, prefix, "module", moduleName); + } + final List postConfigurationMethods = filter.getPostConfigurationMethods(); + if (!postConfigurationMethods.isEmpty()) { + writeProperty(out, prefix, "postConfiguration", toCsvString(postConfigurationMethods)); + } + writeProperties(out, prefix, filter, writeExpressions); + } + } + + private static void writeFormatterConfiguration(final Writer out, final FormatterConfiguration formatter, final boolean writeExpressions) throws IOException { + if (formatter != null) { + out.write(NEW_LINE); + final String name = formatter.getName(); + final String prefix = "formatter." + name + "."; + final String className = formatter.getClassName(); + writeProperty(out, "formatter.", name, className); + final String moduleName = formatter.getModuleName(); + if (moduleName != null) { + writeProperty(out, prefix, "module", moduleName); + } + final List postConfigurationMethods = formatter.getPostConfigurationMethods(); + if (!postConfigurationMethods.isEmpty()) { + writeProperty(out, prefix, "postConfiguration", toCsvString(postConfigurationMethods)); + } + writeProperties(out, prefix, formatter, writeExpressions); + } + } + + private static void writeErrorManagerConfiguration(final Writer out, final ErrorManagerConfiguration errorManager, final boolean writeExpressions) throws IOException { + if (errorManager != null) { + out.write(NEW_LINE); + final String name = errorManager.getName(); + final String prefix = "errorManager." + name + "."; + final String className = errorManager.getClassName(); + writeProperty(out, "errorManager.", name, className); + final String moduleName = errorManager.getModuleName(); + if (moduleName != null) { + writeProperty(out, prefix, "module", moduleName); + } + final List postConfigurationMethods = errorManager.getPostConfigurationMethods(); + if (!postConfigurationMethods.isEmpty()) { + writeProperty(out, prefix, "postConfiguration", toCsvString(postConfigurationMethods)); + } + writeProperties(out, prefix, errorManager, writeExpressions); + } + } + + private static void writePojoConfiguration(final Writer out, final PojoConfiguration pojo, final boolean writeExpressions) throws IOException { + if (pojo != null) { + out.write(NEW_LINE); + final String name = pojo.getName(); + final String prefix = "pojo." + name + "."; + final String className = pojo.getClassName(); + writeProperty(out, "pojo.", name, className); + final String moduleName = pojo.getModuleName(); + if (moduleName != null) { + writeProperty(out, prefix, "module", moduleName); + } + final List postConfigurationMethods = pojo.getPostConfigurationMethods(); + if (!postConfigurationMethods.isEmpty()) { + writeProperty(out, prefix, "postConfiguration", toCsvString(postConfigurationMethods)); + } + writeProperties(out, prefix, pojo, writeExpressions); + } + } + + /** + * Writes a comment to the print stream. Prepends the comment with a {@code #}. + * + * @param out the print stream to write to. + * @param comment the comment to write. + */ + private static void writePropertyComment(final Writer out, final String comment) throws IOException { + out.write(NEW_LINE); + out.write("# "); + out.write(comment); + out.write(NEW_LINE); + } + + /** + * Writes a property to the print stream. + * + * @param out the print stream to write to. + * @param name the name of the property. + * @param value the value of the property. + */ + private static void writeProperty(final Writer out, final String name, final String value) throws IOException { + writeProperty(out, null, name, value); + } + + /** + * Writes a property to the print stream. + * + * @param out the print stream to write to. + * @param prefix the prefix for the name or {@code null} to use no prefix. + * @param name the name of the property. + * @param value the value of the property. + */ + private static void writeProperty(final Writer out, final String prefix, final String name, final String value) throws IOException { + if (prefix == null) { + writeKey(out, name); + } else { + writeKey(out, String.format("%s%s", prefix, name)); + } + writeValue(out, value); + out.write(NEW_LINE); + } + + /** + * Writes a collection of properties to the print stream. Uses the {@link org.jboss.logmanager.config.PropertyConfigurable#getPropertyValueString(String)} + * to extract the value. + * + * @param out the print stream to write to. + * @param prefix the prefix for the name or {@code null} to use no prefix. + * @param propertyConfigurable the configuration to extract the property value from. + * @param writeExpression {@code true} if expressions should be written, {@code false} if the resolved value + * should be written + */ + private static void writeProperties(final Writer out, final String prefix, final PropertyConfigurable propertyConfigurable, final boolean writeExpression) throws IOException { + final List names = propertyConfigurable.getPropertyNames(); + if (!names.isEmpty()) { + final List ctorProps = propertyConfigurable.getConstructorProperties(); + if (prefix == null) { + writeProperty(out, "properties", toCsvString(names)); + if (!ctorProps.isEmpty()) { + writeProperty(out, "constructorProperties", toCsvString(ctorProps)); + } + for (String name : names) { + if (writeExpression) { + writeProperty(out, name, propertyConfigurable.getPropertyValueExpression(name).getValue()); + } else { + writeProperty(out, name, propertyConfigurable.getPropertyValueString(name)); + } + } + } else { + writeProperty(out, prefix, "properties", toCsvString(names)); + if (!ctorProps.isEmpty()) { + writeProperty(out, prefix, "constructorProperties", toCsvString(ctorProps)); + } + for (String name : names) { + if (writeExpression) { + writeProperty(out, prefix, name, propertyConfigurable.getPropertyValueExpression(name) + .getValue()); + } else { + writeProperty(out, prefix, name, propertyConfigurable.getPropertyValueString(name)); + } + } + } + } + } + + /** + * Parses the list and creates a comma delimited string of the names. + *

+ * Notes: empty names are ignored. + * + * @param names the names to process. + * + * @return a comma delimited list of the names. + */ + private static String toCsvString(final List names) { + final StringBuilder result = new StringBuilder(1024); + Iterator iterator = names.iterator(); + while (iterator.hasNext()) { + final String name = iterator.next(); + // No need to write empty names + if (!name.isEmpty()) { + result.append(name); + if (iterator.hasNext()) { + result.append(","); + } + } + } + return result.toString(); + } + + private static void writeValue(final Appendable out, final String value) throws IOException { + writeSanitized(out, value, false); + } + + private static void writeKey(final Appendable out, final String key) throws IOException { + writeSanitized(out, key, true); + out.append('='); + } + + private static void writeSanitized(final Appendable out, final String string, final boolean escapeSpaces) throws IOException { + for (int x = 0; x < string.length(); x++) { + final char c = string.charAt(x); + switch (c) { + case ' ': + if (x == 0 || escapeSpaces) + out.append('\\'); + out.append(c); + break; + case '\t': + out.append('\\').append('t'); + break; + case '\n': + out.append('\\').append('n'); + break; + case '\r': + out.append('\\').append('r'); + break; + case '\f': + out.append('\\').append('f'); + break; + case '\\': + case '=': + case ':': + case '#': + case '!': + out.append('\\').append(c); + break; + default: + out.append(c); + } + } + } + + /** + * Prints the message to stderr. + * + * @param format the format of the message + * @param args the format arguments + */ + static void printError(final String format, final Object... args) { + StandardOutputStreams.printError(format, args); + } + + + private static void safeClose(final Closeable stream) { + if (stream != null) try { + stream.close(); + } catch (Exception e) { + // can't do anything about it + } + } +} diff --git a/logging/src/main/java/org/jboss/as/logging/logmanager/WildFlyLogContextSelector.java b/logging/src/main/java/org/jboss/as/logging/logmanager/WildFlyLogContextSelector.java deleted file mode 100644 index 49d9227694b..00000000000 --- a/logging/src/main/java/org/jboss/as/logging/logmanager/WildFlyLogContextSelector.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * JBoss, Home of Professional Open Source. - * Copyright 2013, Red Hat, Inc., and individual contributors - * as indicated by the @author tags. See the copyright.txt file in the - * distribution for a full listing of individual contributors. - * - * This is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. - */ - -package org.jboss.as.logging.logmanager; - -import java.util.Collections; -import java.util.List; -import java.util.logging.Handler; - -import org.jboss.logmanager.Configurator; -import org.jboss.logmanager.Level; -import org.jboss.logmanager.LogContext; -import org.jboss.logmanager.LogContextSelector; -import org.jboss.logmanager.Logger; -import org.jboss.logmanager.PropertyConfigurator; -import org.jboss.logmanager.config.LogContextConfiguration; - -/** - * The log context selector to use for the WildFly logging extension. - * - * @author James R. Perkins - */ -public interface WildFlyLogContextSelector extends LogContextSelector { - - /** - * Get and set the log context. - * - * @param newValue the new log context value, or {@code null} to clear - * - * @return the previous log context value, or {@code null} if none was set - * - * @see org.jboss.logmanager.ThreadLocalLogContextSelector#getAndSet(Object, org.jboss.logmanager.LogContext) - */ - LogContext setLocalContext(LogContext newValue); - - /** - * Register a class loader with a log context. - * - * @param classLoader the class loader - * @param logContext the log context - * - * @throws IllegalArgumentException if the class loader is already associated with a log context - * @see org.jboss.logmanager.ClassLoaderLogContextSelector#registerLogContext(ClassLoader, - * org.jboss.logmanager.LogContext) - */ - void registerLogContext(ClassLoader classLoader, LogContext logContext); - - /** - * Unregister a class loader/log context association. - * - * @param classLoader the class loader - * @param logContext the log context - * - * @return {@code true} if the association exists and was removed, {@code false} otherwise - * - * @see org.jboss.logmanager.ClassLoaderLogContextSelector#unregisterLogContext(ClassLoader, - * org.jboss.logmanager.LogContext) - */ - boolean unregisterLogContext(ClassLoader classLoader, LogContext logContext); - - /** - * Register a class loader which is a known log API, and thus should be skipped over when searching for the - * log context to use for the caller class. - * - * @param apiClassLoader the API class loader - * - * @return {@code true} if this class loader was previously unknown, or {@code false} if it was already - * registered - * - * @see org.jboss.logmanager.ClassLoaderLogContextSelector#addLogApiClassLoader(ClassLoader) - */ - boolean addLogApiClassLoader(ClassLoader apiClassLoader); - - /** - * Remove a class loader from the known log APIs set. - * - * @param apiClassLoader the API class loader - * - * @return {@code true} if the class loader was removed, or {@code false} if it was not known to this selector - * - * @see org.jboss.logmanager.ClassLoaderLogContextSelector#removeLogApiClassLoader(ClassLoader) - */ - boolean removeLogApiClassLoader(ClassLoader apiClassLoader); - - /** - * Returns the number of registered {@link org.jboss.logmanager.LogContext log contexts}. - * - * @return the number of registered log contexts - */ - int registeredCount(); - - class Factory { - private static final LogContext EMBEDDED_LOG_CONTEXT = LogContext.create(); - - /** - * Creates a new selector which wraps the current {@linkplain LogContext#getLogContextSelector() selector}. - * - * @return a new selector that wraps the current selector - */ - public static WildFlyLogContextSelector create() { - // Wrap the current LogContextSelector. This will be used as the default in the cases where this selector - // does not find a log context. - return new WildFlyLogContextSelectorImpl(LogContext.getLogContextSelector()); - } - - /** - * Creates a new selector which by default returns a static embedded context which can be used. - * - * @return a new selector - */ - public static WildFlyLogContextSelector createEmbedded() { - clearLogContext(); - return new WildFlyLogContextSelectorImpl(EMBEDDED_LOG_CONTEXT); - } - - private static void clearLogContext() { - // Remove the configurator and clear the log context - final Configurator configurator = EMBEDDED_LOG_CONTEXT.getLogger("").detach(Configurator.ATTACHMENT_KEY); - // If this was a PropertyConfigurator we can use the LogContextConfiguration API to tear down the LogContext - if (configurator instanceof PropertyConfigurator) { - final LogContextConfiguration logContextConfiguration = ((PropertyConfigurator) configurator).getLogContextConfiguration(); - clearLogContext(logContextConfiguration); - } else if (configurator instanceof LogContextConfiguration) { - clearLogContext((LogContextConfiguration) configurator); - } else { - // Remove all the handlers and close them as well as reset the loggers - final List loggerNames = Collections.list(EMBEDDED_LOG_CONTEXT.getLoggerNames()); - for (String name : loggerNames) { - final Logger logger = EMBEDDED_LOG_CONTEXT.getLoggerIfExists(name); - if (logger != null) { - final Handler[] handlers = logger.clearHandlers(); - if (handlers != null) { - for (Handler handler : handlers) { - handler.close(); - } - } - logger.setFilter(null); - logger.setUseParentFilters(false); - logger.setUseParentHandlers(true); - logger.setLevel(Level.INFO); - } - } - } - } - - private static void clearLogContext(final LogContextConfiguration logContextConfiguration) { - try { - // Remove all the loggers - for (String name : logContextConfiguration.getLoggerNames()) { - logContextConfiguration.removeLoggerConfiguration(name); - } - // Remove all the handlers - for (String name : logContextConfiguration.getHandlerNames()) { - logContextConfiguration.removeHandlerConfiguration(name); - } - // Remove all the formatters - for (String name : logContextConfiguration.getFormatterNames()) { - logContextConfiguration.removeFormatterConfiguration(name); - } - // Remove all the error managers - for (String name : logContextConfiguration.getErrorManagerNames()) { - logContextConfiguration.removeErrorManagerConfiguration(name); - } - // Remove all the POJO's - for (String name : logContextConfiguration.getPojoNames()) { - logContextConfiguration.removePojoConfiguration(name); - } - // Remove all the filters - for (String name : logContextConfiguration.getFilterNames()) { - logContextConfiguration.removeFilterConfiguration(name); - } - logContextConfiguration.commit(); - } finally { - logContextConfiguration.forget(); - } - } - } - -} diff --git a/logging/src/test/java/org/jboss/as/logging/AbstractLoggingSubsystemTest.java b/logging/src/test/java/org/jboss/as/logging/AbstractLoggingSubsystemTest.java index 05e655f07e1..dffa51cd1a3 100644 --- a/logging/src/test/java/org/jboss/as/logging/AbstractLoggingSubsystemTest.java +++ b/logging/src/test/java/org/jboss/as/logging/AbstractLoggingSubsystemTest.java @@ -63,7 +63,6 @@ import org.jboss.as.logging.loggers.LoggerAttributes; import org.jboss.as.logging.loggers.LoggerResourceDefinition; import org.jboss.as.logging.loggers.RootLoggerResourceDefinition; -import org.jboss.as.logging.logmanager.ConfigurationPersistence; import org.jboss.as.logging.resolvers.SizeResolver; import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest; import org.jboss.as.subsystem.test.AdditionalInitialization; @@ -72,9 +71,7 @@ import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; import org.jboss.dmr.Property; -import org.jboss.logmanager.Configurator; import org.jboss.logmanager.LogContext; -import org.jboss.logmanager.PropertyConfigurator; import org.jboss.logmanager.config.FormatterConfiguration; import org.jboss.logmanager.config.HandlerConfiguration; import org.jboss.logmanager.config.LogContextConfiguration; @@ -226,8 +223,7 @@ void compare(final String profileName, final ModelNode node1, final ModelNode no } } - void compare(final ModelNode currentModel, final ConfigurationPersistence config) throws OperationFailedException { - final LogContextConfiguration logContextConfig = config.getLogContextConfiguration(); + void compare(final ModelNode currentModel, final LogContextConfiguration logContextConfig) throws OperationFailedException { final List handlerNames = logContextConfig.getHandlerNames(); final List modelHandlerNames = getHandlerNames(currentModel); final List missingConfigHandlers = new ArrayList<>(handlerNames); @@ -246,7 +242,7 @@ void compare(final ModelNode currentModel, final ConfigurationPersistence config } - void compare(final String profileName, final ModelNode currentModel, final ConfigurationPersistence config) throws OperationFailedException { + void compare(final String profileName, final ModelNode currentModel, final LogContextConfiguration config) throws OperationFailedException { if (profileName == null) { compare(currentModel, config); } else { @@ -537,18 +533,6 @@ private static String convertModelPropertyName(final String xmlName) { return xmlName; } - @SuppressWarnings("ChainOfInstanceofChecks") - private LogContextConfiguration getLogContextConfiguration(final LogContext logContext) { - final Configurator configurator = logContext.getAttachment(CommonAttributes.ROOT_LOGGER_NAME, Configurator.ATTACHMENT_KEY); - if (configurator instanceof LogContextConfiguration) { - return (LogContextConfiguration) configurator; - } - if (configurator instanceof PropertyConfigurator) { - return ((PropertyConfigurator) configurator).getLogContextConfiguration(); - } - return null; - } - static class RemoveOperationComparator implements Comparator { static final RemoveOperationComparator INSTANCE = new RemoveOperationComparator(); diff --git a/logging/src/test/java/org/jboss/as/logging/AbstractOperationsTestCase.java b/logging/src/test/java/org/jboss/as/logging/AbstractOperationsTestCase.java index c1926a11302..2b51dfa9463 100644 --- a/logging/src/test/java/org/jboss/as/logging/AbstractOperationsTestCase.java +++ b/logging/src/test/java/org/jboss/as/logging/AbstractOperationsTestCase.java @@ -37,6 +37,7 @@ import org.jboss.dmr.ModelNode; import org.junit.After; import org.junit.BeforeClass; +import org.wildfly.core.logmanager.WildFlyLogContextSelector; /** * @author James R. Perkins @@ -57,10 +58,10 @@ public static void setupLoggingDir() throws Exception { @Override public void clearLogContext() throws Exception { super.clearLogContext(); - final LoggingProfileContextSelector contextSelector = LoggingProfileContextSelector.getInstance(); - if (contextSelector.exists(PROFILE)) { - contextSelector.get(PROFILE).close(); - contextSelector.remove(PROFILE); + final WildFlyLogContextSelector contextSelector = WildFlyLogContextSelector.getContextSelector(); + if (contextSelector.profileContextExists(PROFILE)) { + contextSelector.getProfileContext(PROFILE).close(); + contextSelector.removeProfileContext(PROFILE); } } diff --git a/logging/src/test/java/org/jboss/as/logging/FormatterOperationsTestCase.java b/logging/src/test/java/org/jboss/as/logging/FormatterOperationsTestCase.java index 864f56e00d7..863bf23c073 100644 --- a/logging/src/test/java/org/jboss/as/logging/FormatterOperationsTestCase.java +++ b/logging/src/test/java/org/jboss/as/logging/FormatterOperationsTestCase.java @@ -40,6 +40,7 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.wildfly.core.logmanager.WildFlyLogContextSelector; /** * @author James R. Perkins @@ -78,10 +79,10 @@ public void shutdown() { @Override public void clearLogContext() throws Exception { super.clearLogContext(); - final LoggingProfileContextSelector contextSelector = LoggingProfileContextSelector.getInstance(); - if (contextSelector.exists(PROFILE)) { - contextSelector.get(PROFILE).close(); - contextSelector.remove(PROFILE); + final WildFlyLogContextSelector contextSelector = WildFlyLogContextSelector.getContextSelector(); + if (contextSelector.profileContextExists(PROFILE)) { + contextSelector.getProfileContext(PROFILE).close(); + contextSelector.removeProfileContext(PROFILE); } } diff --git a/logging/src/test/java/org/jboss/as/logging/LoggingOperationsSubsystemTestCase.java b/logging/src/test/java/org/jboss/as/logging/LoggingOperationsSubsystemTestCase.java index bcc30008993..3f5ec6bd780 100644 --- a/logging/src/test/java/org/jboss/as/logging/LoggingOperationsSubsystemTestCase.java +++ b/logging/src/test/java/org/jboss/as/logging/LoggingOperationsSubsystemTestCase.java @@ -59,6 +59,7 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.wildfly.core.logmanager.WildFlyLogContextSelector; /** * @author Kabir Khan @@ -105,10 +106,10 @@ public void shutdown() { @Override public void clearLogContext() throws Exception { super.clearLogContext(); - final LoggingProfileContextSelector contextSelector = LoggingProfileContextSelector.getInstance(); - if (contextSelector.exists(PROFILE)) { - contextSelector.get(PROFILE).close(); - contextSelector.remove(PROFILE); + final WildFlyLogContextSelector contextSelector = WildFlyLogContextSelector.getContextSelector(); + if (contextSelector.profileContextExists(PROFILE)) { + contextSelector.getProfileContext(PROFILE).close(); + contextSelector.removeProfileContext(PROFILE); } } @@ -724,7 +725,7 @@ private void doLog(final String loggingProfile, final Level[] levels, final Stri private Logger getLogger(final String profileName) { final LogContext logContext; if (profileName != null) { - logContext = LoggingProfileContextSelector.getInstance().get(profileName); + logContext = WildFlyLogContextSelector.getContextSelector().getProfileContext(profileName); } else { logContext = LogContext.getSystemLogContext(); } diff --git a/logging/src/test/java/org/jboss/as/logging/LoggingSubsystemRollbackTestCase.java b/logging/src/test/java/org/jboss/as/logging/LoggingSubsystemRollbackTestCase.java index 1d39583bfec..cbabf361d90 100644 --- a/logging/src/test/java/org/jboss/as/logging/LoggingSubsystemRollbackTestCase.java +++ b/logging/src/test/java/org/jboss/as/logging/LoggingSubsystemRollbackTestCase.java @@ -43,6 +43,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.wildfly.core.logmanager.WildFlyLogContextSelector; /** * @author James R. Perkins @@ -79,10 +80,10 @@ protected void standardSubsystemTest(final String configId) throws Exception { @Override public void clearLogContext() throws Exception { super.clearLogContext(); - final LoggingProfileContextSelector contextSelector = LoggingProfileContextSelector.getInstance(); - if (contextSelector.exists(PROFILE_NAME)) { - contextSelector.get(PROFILE_NAME).close(); - contextSelector.remove(PROFILE_NAME); + final WildFlyLogContextSelector contextSelector = WildFlyLogContextSelector.getContextSelector(); + if (contextSelector.profileContextExists(PROFILE_NAME)) { + contextSelector.getProfileContext(PROFILE_NAME).close(); + contextSelector.removeProfileContext(PROFILE_NAME); } } @@ -384,7 +385,8 @@ public void testRollbackRemoveProfile() throws Exception { ConfigurationPersistence config = ConfigurationPersistence.getConfigurationPersistence(LogContext.getLogContext()); compare(currentModel, config); // Check the profile was rolled back - config = ConfigurationPersistence.getConfigurationPersistence(LoggingProfileContextSelector.getInstance().get(PROFILE_NAME)); + config = ConfigurationPersistence.getConfigurationPersistence(WildFlyLogContextSelector.getContextSelector() + .getProfileContext(PROFILE_NAME)); compare(PROFILE_NAME, currentModel, config); } @@ -405,7 +407,7 @@ public void rollbackAdd(final String profileName) throws Exception { ModelNode currentModel = getSubsystemModel(kernelServices); compare(profileName, validSubsystemModel, currentModel); - final LogContext logContext = (profileName == null ? LogContext.getLogContext() : LoggingProfileContextSelector.getInstance().get(profileName)); + final LogContext logContext = Logging.getLogContext(profileName); ConfigurationPersistence config = ConfigurationPersistence.getConfigurationPersistence(logContext); compare(profileName, currentModel, config); @@ -438,7 +440,7 @@ public void rollbackWriteAttribute(final String profileName) throws Exception { ModelNode currentModel = getSubsystemModel(kernelServices); compare(profileName, validSubsystemModel, currentModel); - final LogContext logContext = (profileName == null ? LogContext.getLogContext() : LoggingProfileContextSelector.getInstance().get(profileName)); + final LogContext logContext = Logging.getLogContext(profileName); ConfigurationPersistence config = ConfigurationPersistence.getConfigurationPersistence(logContext); compare(profileName, currentModel, config); @@ -473,7 +475,7 @@ public void rollbackUpdateAttribute(final String profileName) throws Exception { ModelNode currentModel = getSubsystemModel(kernelServices); compare(profileName, validSubsystemModel, currentModel); - final LogContext logContext = (profileName == null ? LogContext.getLogContext() : LoggingProfileContextSelector.getInstance().get(profileName)); + final LogContext logContext = Logging.getLogContext(profileName); ConfigurationPersistence config = ConfigurationPersistence.getConfigurationPersistence(logContext); compare(profileName, currentModel, config); @@ -519,7 +521,7 @@ public void rollbackRemove(final String profileName) throws Exception { ModelNode currentModel = getSubsystemModel(kernelServices); compare(profileName, validSubsystemModel, currentModel); - final LogContext logContext = (profileName == null ? LogContext.getLogContext() : LoggingProfileContextSelector.getInstance().get(profileName)); + final LogContext logContext = Logging.getLogContext(profileName); ConfigurationPersistence config = ConfigurationPersistence.getConfigurationPersistence(logContext); compare(profileName, currentModel, config); diff --git a/logging/src/test/java/org/jboss/as/logging/LoggingSubsystemTestCase.java b/logging/src/test/java/org/jboss/as/logging/LoggingSubsystemTestCase.java index ede2110f84a..06998431b1b 100644 --- a/logging/src/test/java/org/jboss/as/logging/LoggingSubsystemTestCase.java +++ b/logging/src/test/java/org/jboss/as/logging/LoggingSubsystemTestCase.java @@ -24,6 +24,8 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; @@ -32,12 +34,14 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.List; +import java.util.Properties; import java.util.regex.Pattern; import org.jboss.as.logging.logmanager.ConfigurationPersistence; import org.jboss.as.subsystem.test.KernelServices; import org.jboss.dmr.ModelNode; import org.jboss.logmanager.LogContext; +import org.jboss.logmanager.config.LogContextConfiguration; import org.junit.Assert; import org.junit.Test; import org.wildfly.security.manager.WildFlySecurityManager; @@ -69,9 +73,10 @@ public void testConfiguration() throws Exception { final String dir = resolveRelativePath(kernelServices, "jboss.server.config.dir"); Assert.assertNotNull("jboss.server.config.dir could not be resolved", dir); final LogContext logContext = LogContext.create(); - final ConfigurationPersistence config = ConfigurationPersistence.getOrCreateConfigurationPersistence(logContext); try (final FileInputStream in = new FileInputStream(new File(dir, "logging.properties"))) { - config.configure(in); + final Properties properties = new Properties(); + properties.load(new InputStreamReader(in, StandardCharsets.UTF_8)); + LogContextConfiguration config = LogContextConfiguration.create(logContext, properties); compare(currentModel, config); } logContext.close(); diff --git a/logging/src/test/java/org/jboss/as/logging/RootSubsystemOperationsTestCase.java b/logging/src/test/java/org/jboss/as/logging/RootSubsystemOperationsTestCase.java index ace8a3a524a..3c96c426e91 100644 --- a/logging/src/test/java/org/jboss/as/logging/RootSubsystemOperationsTestCase.java +++ b/logging/src/test/java/org/jboss/as/logging/RootSubsystemOperationsTestCase.java @@ -374,7 +374,7 @@ private Logger getLogger() { } private Logger getLogger(final String loggingProfile) { - return LoggingProfileContextSelector.getInstance().get(loggingProfile).getLogger("org.jboss.as.logging.test"); + return Logging.getLogContext(loggingProfile).getLogger("org.jboss.as.logging.test"); } private static boolean setReadable(final Path path, final boolean readable) throws IOException { diff --git a/logmanager/pom.xml b/logmanager/pom.xml new file mode 100644 index 00000000000..484698c96df --- /dev/null +++ b/logmanager/pom.xml @@ -0,0 +1,53 @@ + + + + + 4.0.0 + + org.wildfly.core + wildfly-core-parent + 21.0.0.Beta5-SNAPSHOT + + + wildfly-logmanager + WildFly: Log Manager + + + + + org.wildfly.core + wildfly-core-testbom + ${project.version} + pom + import + + + + + + + org.jboss.logmanager + jboss-logmanager + + + + \ No newline at end of file diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/AbstractBasicConfiguration.java b/logmanager/src/main/java/org/jboss/logmanager/config/AbstractBasicConfiguration.java new file mode 100644 index 00000000000..85b2e7710e0 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/AbstractBasicConfiguration.java @@ -0,0 +1,96 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import java.util.Map; + +import org.jboss.logmanager.configuration.ConfigurationResource; + +/** + * @author David M. Lloyd + */ +abstract class AbstractBasicConfiguration> implements NamedConfigurable { + + private final LogContextConfiguration configuration; + private final String name; + private boolean removed; + protected final Map configs; + + AbstractBasicConfiguration(final String name, final LogContextConfiguration configuration, final Map configs) { + this.name = name; + this.configuration = configuration; + this.configs = configs; + } + + public String getName() { + return name; + } + + void clearRemoved() { + removed = false; + } + + void setRemoved() { + removed = true; + } + + boolean isRemoved() { + return removed; + } + + abstract ConfigurationResource removeInstance(); + + LogContextConfiguration getConfiguration() { + return configuration; + } + + ConfigAction getRemoveAction() { + return new ConfigAction<>() { + public Void validate() throws IllegalArgumentException { + return null; + } + + public void applyPreCreate(final Void param) { + final ConfigurationResource resource = removeInstance(); + if (resource != null) { + try { + resource.close(); + } catch (Exception e) { + // TODO (jrp) what should we do here? + //throw new RuntimeException(e); + } + } + } + + public void applyPostCreate(final Void param) { + } + + @SuppressWarnings({"unchecked"}) + public void rollback() { + configs.put(name, (C) AbstractBasicConfiguration.this); + clearRemoved(); + } + }; + } + + Map getConfigs() { + return configs; + } +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/AbstractPropertyConfiguration.java b/logmanager/src/main/java/org/jboss/logmanager/config/AbstractPropertyConfiguration.java new file mode 100644 index 00000000000..5c9b2031e6d --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/AbstractPropertyConfiguration.java @@ -0,0 +1,588 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import static java.util.Arrays.asList; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.jboss.logmanager.StandardOutputStreams; +import org.jboss.logmanager.configuration.ConfigurationResource; +import org.jboss.modules.Module; +import org.jboss.modules.ModuleLoader; + +/** + * @author David M. Lloyd + */ +// TODO (jrp) we shouldn't to do this. We're going to use an ObjectBuilder and handle it that way, so each type should have it's own implementation rather than being this abstract +abstract class AbstractPropertyConfiguration> extends AbstractBasicConfiguration implements ObjectConfigurable, PropertyConfigurable { + private final Class actualClass; + private final ClassLoader classLoader; + private final String moduleName; + private final String className; + private final String[] constructorProperties; + // TODO (jrp) make this thread-safe + private final Map> properties = new LinkedHashMap<>(0); + private final Map postConfigurationMethods = new LinkedHashMap<>(); + + protected AbstractPropertyConfiguration(final Class baseClass, final LogContextConfiguration configuration, final Map configs, final String name, final String moduleName, final String className, final String[] constructorProperties) { + super(name, configuration, configs); + this.moduleName = moduleName; + this.className = className; + if (className == null) { + throw new IllegalArgumentException("className is null"); + } + this.constructorProperties = constructorProperties; + final ClassLoader classLoader; + if (moduleName != null) try { + classLoader = ModuleFinder.getClassLoader(moduleName); + this.classLoader = classLoader; + } catch (Throwable e) { + throw new IllegalArgumentException(String.format("Failed to load module \"%s\" for %s \"%s\"", moduleName, getDescription(), name), e); + } + else { + classLoader = getClass().getClassLoader(); + this.classLoader = null; + } + final Class actualClass; + try { + actualClass = Class.forName(className, true, classLoader).asSubclass(baseClass); + } catch (Exception e) { + throw new IllegalArgumentException(String.format("Failed to load class \"%s\" for %s \"%s\"", className, getDescription(), name), e); + } + this.actualClass = actualClass; + } + + ConfigAction getConstructAction() { + return new ConstructAction(); + } + + abstract String getDescription(); + + abstract void addConfigurationResource(ConfigurationResource resource); + + class ConstructAction implements ConfigAction { + + public T validate() throws IllegalArgumentException { + final int length = constructorProperties.length; + final Class[] paramTypes = new Class[length]; + for (int i = 0; i < length; i++) { + final String property = constructorProperties[i]; + final Class type = WrappedAction.execute(() -> getConstructorPropertyType(actualClass, property), classLoader); + if (type == null) { + throw new IllegalArgumentException(String.format("No property named \"%s\" for %s \"%s\"", property, getDescription(), getName())); + } + paramTypes[i] = type; + } + final Constructor constructor = WrappedAction.execute(() -> { + try { + return actualClass.getConstructor(paramTypes); + } catch (Exception e) { + throw new IllegalArgumentException(String.format("Failed to locate constructor in class \"%s\" for %s \"%s\"", className, getDescription(), getName()), e); + } + }, classLoader); + final Object[] params = new Object[length]; + for (int i = 0; i < length; i++) { + final String property = constructorProperties[i]; + if (!properties.containsKey(property)) { + throw new IllegalArgumentException(String.format("No property named \"%s\" is configured on %s \"%s\"", property, getDescription(), getName())); + } + final ValueExpression valueExpression = properties.get(property); + final Object value = getConfiguration().getValue(actualClass, property, paramTypes[i], valueExpression, true) + .getObject(); + params[i] = value; + } + return WrappedAction.execute(() -> { + try { + return constructor.newInstance(params); + } catch (Exception e) { + throw new IllegalArgumentException(String.format("Failed to instantiate class \"%s\" for %s \"%s\"", className, getDescription(), getName()), e); + } + }, classLoader); + } + + public void applyPreCreate(final T param) { + addConfigurationResource(ConfigurationResource.of(param)); + } + + public void applyPostCreate(T param) { + } + + public void rollback() { + getConfigs().remove(getName()); + } + } + + public String getModuleName() { + return moduleName; + } + + public String getClassName() { + return className; + } + + static boolean contains(Object[] array, Object val) { + for (Object o : array) { + if (o.equals(val)) return true; + } + return false; + } + + public void setPropertyValueString(final String propertyName, final String value) throws IllegalArgumentException { + if (isRemoved()) { + throw new IllegalArgumentException(String.format("Cannot set property \"%s\" on %s \"%s\" (removed)", propertyName, getDescription(), getName())); + } + if (propertyName == null) { + throw new IllegalArgumentException("propertyName is null"); + } + setPropertyValueExpression(propertyName, ValueExpression.STRING_RESOLVER.resolve(value)); + } + + public String getPropertyValueString(final String propertyName) { + return getPropertyValueExpression(propertyName).getResolvedValue(); + } + + @Override + public ValueExpression getPropertyValueExpression(final String propertyName) { + return properties.getOrDefault(propertyName, ValueExpression.NULL_STRING_EXPRESSION); + } + + @Override + public void setPropertyValueExpression(final String propertyName, final String expression) { + if (isRemoved()) { + throw new IllegalArgumentException(String.format("Cannot set property \"%s\" on %s \"%s\" (removed)", propertyName, getDescription(), getName())); + } + if (propertyName == null) { + throw new IllegalArgumentException("propertyName is null"); + } + setPropertyValueExpression(propertyName, ValueExpression.STRING_RESOLVER.resolve(expression)); + } + + @Override + public void setPropertyValueExpression(final String propertyName, final String expression, final String value) { + if (isRemoved()) { + throw new IllegalArgumentException(String.format("Cannot set property \"%s\" on %s \"%s\" (removed)", propertyName, getDescription(), getName())); + } + if (propertyName == null) { + throw new IllegalArgumentException("propertyName is null"); + } + setPropertyValueExpression(propertyName, new ValueExpressionImpl(expression, value)); + } + + private void setPropertyValueExpression(final String propertyName, final ValueExpression expression) { + final boolean replacement = properties.containsKey(propertyName); + final boolean constructorProp = contains(constructorProperties, propertyName); + final Method setter = WrappedAction.execute(() -> getPropertySetter(actualClass, propertyName), classLoader); + if (setter == null && !constructorProp) { + throw new IllegalArgumentException(String.format("No property \"%s\" setter found for %s \"%s\"", propertyName, getDescription(), getName())); + } + final ValueExpression oldValue = properties.put(propertyName, expression); + getConfiguration().addAction(new ConfigAction() { + public ObjectProducer validate() throws IllegalArgumentException { + if (setter == null) { + return ObjectProducer.NULL_PRODUCER; + } + return WrappedAction.execute(() -> { + final Class propertyType = getPropertyType(actualClass, propertyName); + if (propertyType == null) { + throw new IllegalArgumentException(String.format("No property \"%s\" type could be determined for %s \"%s\"", propertyName, getDescription(), getName())); + } + return getConfiguration().getValue(actualClass, propertyName, propertyType, expression, false); + }, classLoader); + } + + public void applyPreCreate(final ObjectProducer param) { + addPostConfigurationActions(); + } + + public void applyPostCreate(final ObjectProducer param) { + if (setter != null) { + WrappedAction.execute(() -> { + final T instance = getInstance(); + try { + setter.invoke(instance, param.getObject()); + } catch (Throwable e) { + StandardOutputStreams.printError(e, "Failed to invoke setter %s with value %s%n.", setter.getName(), param.getObject()); + } + }, classLoader); + } + } + + public void rollback() { + WrappedAction.execute(() -> { + final Class propertyType = getPropertyType(actualClass, propertyName); + if (propertyType == null) { + // We don't want the rest of the rollback to fail so we'll just log a message + StandardOutputStreams.printError("No property \"%s\" type could be determined for %s \"%s\"", propertyName, getDescription(), getName()); + return; + } + final ObjectProducer producer; + if (replacement) { + properties.put(propertyName, oldValue); + producer = getConfiguration().getValue(actualClass, propertyName, propertyType, oldValue, true); + } else { + properties.remove(propertyName); + producer = getDefaultValue(propertyType); + } + if (setter != null) { + // Get the reference instance, the old value and reset to the old value + final T instance = getInstance(); + if (instance != null) { + try { + setter.invoke(instance, producer.getObject()); + } catch (Throwable e) { + StandardOutputStreams.printError(e, "Failed to invoke setter %s with value %s%n.", setter.getName(), producer.getObject()); + } + } else { + // If the instance is not available don't keep the property + properties.remove(propertyName); + } + } + }, classLoader); + } + }); + } + + public boolean hasProperty(final String propertyName) { + return properties.containsKey(propertyName); + } + + public boolean removeProperty(final String propertyName) { + if (isRemoved()) { + throw new IllegalArgumentException(String.format("Cannot remove property \"%s\" on %s \"%s\" (removed)", propertyName, getDescription(), getName())); + } + final Method setter = WrappedAction.execute(() -> getPropertySetter(actualClass, propertyName), classLoader); + if (setter == null) { + throw new IllegalArgumentException(String.format("No property \"%s\" setter found for %s \"%s\"", propertyName, getDescription(), getName())); + } + final ValueExpression oldValue = properties.remove(propertyName); + if (oldValue != null) { + getConfiguration().addAction(new ConfigAction() { + public ObjectProducer validate() throws IllegalArgumentException { + return WrappedAction.execute(() -> { + final Class propertyType = getPropertyType(actualClass, propertyName); + if (propertyType == null) { + throw new IllegalArgumentException(String.format("No property \"%s\" type could be determined for %s \"%s\"", propertyName, getDescription(), getName())); + } + return getDefaultValue(propertyType); + }, classLoader); + } + + public void applyPreCreate(final ObjectProducer param) { + addPostConfigurationActions(); + } + + public void applyPostCreate(final ObjectProducer param) { + WrappedAction.execute(() -> { + final T instance = getInstance(); + try { + setter.invoke(instance, param.getObject()); + } catch (Throwable e) { + StandardOutputStreams.printError(e, "Failed to invoke setter %s with value %s%n.", setter.getName(), param.getObject()); + } + }, classLoader); + } + + public void rollback() { + WrappedAction.execute(() -> { + // We need to once again determine the property type + final Class propertyType = getPropertyType(actualClass, propertyName); + if (propertyType == null) { + // We don't want the rest of the rollback to fail so we'll just log a message + StandardOutputStreams.printError("No property \"%s\" type could be determined for %s \"%s\"", propertyName, getDescription(), getName()); + return; + } + // Get the reference instance, the old value and reset to the old value + final T instance = getInstance(); + final ObjectProducer producer = getConfiguration().getValue(actualClass, propertyName, propertyType, oldValue, true); + try { + setter.invoke(instance, producer.getObject()); + } catch (Throwable e) { + StandardOutputStreams.printError(e, "Failed to invoke setter %s with value %s%n.", setter.getName(), producer.getObject()); + } + }, classLoader); + } + }); + return true; + } + return false; + } + + public List getPropertyNames() { + return new ArrayList(properties.keySet()); + } + + @Override + public boolean hasConstructorProperty(final String propertyName) { + return contains(constructorProperties, propertyName); + } + + Class getActualClass() { + return actualClass; + } + + @Override + public List getConstructorProperties() { + return Arrays.asList(constructorProperties); + } + + @Override + public boolean addPostConfigurationMethod(final String methodName) { + final LogContextConfiguration configuration = getConfiguration(); + if (postConfigurationMethods.containsKey(methodName)) { + return false; + } + configuration.addAction(new ConfigAction() { + public Method validate() throws IllegalArgumentException { + return WrappedAction.execute(() -> { + try { + return actualClass.getMethod(methodName); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException(String.format("Method '%s' not found on '%s'", methodName, actualClass.getName())); + } + }, classLoader); + } + + public void applyPreCreate(final Method param) { + } + + public void applyPostCreate(final Method param) { + postConfigurationMethods.put(methodName, param); + // TODO (jrp) this isn't the best for performance + addPostConfigurationActions(true); + } + + public void rollback() { + postConfigurationMethods.remove(methodName); + addPostConfigurationActions(true); + } + }); + return true; + } + + @Override + public List getPostConfigurationMethods() { + return new ArrayList<>(postConfigurationMethods.keySet()); + } + + @Override + public void setPostConfigurationMethods(final String... methodNames) { + setPostConfigurationMethods(asList(methodNames)); + } + + @Override + public void setPostConfigurationMethods(final List methodNames) { + final Map oldMethods = new LinkedHashMap(postConfigurationMethods); + postConfigurationMethods.clear(); + final LinkedHashSet names = new LinkedHashSet(methodNames); + getConfiguration().addAction(new ConfigAction>() { + @Override + public Map validate() throws IllegalArgumentException { + final Map result = new LinkedHashMap(); + for (String methodName : names) { + WrappedAction.execute(() -> { + try { + result.put(methodName, actualClass.getMethod(methodName)); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException(String.format("Method '%s' not found on '%s'", methodName, actualClass.getName())); + } + }, classLoader); + } + return result; + } + + @Override + public void applyPreCreate(final Map param) { + } + + @Override + public void applyPostCreate(final Map param) { + postConfigurationMethods.clear(); + postConfigurationMethods.putAll(param); + addPostConfigurationActions(true); + } + + @Override + public void rollback() { + postConfigurationMethods.clear(); + postConfigurationMethods.putAll(oldMethods); + addPostConfigurationActions(true); + } + }); + } + + @Override + public boolean removePostConfigurationMethod(final String methodName) { + final LogContextConfiguration configuration = getConfiguration(); + if (!postConfigurationMethods.containsKey(methodName)) { + return false; + } + final Method method = postConfigurationMethods.get(methodName); + postConfigurationMethods.remove(methodName); + configuration.addAction(new ConfigAction() { + public Void validate() throws IllegalArgumentException { + return null; + } + + public void applyPreCreate(final Void param) { + } + + public void applyPostCreate(final Void param) { + } + + public void rollback() { + postConfigurationMethods.put(methodName, method); + addPostConfigurationActions(true); + } + }); + return true; + } + + protected final void addPostConfigurationActions() { + addPostConfigurationActions(false); + } + + private void addPostConfigurationActions(final boolean replace) { + final String name = className + "." + getName(); + final LogContextConfiguration configuration = getConfiguration(); + if (!replace && configuration.postConfigurationActionsExist(name)) { + return; + } + final Deque> queue = new ArrayDeque<>(postConfigurationMethods.size()); + for (final String methodName : postConfigurationMethods.keySet()) { + final ConfigAction configAction = new ConfigAction<>() { + + public Method validate() throws IllegalArgumentException { + final Method result = postConfigurationMethods.get(methodName); + if (result == null) { + throw new IllegalArgumentException(String.format("Method '%s' not found on '%s'", methodName, actualClass.getName())); + } + return result; + } + + public void applyPreCreate(final Method param) { + } + + public void applyPostCreate(final Method param) { + WrappedAction.execute(() -> { + final T instance = getInstance(); + try { + param.invoke(instance); + } catch (Throwable e) { + StandardOutputStreams.printError(e, "Failed to invoke method %s%n.", param.getName()); + } + }, classLoader); + } + + public void rollback() { + // ignore any rollbacks at this point + } + }; + queue.addLast(configAction); + } + configuration.addPostConfigurationActions(name, queue); + } + + protected final Deque removePostConfigurationActions() { + final String name = className + "." + getName(); + return getConfiguration().removePostConfigurationActions(name); + } + + private static Class getPropertyType(Class clazz, String propertyName) { + final Method setter = getPropertySetter(clazz, propertyName); + return setter != null ? setter.getParameterTypes()[0] : null; + } + + private static Class getConstructorPropertyType(Class clazz, String propertyName) { + final Method getter = getPropertyGetter(clazz, propertyName); + return getter != null ? getter.getReturnType() : getPropertyType(clazz, propertyName); + } + + private static Method getPropertySetter(Class clazz, String propertyName) { + final String upperPropertyName = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); + final String set = "set" + upperPropertyName; + for (Method method : clazz.getMethods()) { + if ((method.getName() + .equals(set) && Modifier.isPublic(method.getModifiers())) && method.getParameterTypes().length == 1) { + return method; + } + } + return null; + } + + private static Method getPropertyGetter(Class clazz, String propertyName) { + final String upperPropertyName = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); + final Pattern pattern = Pattern.compile("(get|has|is)(" + Pattern.quote(upperPropertyName) + ")"); + for (Method method : clazz.getMethods()) { + if ((pattern.matcher(method.getName()) + .matches() && Modifier.isPublic(method.getModifiers())) && method.getParameterTypes().length == 0) { + return method; + } + } + return null; + } + + private static ObjectProducer getDefaultValue(final Class paramType) { + if (paramType == boolean.class) { + return new SimpleObjectProducer(Boolean.FALSE); + } else if (paramType == byte.class) { + return new SimpleObjectProducer((byte) 0x00); + } else if (paramType == short.class) { + return new SimpleObjectProducer((short) 0); + } else if (paramType == int.class) { + return new SimpleObjectProducer(0); + } else if (paramType == long.class) { + return new SimpleObjectProducer(0L); + } else if (paramType == float.class) { + return new SimpleObjectProducer(0.0f); + } else if (paramType == double.class) { + return new SimpleObjectProducer(0.0d); + } else if (paramType == char.class) { + return new SimpleObjectProducer((char) 0x00); + } else { + return SimpleObjectProducer.NULL_PRODUCER; + } + } + + static class ModuleFinder { + + private ModuleFinder() { + } + + static ClassLoader getClassLoader(final String moduleName) throws Exception { + ModuleLoader moduleLoader = ModuleLoader.forClass(ModuleFinder.class); + if (moduleLoader == null) { + moduleLoader = Module.getBootModuleLoader(); + } + return moduleLoader.loadModule(moduleName).getClassLoader(); + } + } +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/ConfigAction.java b/logmanager/src/main/java/org/jboss/logmanager/config/ConfigAction.java new file mode 100644 index 00000000000..6e20b283417 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/ConfigAction.java @@ -0,0 +1,39 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import org.jboss.logmanager.configuration.ConfigurationResource; + +/** + * @author David M. Lloyd + * @author James R. Perkins + */ +interface ConfigAction { + T validate() throws IllegalArgumentException; + default T validate(ConfigurationResource resource) { + return resource.get(); + } + + void applyPreCreate(T param); + + void applyPostCreate(T param); + + void rollback(); +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/DelegatingContextConfiguration.java b/logmanager/src/main/java/org/jboss/logmanager/config/DelegatingContextConfiguration.java new file mode 100644 index 00000000000..8f416804454 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/DelegatingContextConfiguration.java @@ -0,0 +1,195 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; +import java.util.logging.ErrorManager; +import java.util.logging.Filter; +import java.util.logging.Formatter; +import java.util.logging.Handler; + +import org.jboss.logmanager.LogContext; +import org.jboss.logmanager.Logger; +import org.jboss.logmanager.configuration.ConfigurationResource; +import org.jboss.logmanager.configuration.ContextConfiguration; + +/** + * @author James R. Perkins + */ +class DelegatingContextConfiguration extends ContextConfiguration { + private final ContextConfiguration delegate; + + public DelegatingContextConfiguration(final ContextConfiguration delegate) { + super(delegate.getContext()); + this.delegate = delegate; + } + + @Override + public LogContext getContext() { + return delegate.getContext(); + } + + @Override + public boolean hasLogger(final String name) { + return delegate.hasLogger(name); + } + + @Override + public Logger getLogger(final String name) { + return delegate.getLogger(name); + } + + @Override + public Set getLoggers() { + return delegate.getLoggers(); + } + + @Override + public ConfigurationResource addErrorManager(final String name, final Supplier errorManager) { + return delegate.addErrorManager(name, errorManager); + } + + @Override + public ConfigurationResource removeErrorManager(final String name) { + return delegate.removeErrorManager(name); + } + + @Override + public boolean hasErrorManager(final String name) { + return delegate.hasErrorManager(name); + } + + @Override + public ErrorManager getErrorManager(final String name) { + return delegate.getErrorManager(name); + } + + @Override + public Map> getErrorManagers() { + return delegate.getErrorManagers(); + } + + @Override + public ConfigurationResource addHandler(final String name, final Supplier handler) { + return delegate.addHandler(name, handler); + } + + @Override + public ConfigurationResource removeHandler(final String name) { + return delegate.removeHandler(name); + } + + @Override + public boolean hasHandler(final String name) { + return delegate.hasHandler(name); + } + + @Override + public Handler getHandler(final String name) { + return delegate.getHandler(name); + } + + @Override + public Map> getHandlers() { + return delegate.getHandlers(); + } + + @Override + public ConfigurationResource addFormatter(final String name, final Supplier formatter) { + return delegate.addFormatter(name, formatter); + } + + @Override + public ConfigurationResource removeFormatter(final String name) { + return delegate.removeFormatter(name); + } + + @Override + public boolean hasFormatter(final String name) { + return delegate.hasFormatter(name); + } + + @Override + public Formatter getFormatter(final String name) { + return delegate.getFormatter(name); + } + + @Override + public Map> getFormatters() { + return delegate.getFormatters(); + } + + @Override + public ConfigurationResource addFilter(final String name, final Supplier filter) { + return delegate.addFilter(name, filter); + } + + @Override + public ConfigurationResource removeFilter(final String name) { + return delegate.removeFilter(name); + } + + @Override + public boolean hasFilter(final String name) { + return delegate.hasFilter(name); + } + + @Override + public Filter getFilter(final String name) { + return delegate.getFilter(name); + } + + @Override + public Map> getFilters() { + return delegate.getFilters(); + } + + @Override + public ConfigurationResource addObject(final String name, final Supplier object) { + return delegate.addObject(name, object); + } + + @Override + public ConfigurationResource removeObject(final String name) { + return delegate.removeObject(name); + } + + @Override + public boolean hasObject(final String name) { + return delegate.hasObject(name); + } + + @Override + public Object getObject(final String name) { + return delegate.getObject(name); + } + + @Override + public Map> getObjects() { + return delegate.getObjects(); + } + + @Override + public void close() throws Exception { + delegate.close(); + } +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/ErrorManagerConfiguration.java b/logmanager/src/main/java/org/jboss/logmanager/config/ErrorManagerConfiguration.java new file mode 100644 index 00000000000..7cc453d278a --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/ErrorManagerConfiguration.java @@ -0,0 +1,30 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import java.util.logging.ErrorManager; + +/** + * Configuration for an error manager. + * + * @author David M. Lloyd + */ +public interface ErrorManagerConfiguration extends ObjectConfigurable, PropertyConfigurable, NamedConfigurable { +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/ErrorManagerConfigurationImpl.java b/logmanager/src/main/java/org/jboss/logmanager/config/ErrorManagerConfigurationImpl.java new file mode 100644 index 00000000000..25b2ffc806c --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/ErrorManagerConfigurationImpl.java @@ -0,0 +1,53 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import java.util.logging.ErrorManager; + +import org.jboss.logmanager.configuration.ConfigurationResource; + +/** + * @author David M. Lloyd + */ +final class ErrorManagerConfigurationImpl extends AbstractPropertyConfiguration implements ErrorManagerConfiguration { + + ErrorManagerConfigurationImpl(final LogContextConfiguration configuration, final String name, final String moduleName, final String className, final String[] constructorProperties) { + super(ErrorManager.class, configuration, configuration.getErrorManagerConfigurations(), name, moduleName, className, constructorProperties); + } + + String getDescription() { + return "error manager"; + } + + @Override + void addConfigurationResource(final ConfigurationResource resource) { + getConfiguration().addErrorManager(getName(), resource); + } + + @Override + ConfigurationResource removeInstance() { + return getConfiguration().removeErrorManager(getName()); + } + + @Override + public ErrorManager getInstance() { + return getConfiguration().getErrorManager(getName()); + } +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/FilterConfiguration.java b/logmanager/src/main/java/org/jboss/logmanager/config/FilterConfiguration.java new file mode 100644 index 00000000000..9878c05a9e6 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/FilterConfiguration.java @@ -0,0 +1,29 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import java.util.logging.Filter; + +/** + * A configuration for a filter. + * @author David M. Lloyd + */ +public interface FilterConfiguration extends ObjectConfigurable, NamedConfigurable, PropertyConfigurable { +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/FilterConfigurationImpl.java b/logmanager/src/main/java/org/jboss/logmanager/config/FilterConfigurationImpl.java new file mode 100644 index 00000000000..30da3c74931 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/FilterConfigurationImpl.java @@ -0,0 +1,53 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import java.util.logging.Filter; + +import org.jboss.logmanager.configuration.ConfigurationResource; + +/** + * @author David M. Lloyd + */ +final class FilterConfigurationImpl extends AbstractPropertyConfiguration implements FilterConfiguration { + + FilterConfigurationImpl(final LogContextConfiguration configuration, final String name, final String moduleName, final String className, final String[] constructorProperties) { + super(Filter.class, configuration, configuration.getFilterConfigurations(), name, moduleName, className, constructorProperties); + } + + String getDescription() { + return "filter"; + } + + @Override + void addConfigurationResource(final ConfigurationResource resource) { + getConfiguration().addFilter(getName(), resource); + } + + @Override + ConfigurationResource removeInstance() { + return getConfiguration().removeFilter(getName()); + } + + @Override + public Filter getInstance() { + return getConfiguration().getFilter(getName()); + } +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/FormatterConfiguration.java b/logmanager/src/main/java/org/jboss/logmanager/config/FormatterConfiguration.java new file mode 100644 index 00000000000..77983ed07ff --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/FormatterConfiguration.java @@ -0,0 +1,30 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import java.util.logging.Formatter; + +/** + * A configuration for a logger formatter. + * + * @author David M. Lloyd + */ +public interface FormatterConfiguration extends NamedConfigurable, ObjectConfigurable, PropertyConfigurable { +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/FormatterConfigurationImpl.java b/logmanager/src/main/java/org/jboss/logmanager/config/FormatterConfigurationImpl.java new file mode 100644 index 00000000000..0d52b4e92b4 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/FormatterConfigurationImpl.java @@ -0,0 +1,53 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import java.util.logging.Formatter; + +import org.jboss.logmanager.configuration.ConfigurationResource; + +/** + * @author David M. Lloyd + */ +final class FormatterConfigurationImpl extends AbstractPropertyConfiguration implements FormatterConfiguration { + + FormatterConfigurationImpl(final LogContextConfiguration configuration, final String name, final String moduleName, final String className, final String[] constructorProperties) { + super(Formatter.class, configuration, configuration.getFormatterConfigurations(), name, moduleName, className, constructorProperties); + } + + String getDescription() { + return "formatter"; + } + + @Override + void addConfigurationResource(final ConfigurationResource resource) { + getConfiguration().addFormatter(getName(), resource); + } + + @Override + ConfigurationResource removeInstance() { + return getConfiguration().removeFormatter(getName()); + } + + @Override + public Formatter getInstance() { + return getConfiguration().getFormatter(getName()); + } +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/HandlerConfiguration.java b/logmanager/src/main/java/org/jboss/logmanager/config/HandlerConfiguration.java new file mode 100644 index 00000000000..4a63691577f --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/HandlerConfiguration.java @@ -0,0 +1,177 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import java.util.logging.Handler; + +/** + * Configuration for a single handler. + * + * @author David M. Lloyd + */ +public interface HandlerConfiguration extends HandlerContainingConfigurable, NamedConfigurable, PropertyConfigurable, ObjectConfigurable { + + /** + * Get the name of the configured formatter for this handler. + * + * @return the formatter name + */ + String getFormatterName(); + + /** + * Gets the formatter name which may be an expression. + * + * @return the formatter name + */ + ValueExpression getFormatterNameValueExpression(); + + /** + * Set the name of the configured formatter for this handler. + * + * @param name the formatter name + */ + void setFormatterName(String name); + + + /** + * Sets the expression value for the formatter name. + *

+ * This method will not parse the expression for the value and instead use the {@code level} parameter for the + * formatter name on the handler. + * + * @param expression the expression used to resolve the level + * @param value the value to set the formatter name to + * + * @see #setFormatterName(String) + * @see ValueExpression + */ + void setFormatterName(String expression, String value); + + /** + * Gets the level set on the handler. + * + * @return the level + */ + String getLevel(); + + /** + * Returns the level that may be an expression. + * + * @return the level + */ + ValueExpression getLevelValueExpression(); + + /** + * Sets the level on the handler. + * + * @param level the level to set, may be an expression + * + * @see ValueExpression + */ + void setLevel(String level); + + /** + * Sets the expression value for the level. + *

+ * This method will not parse the expression for the value and instead use the {@code level} parameter for the + * level on the handler. + * + * @param expression the expression used to resolve the level + * @param level the level to use + * + * @see #setLevel(String) + * @see ValueExpression + */ + void setLevel(String expression, String level); + + // TODO (jrp) better document ValueExpression and filter expressions + + String getFilter(); + + /** + * Returns a filter that may be an expression. + * + * @return the filter + */ + ValueExpression getFilterValueExpression(); + + void setFilter(String name); + + /** + * Sets the expression value and for the filter. + *

+ * This method will not parse the expression for the value and instead use the {@code value} parameter for the + * filter on the handler. + * + * @param expression the expression + * @param value the value to set the filter to + */ + void setFilter(String expression, String value); + + String getEncoding(); + + /** + * Returns the encoding which may be an expression. + * + * @return the encoding + */ + ValueExpression getEncodingValueExpression(); + + void setEncoding(String name); + + /** + * Sets the expression value for the encoding. + *

+ * This method will not parse the expression for the value and instead use the {@code value} parameter for the + * encoding on the handler. + * + * @param expression the expression + * @param value the value to set the encoding to + * + * @see #setEncoding(String) + * @see ValueExpression + */ + void setEncoding(String expression, String value); + + String getErrorManagerName(); + + /** + * Returns the error manager name which may be an expression. + * + * @return the error manager name + */ + ValueExpression getErrorManagerNameValueExpression(); + + void setErrorManagerName(String name); + + /** + * Sets the expression value for the error manager name. + *

+ * This method will not parse the expression for the value and instead use the {@code value} parameter for the + * error manager name on the handler. + * + * @param expression the expression + * @param value the value to set the error manager name to + * + * @see #setErrorManagerName(String) + * @see ValueExpression + */ + void setErrorManagerName(String expression, String value); +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/HandlerConfigurationImpl.java b/logmanager/src/main/java/org/jboss/logmanager/config/HandlerConfigurationImpl.java new file mode 100644 index 00000000000..39f30fa66aa --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/HandlerConfigurationImpl.java @@ -0,0 +1,467 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import static java.util.Arrays.asList; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.logging.Filter; +import java.util.logging.Handler; +import java.util.logging.Level; + +import org.jboss.logmanager.ExtHandler; +import org.jboss.logmanager.configuration.ConfigurationResource; + +/** + * @author David M. Lloyd + */ +final class HandlerConfigurationImpl extends AbstractPropertyConfiguration implements HandlerConfiguration { + + private final List handlerNames = new ArrayList(0); + + private ValueExpression formatterName; + private ValueExpression level; + private ValueExpression filter; + private ValueExpression encoding; + private ValueExpression errorManagerName; + + HandlerConfigurationImpl(final LogContextConfiguration configuration, final String name, final String moduleName, final String className, final String[] constructorProperties) { + super(Handler.class, configuration, configuration.getHandlerConfigurations(), name, moduleName, className, constructorProperties); + } + + public String getFormatterName() { + return getFormatterNameValueExpression().getResolvedValue(); + } + + @Override + public ValueExpression getFormatterNameValueExpression() { + return formatterName == null ? ValueExpression.NULL_STRING_EXPRESSION : formatterName; + } + + public void setFormatterName(final String formatterName) { + setFormatterName(ValueExpression.STRING_RESOLVER.resolve(formatterName)); + } + + @Override + public void setFormatterName(final String expression, final String value) { + setFormatterName(new ValueExpressionImpl(expression, value)); + } + + private void setFormatterName(final ValueExpression expression) { + final ValueExpression oldFormatterName = this.formatterName; + this.formatterName = expression; + final String formatterName = expression.getResolvedValue(); + final LogContextConfiguration configuration = getConfiguration(); + configuration.addAction(new ConfigAction() { + public Void validate() throws IllegalArgumentException { + if (formatterName != null && configuration.getFormatterConfiguration(formatterName) == null) { + throw new IllegalArgumentException(String.format("Formatter \"%s\" is not found", formatterName)); + } + return null; + } + + public void applyPreCreate(final Void param) { + addPostConfigurationActions(); + } + + public void applyPostCreate(final Void param) { + getInstance().setFormatter(formatterName == null ? null : configuration.getFormatter(formatterName)); + } + + public void rollback() { + HandlerConfigurationImpl.this.formatterName = oldFormatterName; + } + }); + } + + public String getLevel() { + return getLevelValueExpression().getResolvedValue(); + } + + @Override + public ValueExpression getLevelValueExpression() { + return level == null ? ValueExpression.NULL_STRING_EXPRESSION : level; + } + + public void setLevel(final String level) { + setLevelValueExpression(ValueExpression.STRING_RESOLVER.resolve(level)); + } + + @Override + public void setLevel(final String expression, final String level) { + setLevelValueExpression(new ValueExpressionImpl<>(expression, level)); + } + + private void setLevelValueExpression(final ValueExpression expression) { + final ValueExpression oldLevel = this.level; + this.level = expression; + final String resolvedLevel = expression.getResolvedValue(); + final LogContextConfiguration configuration = getConfiguration(); + configuration.addAction(new ConfigAction() { + public Level validate() throws IllegalArgumentException { + return resolvedLevel == null ? null : configuration.getContext().getLevelForName(resolvedLevel); + } + + public void applyPreCreate(final Level param) { + addPostConfigurationActions(); + } + + public void applyPostCreate(final Level param) { + getInstance().setLevel(param); + } + + public void rollback() { + HandlerConfigurationImpl.this.level = oldLevel; + } + }); + } + + public String getFilter() { + return getFilterValueExpression().getResolvedValue(); + } + + @Override + public ValueExpression getFilterValueExpression() { + return filter == null ? ValueExpression.NULL_STRING_EXPRESSION : filter; + } + + public void setFilter(final String filter) { + setFilter(ValueExpression.STRING_RESOLVER.resolve(filter)); + } + + @Override + public void setFilter(final String expression, final String value) { + setFilter(new ValueExpressionImpl(expression, value)); + } + + private void setFilter(final ValueExpression expression) { + final ValueExpression oldFilterName = this.filter; + this.filter = expression; + final String filterName = expression.getResolvedValue(); + final LogContextConfiguration configuration = getConfiguration(); + configuration.addAction(new ConfigAction() { + public ObjectProducer validate() throws IllegalArgumentException { + return configuration.resolveFilter(filterName); + } + + public void applyPreCreate(final ObjectProducer param) { + addPostConfigurationActions(); + } + + public void applyPostCreate(final ObjectProducer param) { + getInstance().setFilter((Filter) param.getObject()); + } + + public void rollback() { + HandlerConfigurationImpl.this.filter = oldFilterName; + } + }); + } + + public String getEncoding() { + return getEncodingValueExpression().getResolvedValue(); + } + + @Override + public ValueExpression getEncodingValueExpression() { + return encoding == null ? ValueExpression.NULL_STRING_EXPRESSION : encoding; + } + + public void setEncoding(final String encoding) { + setEncoding(ValueExpression.STRING_RESOLVER.resolve(encoding)); + } + + @Override + public void setEncoding(final String expression, final String value) { + setEncoding(new ValueExpressionImpl(expression, value)); + } + + private void setEncoding(final ValueExpression expression) { + final ValueExpression oldEncoding = this.encoding; + this.encoding = expression; + final String encoding = expression.getResolvedValue(); + final LogContextConfiguration configuration = getConfiguration(); + configuration.addAction(new ConfigAction() { + public Void validate() throws IllegalArgumentException { + if (encoding != null) { + try { + Charset.forName(encoding); + } catch (Throwable t) { + throw new IllegalArgumentException(String.format("Unsupported character set \"%s\"", encoding)); + } + } + return null; + } + + public void applyPreCreate(final Void param) { + addPostConfigurationActions(); + } + + public void applyPostCreate(final Void param) { + try { + getInstance().setEncoding(encoding); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException(String.format("The encoding value '%s' is invalid.", encoding), e); + } + } + + public void rollback() { + HandlerConfigurationImpl.this.encoding = oldEncoding; + } + }); + } + + public String getErrorManagerName() { + return getErrorManagerNameValueExpression().getResolvedValue(); + } + + @Override + public ValueExpression getErrorManagerNameValueExpression() { + return errorManagerName == null ? ValueExpression.NULL_STRING_EXPRESSION : errorManagerName; + } + + public void setErrorManagerName(final String errorManagerName) { + setErrorManagerName(ValueExpression.STRING_RESOLVER.resolve(errorManagerName)); + } + + @Override + public void setErrorManagerName(final String expression, final String value) { + setErrorManagerName(new ValueExpressionImpl(expression, value)); + } + + private void setErrorManagerName(final ValueExpression expression) { + final ValueExpression oldErrorManagerName = this.errorManagerName; + this.errorManagerName = expression; + final String errorManagerName = expression.getResolvedValue(); + final LogContextConfiguration configuration = getConfiguration(); + configuration.addAction(new ConfigAction() { + public Void validate() throws IllegalArgumentException { + if (errorManagerName != null && configuration.getErrorManagerConfiguration(errorManagerName) == null) { + throw new IllegalArgumentException(String.format("errorManager \"%s\" is not found", errorManagerName)); + } + return null; + } + + public void applyPreCreate(final Void param) { + addPostConfigurationActions(); + } + + public void applyPostCreate(final Void param) { + getInstance().setErrorManager(errorManagerName == null ? null : configuration.getErrorManager(errorManagerName)); + } + + public void rollback() { + HandlerConfigurationImpl.this.errorManagerName = oldErrorManagerName; + } + }); + } + + public List getHandlerNames() { + return new ArrayList<>(handlerNames); + } + + public void setHandlerNames(final String... names) { + final String[] oldHandlerNames = handlerNames.toArray(new String[0]); + handlerNames.clear(); + final LinkedHashSet strings = new LinkedHashSet<>(asList(names)); + handlerNames.addAll(strings); + final String[] stringsArray = strings.toArray(new String[0]); + final LogContextConfiguration configuration = getConfiguration(); + if (!ExtHandler.class.isAssignableFrom(getActualClass())) { + if (names.length == 0) { + return; + } + throw new IllegalArgumentException("Nested handlers not supported for handler " + getActualClass()); + } + configuration.addAction(new ConfigAction() { + public Void validate() throws IllegalArgumentException { + for (String name : stringsArray) { + if (configuration.getHandlerConfiguration(name) == null) { + throw new IllegalArgumentException(String.format("Handler \"%s\" is not found", name)); + } + } + return null; + } + + public void applyPreCreate(final Void param) { + addPostConfigurationActions(); + } + + public void applyPostCreate(final Void param) { + final ExtHandler handler = (ExtHandler) getInstance(); + final int length = stringsArray.length; + final Handler[] handlers = new Handler[length]; + for (int i = 0; i < length; i++) { + handlers[i] = configuration.getHandler(stringsArray[i]); + } + handler.setHandlers(handlers); + } + + public void rollback() { + handlerNames.clear(); + handlerNames.addAll(asList(oldHandlerNames)); + } + }); + } + + public void setHandlerNames(final Collection names) { + setHandlerNames(names.toArray(new String[0])); + } + + public boolean addHandlerName(final String name) { + final LogContextConfiguration configuration = getConfiguration(); + if (!ExtHandler.class.isAssignableFrom(getActualClass())) { + throw new IllegalArgumentException("Nested handlers not supported for handler " + getActualClass()); + } + if (handlerNames.contains(name)) { + return false; + } + handlerNames.add(name); + configuration.addAction(new ConfigAction() { + public Void validate() throws IllegalArgumentException { + if (configuration.getHandlerConfiguration(name) == null) { + throw new IllegalArgumentException(String.format("Handler \"%s\" is not found", name)); + } + return null; + } + + public void applyPreCreate(final Void param) { + addPostConfigurationActions(); + } + + public void applyPostCreate(final Void param) { + final ExtHandler handler = (ExtHandler) getInstance(); + handler.addHandler(configuration.getHandler(name)); + } + + public void rollback() { + handlerNames.remove(name); + } + }); + return true; + } + + public boolean removeHandlerName(final String name) { + final LogContextConfiguration configuration = getConfiguration(); + if (!ExtHandler.class.isAssignableFrom(getActualClass())) { + return false; + } + if (!handlerNames.contains(name)) { + return false; + } + final int index = handlerNames.indexOf(name); + handlerNames.remove(index); + configuration.addAction(new ConfigAction() { + public Handler validate() throws IllegalArgumentException { + return getInstance(); + } + + public void applyPreCreate(final Handler param) { + addPostConfigurationActions(); + } + + public void applyPostCreate(final Handler param) { + final ExtHandler handler = (ExtHandler) getInstance(); + handler.removeHandler(param); + } + + public void rollback() { + handlerNames.add(index, name); + } + }); + return true; + } + + String getDescription() { + return "handler"; + } + + @Override + void addConfigurationResource(final ConfigurationResource resource) { + getConfiguration().addHandler(getName(), resource); + } + + @Override + ConfigAction getConstructAction() { + return new ConstructAction() { + @Override + public void rollback() { + final ConfigurationResource handler = removeInstance(); + if (handler != null) { + try { + handler.close(); + } catch (Exception ignore) { + } + } + super.rollback(); + } + }; + } + + + @Override + ConfigurationResource removeInstance() { + return getConfiguration().removeHandler(getName()); + } + + @Override + ConfigAction getRemoveAction() { + return new ConfigAction<>() { + @Override + public Void validate() throws IllegalArgumentException { + return null; + } + + @Override + public void applyPreCreate(final Void param) { + final ConfigurationResource handler = removeInstance(); + if (handler != null) { + try { + handler.close(); + } catch (Exception e) { + // TODO (jrp) what do we do here? + } + } + } + + @Override + public void applyPostCreate(final Void param) { + removePostConfigurationActions(); + } + + @Override + public void rollback() { + configs.put(getName(), HandlerConfigurationImpl.this); + clearRemoved(); + addPostConfigurationActions(); + } + }; + } + + @Override + public Handler getInstance() { + return getConfiguration().getHandler(getName()); + } +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/HandlerContainingConfigurable.java b/logmanager/src/main/java/org/jboss/logmanager/config/HandlerContainingConfigurable.java new file mode 100644 index 00000000000..61b61e70b46 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/HandlerContainingConfigurable.java @@ -0,0 +1,69 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import java.util.Collection; +import java.util.List; + +/** + * A configurable object which is a container for handlers. + * + * @author David M. Lloyd + */ +public interface HandlerContainingConfigurable { + + /** + * Get the names of the configured handlers. + * + * @return the names of the configured handlers + */ + List getHandlerNames(); + + /** + * Set the names of the configured handlers. + * + * @param names the names of the configured handlers + */ + void setHandlerNames(String... names); + + /** + * Set the names of the configured handlers. + * + * @param names the names of the configured handlers + */ + void setHandlerNames(Collection names); + + /** + * Add a handler name to this logger. + * + * @param name the handler name + * @return {@code true} if the name was not already set, {@code false} if it was + */ + boolean addHandlerName(String name); + + /** + * Remove a handler name from this logger. + * + * @param name the handler name + * @return {@code true} if the name was removed, {@code false} if it was not present + */ + boolean removeHandlerName(String name); + +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/LogContextConfiguration.java b/logmanager/src/main/java/org/jboss/logmanager/config/LogContextConfiguration.java new file mode 100644 index 00000000000..1e95ef09a55 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/LogContextConfiguration.java @@ -0,0 +1,815 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import static java.lang.Character.isJavaIdentifierPart; +import static java.lang.Character.isJavaIdentifierStart; +import static java.lang.Character.isWhitespace; + +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TimeZone; +import java.util.logging.ErrorManager; +import java.util.logging.Filter; +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.Level; + +import org.jboss.logmanager.LogContext; +import org.jboss.logmanager.Logger; +import org.jboss.logmanager.configuration.ContextConfiguration; +import org.jboss.logmanager.filters.AcceptAllFilter; +import org.jboss.logmanager.filters.AllFilter; +import org.jboss.logmanager.filters.AnyFilter; +import org.jboss.logmanager.filters.DenyAllFilter; +import org.jboss.logmanager.filters.InvertFilter; +import org.jboss.logmanager.filters.LevelChangingFilter; +import org.jboss.logmanager.filters.LevelFilter; +import org.jboss.logmanager.filters.LevelRangeFilter; +import org.jboss.logmanager.filters.RegexFilter; +import org.jboss.logmanager.filters.SubstituteFilter; +import org.wildfly.core.logmanager.WildFlyLogContextSelector; + +/** + * A log context configuration. + * + * @author David M. Lloyd + */ +// TODO (jrp) This should become not an interface and should a class that extends ContextConfiguration. +// TODO (jrp) We then just have stuff like LoggerConfiguration addLoggerConfiguration(loggerName) +// TODO (jrp) Then each *Configuration should likely extend ConfigurationResource +// TODO (jrp) this needs to be renamed to WildFlContextConfiguration, but that will require a refactor of the subsystem as well +public class LogContextConfiguration extends DelegatingContextConfiguration { + + private final Map loggers = new HashMap<>(); + private final Map handlers = new HashMap<>(); + private final Map formatters = new HashMap<>(); + private final Map filters = new HashMap<>(); + private final Map errorManagers = new HashMap<>(); + private final Map pojos = new HashMap<>(); + + private final Deque> transactionState = new ArrayDeque<>(); + private final Map>> postConfigurationTransactionState = new LinkedHashMap<>(); + private final Deque> preparedTransactions = new ArrayDeque<>(); + + private boolean prepared = false; + + // TODO (jrp) there are better ways to do this + private static final ObjectProducer ACCEPT_PRODUCER = new SimpleObjectProducer(AcceptAllFilter.getInstance()); + private static final ObjectProducer DENY_PRODUCER = new SimpleObjectProducer(DenyAllFilter.getInstance()); + + /** + * Creates a new context configuration. + * + * @param context + */ + public LogContextConfiguration(final LogContext context) { + super(new ContextConfiguration(context)); + } + + public LogContextConfiguration(final ContextConfiguration delegate) { + super(delegate); + } + + public static LogContextConfiguration getInstance() { + return getInstance(LogContext.getLogContext()); + } + + // TODO (jrp) what do we do here? Just get the LogContext and then use getInstance(LogContext)? + public static LogContextConfiguration getInstance(final String name) { + // TODO (jrp) we'll need a way to get this for logging profiles as well. + // TODO (jrp) what do we do if this is null? It shouldn't happen, but that means this has been accessed before + // TODO (jrp) the WildFlyConfiguratorFactory has been completed + + // TODO (jrp) I think what we should do is move the LoggingProfileSelector and the WildFlyContextSelector here + final var selector = WildFlyLogContextSelector.getContextSelector(); + final var profileContext = selector.getProfileContext(name); + return profileContext == null ? getInstance(selector.getLogContext()) : getInstance(profileContext); + } + + public static LogContextConfiguration getInstance(final LogContext logContext) { + var configuration = logContext.getAttachment(ContextConfiguration.CONTEXT_CONFIGURATION_KEY); + if (configuration == null) { + configuration = new LogContextConfiguration(logContext); + final var appearing = logContext.attachIfAbsent(ContextConfiguration.CONTEXT_CONFIGURATION_KEY, configuration); + if (appearing != null) { + configuration = appearing; + } + } + // TODO (jrp) this is not thread safe + if (!(configuration instanceof LogContextConfiguration)) { + configuration = new LogContextConfiguration(configuration); + logContext.attach(ContextConfiguration.CONTEXT_CONFIGURATION_KEY, configuration); + } + return (LogContextConfiguration) configuration; + } + + public static LogContextConfiguration create(final LogContext context, final Properties properties) { + // TODO (jrp) this should likely be attached to the logContext + final LogContextConfiguration configuration = new LogContextConfiguration(context); + PropertyLogContextConfiguration.configure(configuration, properties); + return configuration; + } + + public static LogContextConfiguration getEmbedded() { + final LogContext logContext = WildFlyLogContextSelector.getEmbeddedContextSelector().getLogContext(); + var configuration = logContext.getAttachment(ContextConfiguration.CONTEXT_CONFIGURATION_KEY); + // If there is not configuration attached, then we need to configure it based on the possible properties + if (configuration == null) { + configuration = new LogContextConfiguration(logContext); + final var appearing = logContext.attachIfAbsent(ContextConfiguration.CONTEXT_CONFIGURATION_KEY, configuration); + if (appearing != null) { + configuration = appearing; + } + // TODO (jrp) this is not thread safe + if (!(configuration instanceof LogContextConfiguration)) { + configuration = new LogContextConfiguration(configuration); + logContext.attach(ContextConfiguration.CONTEXT_CONFIGURATION_KEY, configuration); + } + PropertyLogContextConfiguration.configure((LogContextConfiguration) configuration, (InputStream) null); + } + return (LogContextConfiguration) configuration; + } + + // TODO (jrp) rename this to getContext() for refactoring + public LogContext getLogContext() { + return getContext(); + } + + public LoggerConfiguration addLoggerConfiguration(final String loggerName) { + if (loggers.containsKey(loggerName)) { + throw new IllegalArgumentException(String.format("Logger \"%s\" already exists", loggerName)); + } + final LoggerConfigurationImpl loggerConfiguration = new LoggerConfigurationImpl(loggerName, this); + loggers.put(loggerName, loggerConfiguration); + transactionState.addLast(new ConfigAction() { + public Logger validate() throws IllegalArgumentException { + return getLogger(loggerName); + } + + public void applyPreCreate(final Logger param) { + } + + public void applyPostCreate(Logger param) { + } + + public void rollback() { + loggers.remove(loggerName); + } + }); + return loggerConfiguration; + } + + public boolean removeLoggerConfiguration(final String loggerName) { + final LoggerConfigurationImpl removed = loggers.remove(loggerName); + if (removed != null) { + transactionState.addLast(removed.getRemoveAction()); + removed.setRemoved(); + return true; + } else { + return false; + } + } + + public LoggerConfiguration getLoggerConfiguration(final String loggerName) { + return loggers.get(loggerName); + } + + public List getLoggerNames() { + return new ArrayList<>(loggers.keySet()); + } + + public HandlerConfiguration addHandlerConfiguration(final String moduleName, final String className, final String handlerName, final String... constructorProperties) { + if (handlers.containsKey(handlerName)) { + throw new IllegalArgumentException(String.format("Handler \"%s\" already exists", handlerName)); + } + final HandlerConfigurationImpl handlerConfiguration = new HandlerConfigurationImpl(this, handlerName, moduleName, className, constructorProperties); + handlers.put(handlerName, handlerConfiguration); + addAction(handlerConfiguration.getConstructAction()); + return handlerConfiguration; + } + + public boolean removeHandlerConfiguration(final String handlerName) { + final HandlerConfigurationImpl removed = handlers.remove(handlerName); + if (removed != null) { + transactionState.addLast(removed.getRemoveAction()); + removed.setRemoved(); + return true; + } else { + return false; + } + } + + public HandlerConfiguration getHandlerConfiguration(final String handlerName) { + return handlers.get(handlerName); + } + + public List getHandlerNames() { + return new ArrayList<>(handlers.keySet()); + } + + public FormatterConfiguration addFormatterConfiguration(final String moduleName, final String className, final String formatterName, final String... constructorProperties) { + if (formatters.containsKey(formatterName)) { + throw new IllegalArgumentException(String.format("Formatter \"%s\" already exists", formatterName)); + } + final FormatterConfigurationImpl formatterConfiguration = new FormatterConfigurationImpl(this, formatterName, moduleName, className, constructorProperties); + formatters.put(formatterName, formatterConfiguration); + addAction(formatterConfiguration.getConstructAction()); + return formatterConfiguration; + } + + public boolean removeFormatterConfiguration(final String formatterName) { + final FormatterConfigurationImpl removed = formatters.remove(formatterName); + if (removed != null) { + transactionState.addLast(removed.getRemoveAction()); + removed.setRemoved(); + return true; + } else { + return false; + } + } + + public FormatterConfiguration getFormatterConfiguration(final String formatterName) { + return formatters.get(formatterName); + } + + public List getFormatterNames() { + return new ArrayList<>(formatters.keySet()); + } + + public FilterConfiguration addFilterConfiguration(final String moduleName, final String className, final String filterName, final String... constructorProperties) { + if (filters.containsKey(filterName)) { + throw new IllegalArgumentException(String.format("Filter \"%s\" already exists", filterName)); + } + final FilterConfigurationImpl filterConfiguration = new FilterConfigurationImpl(this, filterName, moduleName, className, constructorProperties); + filters.put(filterName, filterConfiguration); + addAction(filterConfiguration.getConstructAction()); + return filterConfiguration; + } + + public boolean removeFilterConfiguration(final String filterName) { + final FilterConfigurationImpl removed = filters.remove(filterName); + if (removed != null) { + transactionState.addLast(removed.getRemoveAction()); + removed.setRemoved(); + return true; + } else { + return false; + } + } + + public FilterConfiguration getFilterConfiguration(final String filterName) { + return filters.get(filterName); + } + + public List getFilterNames() { + return new ArrayList<>(filters.keySet()); + } + + public ErrorManagerConfiguration addErrorManagerConfiguration(final String moduleName, final String className, final String errorManagerName, final String... constructorProperties) { + if (errorManagers.containsKey(errorManagerName)) { + throw new IllegalArgumentException(String.format("ErrorManager \"%s\" already exists", errorManagerName)); + } + final ErrorManagerConfigurationImpl errorManagerConfiguration = new ErrorManagerConfigurationImpl(this, errorManagerName, moduleName, className, constructorProperties); + errorManagers.put(errorManagerName, errorManagerConfiguration); + addAction(errorManagerConfiguration.getConstructAction()); + return errorManagerConfiguration; + } + + public boolean removeErrorManagerConfiguration(final String errorManagerName) { + final ErrorManagerConfigurationImpl removed = errorManagers.remove(errorManagerName); + if (removed != null) { + transactionState.addLast(removed.getRemoveAction()); + removed.setRemoved(); + return true; + } else { + return false; + } + } + + public ErrorManagerConfiguration getErrorManagerConfiguration(final String errorManagerName) { + return errorManagers.get(errorManagerName); + } + + public List getErrorManagerNames() { + return new ArrayList<>(errorManagers.keySet()); + } + + public PojoConfiguration addPojoConfiguration(final String moduleName, final String className, final String pojoName, final String... constructorProperties) { + if (pojos.containsKey(pojoName)) { + throw new IllegalArgumentException(String.format("POJO \"%s\" already exists", pojoName)); + } + final PojoConfigurationImpl pojoConfiguration = new PojoConfigurationImpl(this, pojoName, moduleName, className, constructorProperties); + pojos.put(pojoName, pojoConfiguration); + transactionState.addLast(pojoConfiguration.getConstructAction()); + return pojoConfiguration; + } + + public boolean removePojoConfiguration(final String pojoName) { + final PojoConfigurationImpl removed = pojos.remove(pojoName); + if (removed != null) { + transactionState.addLast(removed.getRemoveAction()); + removed.setRemoved(); + return true; + } + return false; + } + + public PojoConfiguration getPojoConfiguration(final String pojoName) { + return pojos.get(pojoName); + } + + public List getPojoNames() { + return new ArrayList<>(pojos.keySet()); + } + + public void prepare() { + doPrepare(transactionState); + for (Deque> items : postConfigurationTransactionState.values()) { + doPrepare(items); + } + prepared = true; + } + + public void commit() { + if (!prepared) { + prepare(); + } + clear(); + } + + @SuppressWarnings("unchecked") + private static void doApplyPreCreate(ConfigAction action, Object arg) { + try { + action.applyPreCreate((T) arg); + } catch (Throwable ignored) { + } + } + + @SuppressWarnings("unchecked") + private static void doApplyPostCreate(ConfigAction action, Object arg) { + try { + action.applyPostCreate((T) arg); + } catch (Throwable ignored) { + } + } + + public void forget() { + doForget(transactionState); + doForget(preparedTransactions); + for (Deque> items : postConfigurationTransactionState.values()) { + doForget(items); + } + clear(); + } + + private void clear() { + prepared = false; + postConfigurationTransactionState.clear(); + transactionState.clear(); + preparedTransactions.clear(); + } + + private void doPrepare(final Deque> transactionState) { + List items = new ArrayList<>(); + for (ConfigAction action : transactionState) { + items.add(action.validate()); + preparedTransactions.add(action); + } + Iterator iterator = items.iterator(); + for (ConfigAction action : transactionState) { + doApplyPreCreate(action, iterator.next()); + } + iterator = items.iterator(); + for (ConfigAction action : transactionState) { + doApplyPostCreate(action, iterator.next()); + } + transactionState.clear(); + } + + private void doForget(final Deque> transactionState) { + Iterator> iterator = transactionState.descendingIterator(); + while (iterator.hasNext()) { + final ConfigAction action = iterator.next(); + try { + action.rollback(); + } catch (Throwable ignored) { + } + } + } + + void addAction(final ConfigAction action) { + transactionState.addLast(action); + } + + /** + * Adds or replaces the post configuration actions for the configuration identified by the {@code name} parameter. + * + * @param name the name of the configuration + * @param actions the actions to be invoked after the properties have been set + */ + void addPostConfigurationActions(final String name, final Deque> actions) { + if (actions != null && !actions.isEmpty()) { + postConfigurationTransactionState.put(name, actions); + } + } + + /** + * Removes the post configuration actions for the configuration identified by the {@code name} parameter. + * + * @param name the name of the configuration + */ + Deque removePostConfigurationActions(final String name) { + return postConfigurationTransactionState.remove(name); + } + + /** + * Checks to see if configuration actions have already been defined for the configuration. + * + * @param name the name of the configuration + * + * @return {@code true} if the configuration actions have been defined, otherwise {@code false} + */ + boolean postConfigurationActionsExist(final String name) { + return postConfigurationTransactionState.containsKey(name); + } + + // TODO (jrp) we should use the ValueResolver + ObjectProducer getValue(final Class objClass, final String propertyName, final Class paramType, final ValueExpression valueExpression, final boolean immediate) { + if (valueExpression == null || valueExpression.getResolvedValue() == null) { + if (paramType.isPrimitive()) { + throw new IllegalArgumentException(String.format("Cannot assign null value to primitive property \"%s\" of %s", propertyName, objClass)); + } + return ObjectProducer.NULL_PRODUCER; + } + final String resolvedValue = valueExpression.getResolvedValue(); + final String trimmedValue = resolvedValue.trim(); + if (paramType == String.class) { + // Don't use the trimmed value for strings + return new SimpleObjectProducer(resolvedValue); + } else if (paramType == Handler.class) { + if (!handlers.containsKey(trimmedValue) || immediate && !hasHandler(trimmedValue)) { + throw new IllegalArgumentException(String.format("No handler named \"%s\" is defined", trimmedValue)); + } + if (immediate) { + return new SimpleObjectProducer(getHandler(trimmedValue)); + } else { + return () -> getHandler(trimmedValue); + } + } else if (paramType == Filter.class) { + return resolveFilter(trimmedValue, immediate); + } else if (paramType == Formatter.class) { + if (!formatters.containsKey(trimmedValue) || immediate && !hasFormatter(trimmedValue)) { + throw new IllegalArgumentException(String.format("No formatter named \"%s\" is defined", trimmedValue)); + } + if (immediate) { + return new SimpleObjectProducer(getFormatter(trimmedValue)); + } else { + return () -> getFormatter(trimmedValue); + } + } else if (paramType == ErrorManager.class) { + if (!errorManagers.containsKey(trimmedValue) || immediate && !hasErrorManager(trimmedValue)) { + throw new IllegalArgumentException(String.format("No error manager named \"%s\" is defined", trimmedValue)); + } + if (immediate) { + return new SimpleObjectProducer(getErrorManager(trimmedValue)); + } else { + return () -> getErrorManager(trimmedValue); + } + } else if (paramType == Level.class) { + return new SimpleObjectProducer(LogContext.getSystemLogContext().getLevelForName(trimmedValue)); + } else if (paramType == java.util.logging.Logger.class) { + return new SimpleObjectProducer(LogContext.getSystemLogContext().getLogger(trimmedValue)); + } else if (paramType == boolean.class || paramType == Boolean.class) { + return new SimpleObjectProducer(Boolean.valueOf(trimmedValue)); + } else if (paramType == byte.class || paramType == Byte.class) { + return new SimpleObjectProducer(Byte.valueOf(trimmedValue)); + } else if (paramType == short.class || paramType == Short.class) { + return new SimpleObjectProducer(Short.valueOf(trimmedValue)); + } else if (paramType == int.class || paramType == Integer.class) { + return new SimpleObjectProducer(Integer.valueOf(trimmedValue)); + } else if (paramType == long.class || paramType == Long.class) { + return new SimpleObjectProducer(Long.valueOf(trimmedValue)); + } else if (paramType == float.class || paramType == Float.class) { + return new SimpleObjectProducer(Float.valueOf(trimmedValue)); + } else if (paramType == double.class || paramType == Double.class) { + return new SimpleObjectProducer(Double.valueOf(trimmedValue)); + } else if (paramType == char.class || paramType == Character.class) { + return new SimpleObjectProducer(Character.valueOf(trimmedValue.length() > 0 ? trimmedValue.charAt(0) : 0)); + } else if (paramType == TimeZone.class) { + return new SimpleObjectProducer(TimeZone.getTimeZone(trimmedValue)); + } else if (paramType == Charset.class) { + return new SimpleObjectProducer(Charset.forName(trimmedValue)); + } else if (paramType.isEnum()) { + return new SimpleObjectProducer(Enum.valueOf(paramType.asSubclass(Enum.class), trimmedValue)); + } else if (pojos.containsKey(trimmedValue)) { + return () -> getObject(trimmedValue); + } else { + throw new IllegalArgumentException("Unknown parameter type for property " + propertyName + " on " + objClass); + } + } + + Map getFilterConfigurations() { + return filters; + } + + Map getErrorManagerConfigurations() { + return errorManagers; + } + + Map getHandlerConfigurations() { + return handlers; + } + + Map getFormatterConfigurations() { + return formatters; + } + + Map getLoggerConfigurations() { + return loggers; + } + + Map getPojoConfigurations() { + return pojos; + } + + private static List tokens(String source) { + final List tokens = new ArrayList<>(); + final int length = source.length(); + int idx = 0; + while (idx < length) { + int ch; + ch = source.codePointAt(idx); + if (isWhitespace(ch)) { + ch = source.codePointAt(idx); + idx = source.offsetByCodePoints(idx, 1); + } else if (isJavaIdentifierStart(ch)) { + int start = idx; + do { + idx = source.offsetByCodePoints(idx, 1); + } while (idx < length && isJavaIdentifierPart(ch = source.codePointAt(idx))); + tokens.add(source.substring(start, idx)); + } else if (ch == '"') { + final StringBuilder b = new StringBuilder(); + // tag token as a string + b.append('"'); + idx = source.offsetByCodePoints(idx, 1); + while (idx < length && (ch = source.codePointAt(idx)) != '"') { + ch = source.codePointAt(idx); + if (ch == '\\') { + idx = source.offsetByCodePoints(idx, 1); + if (idx == length) { + throw new IllegalArgumentException("Truncated filter expression string"); + } + ch = source.codePointAt(idx); + switch (ch) { + case '\\': + b.append('\\'); + break; + case '\'': + b.append('\''); + break; + case '"': + b.append('"'); + break; + case 'b': + b.append('\b'); + break; + case 'f': + b.append('\f'); + break; + case 'n': + b.append('\n'); + break; + case 'r': + b.append('\r'); + break; + case 't': + b.append('\t'); + break; + default: + throw new IllegalArgumentException("Invalid escape found in filter expression string"); + } + } else { + b.appendCodePoint(ch); + } + idx = source.offsetByCodePoints(idx, 1); + } + idx = source.offsetByCodePoints(idx, 1); + tokens.add(b.toString()); + } else { + int start = idx; + idx = source.offsetByCodePoints(idx, 1); + tokens.add(source.substring(start, idx)); + } + } + return tokens; + } + + private ObjectProducer parseFilterExpression(Iterator iterator, boolean outermost, final boolean immediate) { + if (!iterator.hasNext()) { + if (outermost) { + return ObjectProducer.NULL_PRODUCER; + } + throw endOfExpression(); + } + final String token = iterator.next(); + if ("accept".equals(token)) { + return ACCEPT_PRODUCER; + } else if ("deny".equals(token)) { + return DENY_PRODUCER; + } else if ("not".equals(token)) { + expect("(", iterator); + final ObjectProducer nested = parseFilterExpression(iterator, false, immediate); + expect(")", iterator); + return new ObjectProducer() { + public Object getObject() { + return new InvertFilter((Filter) nested.getObject()); + } + }; + } else if ("all".equals(token)) { + expect("(", iterator); + final List producers = new ArrayList<>(); + do { + producers.add(parseFilterExpression(iterator, false, immediate)); + } while (expect(",", ")", iterator)); + return new ObjectProducer() { + public Object getObject() { + final int length = producers.size(); + final Filter[] filters = new Filter[length]; + for (int i = 0; i < length; i++) { + filters[i] = (Filter) producers.get(i).getObject(); + } + return new AllFilter(filters); + } + }; + } else if ("any".equals(token)) { + expect("(", iterator); + final List producers = new ArrayList<>(); + do { + producers.add(parseFilterExpression(iterator, false, immediate)); + } while (expect(",", ")", iterator)); + return new ObjectProducer() { + public Object getObject() { + final int length = producers.size(); + final Filter[] filters = new Filter[length]; + for (int i = 0; i < length; i++) { + filters[i] = (Filter) producers.get(i).getObject(); + } + return new AnyFilter(filters); + } + }; + } else if ("levelChange".equals(token)) { + expect("(", iterator); + final String levelName = expectName(iterator); + final Level level = getContext().getLevelForName(levelName); + expect(")", iterator); + return new SimpleObjectProducer(new LevelChangingFilter(level)); + } else if ("levels".equals(token)) { + expect("(", iterator); + final Set levels = new HashSet(); + do { + levels.add(getContext().getLevelForName(expectName(iterator))); + } while (expect(",", ")", iterator)); + return new SimpleObjectProducer(new LevelFilter(levels)); + } else if ("levelRange".equals(token)) { + final boolean minInclusive = expect("[", "(", iterator); + final Level minLevel = getContext().getLevelForName(expectName(iterator)); + expect(",", iterator); + final Level maxLevel = getContext().getLevelForName(expectName(iterator)); + final boolean maxInclusive = expect("]", ")", iterator); + return new SimpleObjectProducer(new LevelRangeFilter(minLevel, minInclusive, maxLevel, maxInclusive)); + } else if ("match".equals(token)) { + expect("(", iterator); + final String pattern = expectString(iterator); + expect(")", iterator); + return new SimpleObjectProducer(new RegexFilter(pattern)); + } else if ("substitute".equals(token)) { + expect("(", iterator); + final String pattern = expectString(iterator); + expect(",", iterator); + final String replacement = expectString(iterator); + expect(")", iterator); + return new SimpleObjectProducer(new SubstituteFilter(pattern, replacement, false)); + } else if ("substituteAll".equals(token)) { + expect("(", iterator); + final String pattern = expectString(iterator); + expect(",", iterator); + final String replacement = expectString(iterator); + expect(")", iterator); + return new SimpleObjectProducer(new SubstituteFilter(pattern, replacement, true)); + } else { + if (!filters.containsKey(token) || immediate && !hasFilter(token)) { + throw new IllegalArgumentException(String.format("No filter named \"%s\" is defined", token)); + } + if (immediate) { + return new SimpleObjectProducer(getFilter(token)); + } else { + return () -> getFilter(token); + } + } + } + + private static String expectName(Iterator iterator) { + if (iterator.hasNext()) { + final String next = iterator.next(); + if (isJavaIdentifierStart(next.codePointAt(0))) { + return next; + } + } + throw new IllegalArgumentException("Expected identifier next in filter expression"); + } + + private static String expectString(Iterator iterator) { + if (iterator.hasNext()) { + final String next = iterator.next(); + if (next.codePointAt(0) == '"') { + return next.substring(1); + } + } + throw new IllegalArgumentException("Expected string next in filter expression"); + } + + private static boolean expect(String trueToken, String falseToken, Iterator iterator) { + final boolean hasNext = iterator.hasNext(); + final String next = hasNext ? iterator.next() : null; + final boolean result; + if (!hasNext || !((result = trueToken.equals(next)) || falseToken.equals(next))) { + throw new IllegalArgumentException("Expected '" + trueToken + "' or '" + falseToken + "' next in filter expression"); + } + return result; + } + + private static void expect(String token, Iterator iterator) { + if (!iterator.hasNext() || !token.equals(iterator.next())) { + throw new IllegalArgumentException("Expected '" + token + "' next in filter expression"); + } + } + + private static IllegalArgumentException endOfExpression() { + return new IllegalArgumentException("Unexpected end of filter expression"); + } + + private ObjectProducer resolveFilter(String expression, final boolean immediate) { + if (expression == null) { + return ObjectProducer.NULL_PRODUCER; + } + // First check for a defined filter + if (filters.containsKey(expression)) { + if (immediate) { + return new SimpleObjectProducer(getFilter(expression)); + } else { + return () -> getFilter(expression); + } + } + final Iterator iterator = tokens(expression).iterator(); + final ObjectProducer result = parseFilterExpression(iterator, true, immediate); + if (iterator.hasNext()) { + throw new IllegalArgumentException("Extra data after filter expression"); + } + return result; + } + + ObjectProducer resolveFilter(String expression) { + return resolveFilter(expression, false); + } + + @Override + public void close() throws Exception { + super.close(); + loggers.clear(); + handlers.clear(); + filters.clear(); + formatters.clear(); + errorManagers.clear(); + pojos.clear(); + } +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/LoggerConfiguration.java b/logmanager/src/main/java/org/jboss/logmanager/config/LoggerConfiguration.java new file mode 100644 index 00000000000..6784a165c91 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/LoggerConfiguration.java @@ -0,0 +1,192 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +/** + * Configuration for a single logger. + * + * @author David M. Lloyd + */ +public interface LoggerConfiguration extends NamedConfigurable, HandlerContainingConfigurable { + + // TODO (jrp) better document ValueExpression and filter expressions + + /** + * Get the name of the filter to use. + * + * @return the filter name + */ + String getFilter(); + + /** + * Returns a filter that may be an expression. + * + * @return the filter + */ + ValueExpression getFilterValueExpression(); + + /** + * Set the name of the filter to use, or {@code null} to leave unconfigured. + * + * @param name the filter name + */ + void setFilter(String name); + + /** + * Sets the expression value and for the filter. + *

+ * This method will not parse the expression for the value and instead use the {@code value} parameter for the + * filter on the logger. + * + * @param expression the expression + * @param value the value to set the filter to + */ + void setFilter(String expression, String value); + + /** + * Determine whether parent filters will be used. + * + * @return the setting, or {@code null} to leave unconfigured + */ + Boolean getUseParentFilters(); + + /** + * Returns the value that may be an expression. + * + * @return the setting, or {@code null} to leave unconfigured as a value expression + */ + ValueExpression getUseParentFiltersValueExpression(); + + /** + * Set whether to use parent filters. A value of {@code null} indicates that the value should be left + * unconfigured. + * + * @param value whether to use parent filters + */ + void setUseParentFilters(Boolean value); + + /** + * Set whether to use parent filters. + * + * @param expression the expression value used to resolve the setting + * + * @see #setUseParentFilters(Boolean) + * @see ValueExpression + */ + void setUseParentFilters(String expression); + + /** + * Set whether to use parent filters. + *

+ * This method will not parse the expression for the value and instead use the {@code value} parameter for the + * setting on the logger. + *

+ * + * @param expression the expression + * @param value the value to set the setting to + * + * @see #setUseParentFilters(Boolean) + * @see ValueExpression + */ + void setUseParentFilters(String expression, Boolean value); + + /** + * Determine whether parent handlers will be used. + * + * @return the setting, or {@code null} to leave unconfigured + */ + Boolean getUseParentHandlers(); + + /** + * Returns the value that may be an expression. + * + * @return the setting, or {@code null} to leave unconfigured as a value expression + */ + ValueExpression getUseParentHandlersValueExpression(); + + /** + * Set whether to use parent handlers. A value of {@code null} indicates that the value should be left + * unconfigured. + * + * @param value whether to use parent handlers + */ + void setUseParentHandlers(Boolean value); + + /** + * Set whether to use parent handlers. + * + * @param expression the expression value used to resolve the setting + * + * @see #setUseParentHandlers(Boolean) + * @see ValueExpression + */ + void setUseParentHandlers(String expression); + + /** + * Set whether to use parent handlers. + *

+ * This method will not parse the expression for the value and instead use the {@code value} parameter for the + * setting on the logger. + * + * @param expression the expression + * @param value the value to set the setting to + * + * @see #setUseParentHandlers(Boolean) + * @see ValueExpression + */ + void setUseParentHandlers(String expression, Boolean value); + + /** + * Gets the level set on the logger. + * + * @return the level + */ + String getLevel(); + + /** + * Returns the level that may be an expression. + * + * @return the level + */ + ValueExpression getLevelValueExpression(); + + /** + * Sets the level on the logger. + * + * @param level the level to set, may be an expression + * + * @see ValueExpression + */ + void setLevel(String level); + + /** + * Sets the expression value for the level. + *

+ * This method will not parse the expression for the value and instead use the {@code level} parameter for the + * level on the logger. + * + * @param expression the expression used to resolve the level + * @param level the level to use + * + * @see #setLevel(String) + * @see ValueExpression + */ + void setLevel(String expression, String level); +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/LoggerConfigurationImpl.java b/logmanager/src/main/java/org/jboss/logmanager/config/LoggerConfigurationImpl.java new file mode 100644 index 00000000000..dd2a29f3189 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/LoggerConfigurationImpl.java @@ -0,0 +1,383 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import static java.util.Arrays.asList; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.logging.Filter; +import java.util.logging.Handler; +import java.util.logging.Level; + +import org.jboss.logmanager.Logger; +import org.jboss.logmanager.configuration.ConfigurationResource; + +/** + * @author David M. Lloyd + */ +final class LoggerConfigurationImpl extends AbstractBasicConfiguration implements LoggerConfiguration { + private ValueExpression filter; + private ValueExpression useParentFilters; + private ValueExpression useParentHandlers; + private ValueExpression level; + private final List handlerNames = new ArrayList(0); + + LoggerConfigurationImpl(final String name, final LogContextConfiguration configuration) { + super(name, configuration, configuration.getLoggerConfigurations()); + } + + public String getFilter() { + return getFilterValueExpression().getResolvedValue(); + } + + @Override + public ValueExpression getFilterValueExpression() { + return filter == null ? ValueExpression.NULL_STRING_EXPRESSION : filter; + } + + public void setFilter(final String filter) { + setFilter(ValueExpression.STRING_RESOLVER.resolve(filter)); + } + + @Override + public void setFilter(final String expression, final String value) { + setFilter(new ValueExpressionImpl(expression, value)); + } + + private void setFilter(final ValueExpression valueExpression) { + final ValueExpression oldFilterName = this.filter; + this.filter = valueExpression; + final String filterName = valueExpression.getResolvedValue(); + final LogContextConfiguration configuration = getConfiguration(); + configuration.addAction(new ConfigAction() { + public ObjectProducer validate() throws IllegalArgumentException { + return configuration.resolveFilter(filterName); + } + + public void applyPreCreate(final ObjectProducer param) { + } + + public void applyPostCreate(final ObjectProducer param) { + configuration.getLogger(getName()).setFilter((Filter) param.getObject()); + } + + public void rollback() { + filter = oldFilterName; + } + }); + } + + @Override + public Boolean getUseParentFilters() { + return getUseParentFiltersValueExpression().getResolvedValue(); + } + + @Override + public ValueExpression getUseParentFiltersValueExpression() { + return useParentFilters; + } + + @Override + public void setUseParentFilters(final Boolean value) { + setUseParentFilters(new ValueExpressionImpl<>(null, value)); + } + + @Override + public void setUseParentFilters(final String expression) { + setUseParentFilters(ValueExpression.BOOLEAN_RESOLVER.resolve(expression)); + } + + @Override + public void setUseParentFilters(final String expression, final Boolean value) { + setUseParentFilters(new ValueExpressionImpl<>(expression, value)); + } + + private void setUseParentFilters(final ValueExpression valueExpression) { + final ValueExpression oldUseParentFilters = this.useParentFilters; + this.useParentFilters = valueExpression; + final Boolean useParentFilters = valueExpression.getResolvedValue(); + final LogContextConfiguration configuration = getConfiguration(); + configuration.addAction(new ConfigAction() { + public Void validate() throws IllegalArgumentException { + return null; + } + + public void applyPreCreate(final Void param) { + } + + public void applyPostCreate(final Void param) { + if (useParentFilters != null) + configuration.getLogger(getName()).setUseParentFilters(useParentFilters); + } + + public void rollback() { + LoggerConfigurationImpl.this.useParentFilters = oldUseParentFilters; + } + }); + } + + + public Boolean getUseParentHandlers() { + return getUseParentHandlersValueExpression().getResolvedValue(); + } + + @Override + public ValueExpression getUseParentHandlersValueExpression() { + return useParentHandlers == null ? ValueExpression.NULL_BOOLEAN_EXPRESSION : useParentHandlers; + } + + public void setUseParentHandlers(final Boolean useParentHandlers) { + setUseParentHandlers(new ValueExpressionImpl(null, useParentHandlers)); + } + + @Override + public void setUseParentHandlers(final String expression) { + setUseParentHandlers(ValueExpression.BOOLEAN_RESOLVER.resolve(expression)); + } + + @Override + public void setUseParentHandlers(final String expression, final Boolean value) { + setUseParentHandlers(new ValueExpressionImpl(expression, value)); + } + + private void setUseParentHandlers(final ValueExpression valueExpression) { + final ValueExpression oldUseParentHandlers = this.useParentHandlers; + this.useParentHandlers = valueExpression; + final Boolean useParentHandlers = valueExpression.getResolvedValue(); + final LogContextConfiguration configuration = getConfiguration(); + configuration.addAction(new ConfigAction() { + public Void validate() throws IllegalArgumentException { + return null; + } + + public void applyPreCreate(final Void param) { + } + + public void applyPostCreate(final Void param) { + if (useParentHandlers != null) + configuration.getLogger(getName()).setUseParentHandlers(useParentHandlers); + } + + public void rollback() { + LoggerConfigurationImpl.this.useParentHandlers = oldUseParentHandlers; + } + }); + } + + public String getLevel() { + return getLevelValueExpression().getResolvedValue(); + } + + @Override + public ValueExpression getLevelValueExpression() { + return level == null ? ValueExpression.NULL_STRING_EXPRESSION : level; + } + + public void setLevel(final String level) { + setLevelValueExpression(ValueExpression.STRING_RESOLVER.resolve(level)); + } + + @Override + public void setLevel(final String expression, final String level) { + setLevelValueExpression(new ValueExpressionImpl(expression, level)); + } + + private void setLevelValueExpression(final ValueExpression expression) { + final ValueExpression oldLevel = this.level; + this.level = expression; + final String resolvedLevel = expression.getResolvedValue(); + final LogContextConfiguration configuration = getConfiguration(); + configuration.addAction(new ConfigAction() { + public Level validate() throws IllegalArgumentException { + return resolvedLevel == null ? null : configuration.getLogContext().getLevelForName(resolvedLevel); + } + + public void applyPreCreate(final Level param) { + } + + public void applyPostCreate(final Level param) { + configuration.getLogger(getName()).setLevel(param); + } + + public void rollback() { + LoggerConfigurationImpl.this.level = oldLevel; + } + }); + } + + public List getHandlerNames() { + return new ArrayList(handlerNames); + } + + public void setHandlerNames(final String... names) { + final String[] oldHandlerNames = handlerNames.toArray(new String[0]); + handlerNames.clear(); + final LinkedHashSet strings = new LinkedHashSet(asList(names)); + handlerNames.addAll(strings); + final String[] stringsArray = strings.toArray(new String[0]); + final LogContextConfiguration configuration = getConfiguration(); + configuration.addAction(new ConfigAction() { + public Void validate() throws IllegalArgumentException { + for (String name : stringsArray) { + if (configuration.getHandlerConfiguration(name) == null) { + throw new IllegalArgumentException(String.format("Handler \"%s\" is not found", name)); + } + } + return null; + } + + public void applyPreCreate(final Void param) { + } + + public void applyPostCreate(final Void param) { + final Logger logger = configuration.getLogger(getName()); + final int length = stringsArray.length; + final Handler[] handlers = new Handler[length]; + for (int i = 0; i < length; i++) { + handlers[i] = getConfiguration().getHandler(stringsArray[i]); + } + logger.setHandlers(handlers); + } + + public void rollback() { + handlerNames.clear(); + handlerNames.addAll(asList(oldHandlerNames)); + } + }); + } + + public void setHandlerNames(final Collection names) { + setHandlerNames(names.toArray(new String[0])); + } + + public boolean addHandlerName(final String name) { + final LogContextConfiguration configuration = getConfiguration(); + if (handlerNames.contains(name)) { + return false; + } + handlerNames.add(name); + configuration.addAction(new ConfigAction() { + public Void validate() throws IllegalArgumentException { + if (configuration.getHandlerConfiguration(name) == null) { + throw new IllegalArgumentException(String.format("Handler \"%s\" is not found", name)); + } + return null; + } + + public void applyPreCreate(final Void param) { + } + + public void applyPostCreate(final Void param) { + final Logger logger = getConfiguration().getLogger(getName()); + logger.addHandler(configuration.getHandler(name)); + } + + public void rollback() { + handlerNames.remove(name); + } + }); + return true; + } + + public boolean removeHandlerName(final String name) { + final LogContextConfiguration configuration = getConfiguration(); + if (!handlerNames.contains(name)) { + return false; + } + final int index = handlerNames.indexOf(name); + handlerNames.remove(index); + configuration.addAction(new ConfigAction() { + public Handler validate() throws IllegalArgumentException { + return configuration.getHandler(name); + } + + public void applyPreCreate(final Handler param) { + } + + public void applyPostCreate(final Handler param) { + final Logger logger = configuration.getLogger(getName()); + logger.removeHandler(param); + } + + public void rollback() { + handlerNames.add(index, name); + } + }); + return true; + } + + @Override + ConfigurationResource removeInstance() { + // Nothing is configured, so there is nothing to do + return null; + } + + @Override + ConfigAction getRemoveAction() { + final String name = getName(); + final Logger refLogger = getConfiguration().getLogger(name); + final Filter filter; + final Handler[] handlers; + final Level level; + final boolean useParentHandlers; + if (refLogger == null) { + filter = null; + handlers = null; + level = null; + useParentHandlers = true; + } else { + filter = refLogger.getFilter(); + handlers = refLogger.getHandlers(); + level = refLogger.getLevel(); + useParentHandlers = refLogger.getUseParentHandlers(); + } + return new ConfigAction<>() { + public Void validate() throws IllegalArgumentException { + return null; + } + + public void applyPreCreate(final Void param) { + } + + public void applyPostCreate(final Void param) { + if (refLogger != null) { + refLogger.setFilter(null); + refLogger.clearHandlers(); + refLogger.setLevel(null); + refLogger.setUseParentHandlers(true); + } + } + + public void rollback() { + if (refLogger != null) { + refLogger.setFilter(filter); + if (handlers != null) refLogger.setHandlers(handlers); + refLogger.setLevel(level); + refLogger.setUseParentHandlers(useParentHandlers); + configs.put(name, LoggerConfigurationImpl.this); + } + clearRemoved(); + } + }; + } +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/NamedConfigurable.java b/logmanager/src/main/java/org/jboss/logmanager/config/NamedConfigurable.java new file mode 100644 index 00000000000..2de030c0d39 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/NamedConfigurable.java @@ -0,0 +1,35 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +/** + * A configurable object with a name. + * + * @author David M. Lloyd + */ +public interface NamedConfigurable { + + /** + * Get the name of this configurable object. + * + * @return the name + */ + String getName(); +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/ObjectConfigurable.java b/logmanager/src/main/java/org/jboss/logmanager/config/ObjectConfigurable.java new file mode 100644 index 00000000000..bdf58a81107 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/ObjectConfigurable.java @@ -0,0 +1,53 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +/** + * A configurable object with a specific class name. + * + * @author David M. Lloyd + */ +public interface ObjectConfigurable { + + /** + * Get the module name for this object's configuration, if any. If JBoss Modules is not present + * on the class path, only {@code null} values are accepted. + * + * @return the module name, or {@code null} if none is configured + */ + String getModuleName(); + + /** + * Get the class name for this object's configuration. + * + * @return the class name + */ + String getClassName(); + + /** + * Returns the instance associated with this configuration or {@code null} if no instance has yet been created. + *

+ * Any changes to the instance will not be recognized by the configuration API. + *

+ * + * @return the instance associated with this configuration or {@code null} + */ + T getInstance(); +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/ObjectProducer.java b/logmanager/src/main/java/org/jboss/logmanager/config/ObjectProducer.java new file mode 100644 index 00000000000..e049e8b657b --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/ObjectProducer.java @@ -0,0 +1,61 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import java.util.Map; + +/** + * @author David M. Lloyd + */ +// TODO (jrp) we can probably replace this with the ConfigurationResource +interface ObjectProducer { + + SimpleObjectProducer NULL_PRODUCER = new SimpleObjectProducer(null); + + Object getObject(); +} + +class SimpleObjectProducer implements ObjectProducer { + + final Object value; + + SimpleObjectProducer(final Object value) { + this.value = value; + } + + public Object getObject() { + return value; + } +} + +class RefProducer implements ObjectProducer { + + private final String name; + private final Map refs; + + RefProducer(final String name, final Map refs) { + this.name = name; + this.refs = refs; + } + + public Object getObject() { + return refs.get(name); + } +} \ No newline at end of file diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/PojoConfiguration.java b/logmanager/src/main/java/org/jboss/logmanager/config/PojoConfiguration.java new file mode 100644 index 00000000000..d5ffdb5b401 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/PojoConfiguration.java @@ -0,0 +1,26 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +/** + * @author James R. Perkins + */ +public interface PojoConfiguration extends NamedConfigurable, PropertyConfigurable, ObjectConfigurable { +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/PojoConfigurationImpl.java b/logmanager/src/main/java/org/jboss/logmanager/config/PojoConfigurationImpl.java new file mode 100644 index 00000000000..2f9dd5de8c0 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/PojoConfigurationImpl.java @@ -0,0 +1,51 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import org.jboss.logmanager.configuration.ConfigurationResource; + +/** + * @author James R. Perkins + */ +final class PojoConfigurationImpl extends AbstractPropertyConfiguration implements PojoConfiguration { + + PojoConfigurationImpl(final LogContextConfiguration configuration, final String name, final String moduleName, final String className, final String[] constructorProperties) { + super(Object.class, configuration, configuration.getPojoConfigurations(), name, moduleName, className, constructorProperties); + } + + String getDescription() { + return "pojo"; + } + + @Override + void addConfigurationResource(final ConfigurationResource resource) { + getConfiguration().addObject(getName(), resource); + } + + @Override + ConfigurationResource removeInstance() { + return getConfiguration().removeObject(getName()); + } + + @Override + public Object getInstance() { + return getConfiguration().getObject(getName()); + } +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/PropertyConfigurable.java b/logmanager/src/main/java/org/jboss/logmanager/config/PropertyConfigurable.java new file mode 100644 index 00000000000..91ce034e590 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/PropertyConfigurable.java @@ -0,0 +1,159 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import java.util.List; + +/** + * An object which is configurable via object properties. + * + * @author David M. Lloyd + */ +public interface PropertyConfigurable { + + /** + * Set a property value from a string. + * + * @param propertyName the property name + * @param value the property value + * + * @throws IllegalArgumentException if the given value is not acceptable for this property + */ + void setPropertyValueString(String propertyName, String value) throws IllegalArgumentException; + + /** + * Get the string property value with the given name. + * + * @param propertyName the property name + * + * @return the property value string + */ + String getPropertyValueString(String propertyName); + + /** + * Get the property value. + * + * @param propertyName the property name + * + * @return the property value + */ + ValueExpression getPropertyValueExpression(String propertyName); + + /** + * Sets the expression value for the property. + * + * @param propertyName the name of the property + * @param expression the expression used to resolve the value + */ + void setPropertyValueExpression(String propertyName, String expression); + + /** + * Sets the expression value for the property. + *

+ * This method will not parse the expression for the value and instead use the {@code value} parameter for the + * value. + * + * @param propertyName the name of the property + * @param expression the expression used to resolve the value + * @param value the value to use + */ + void setPropertyValueExpression(String propertyName, String expression, String value); + + /** + * Determine whether the given property name is configured. + * + * @param propertyName the property name to test + * + * @return {@code true} if the name is configured, {@code false} otherwise + */ + boolean hasProperty(String propertyName); + + /** + * Remove a configured property. Does not affect the underlying configured value; just removes it from the + * configuration. + * + * @param propertyName the property name + * + * @return {@code true} if the property name was removed, {@code false} if it was not present + */ + boolean removeProperty(String propertyName); + + /** + * Get the names of the configured properties in order. + * + * @return the property names + */ + List getPropertyNames(); + + /** + * Determine whether the given property name is a constructor property. + * + * @param propertyName the name of the property to check. + * + * @return {@code true} if the property should be used as a construction property, otherwise {@code false}. + */ + boolean hasConstructorProperty(String propertyName); + + /** + * Returns a collection of the constructor properties. + * + * @return a collection of the constructor properties. + */ + List getConstructorProperties(); + + /** + * Adds a method name to be invoked after all properties have been set. + * + * @param methodName the name of the method + * + * @return {@code true} if the method was successfully added, otherwise {@code false} + */ + boolean addPostConfigurationMethod(String methodName); + + /** + * Returns a collection of the methods to be invoked after the properties have been set. + * + * @return a collection of method names or an empty list + */ + List getPostConfigurationMethods(); + + /** + * Sets the method names to be invoked after the properties have been set. + * + * @param methodNames the method names to invoke + */ + void setPostConfigurationMethods(String... methodNames); + + /** + * Sets the method names to be invoked after the properties have been set. + * + * @param methodNames the method names to invoke + */ + void setPostConfigurationMethods(List methodNames); + + /** + * Removes the post configuration method. + * + * @param methodName the method to remove + * + * @return {@code true} if the method was removed, otherwise {@code false} + */ + boolean removePostConfigurationMethod(String methodName); +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/PropertyLogContextConfiguration.java b/logmanager/src/main/java/org/jboss/logmanager/config/PropertyLogContextConfiguration.java new file mode 100644 index 00000000000..ca04274a90b --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/PropertyLogContextConfiguration.java @@ -0,0 +1,380 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2022 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.Properties; + +import org.jboss.logmanager.LogContext; +import org.jboss.logmanager.StandardOutputStreams; +import org.jboss.logmanager.configuration.PropertyLogContextConfigurator; + +/** + * A utility to parse a {@code logging.properties} file and configure a {@link LogContext}. + * + * @author James R. Perkins + */ +// TODO (jrp) rename this and make it private access as only the LogContextConfiguration should use it. +@SuppressWarnings("WeakerAccess") +public class PropertyLogContextConfiguration { + + private static final String[] EMPTY_STRINGS = new String[0]; + private final LogContextConfiguration configuration; + private final Properties properties; + + private PropertyLogContextConfiguration(final LogContextConfiguration configuration, final Properties properties) { + this.configuration = configuration; + this.properties = properties; + } + + /** + * Configures the {@link LogContext} based on the properties. + * + * @param configuration the configuration to configure the properties on + * + * @return the context configuration for the properties + */ + public static LogContextConfiguration configure(final LogContextConfiguration configuration, final InputStream inputStream) { + final InputStream configIn = inputStream != null ? inputStream : findConfiguration(); + if (configIn != null) { + final Properties properties = new Properties(); + try (Reader reader = new InputStreamReader(configIn, StandardCharsets.UTF_8)) { + properties.load(reader); + return configure(configuration, properties); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + return configuration; + } + + /** + * Configures the {@link LogContext} based on the properties. + * + * @param configuration the configuration to configure the properties on + * @param properties the properties used to configure the log context + * + * @return the context configuration for the properties + */ + public static LogContextConfiguration configure(final LogContextConfiguration configuration, final Properties properties) { + final PropertyLogContextConfiguration config = new PropertyLogContextConfiguration( + Objects.requireNonNull(configuration), + Objects.requireNonNull(properties)); + config.doConfigure(); + return configuration; + } + + private void doConfigure() { + try { + // POJO's must be configured first so other + for (String pojoName : getStringCsvArray("pojos")) { + configurePojos(pojoName); + } + // Start with the list of loggers to configure. The root logger is always on the list. + configureLogger(""); + // And, for each logger name, configure any filters, handlers, etc. + for (String loggerName : getStringCsvArray("loggers")) { + configureLogger(loggerName); + } + // Configure any declared handlers. + for (String handlerName : getStringCsvArray("handlers")) { + configureHandler(handlerName); + } + // Configure any declared filters. + for (String filterName : getStringCsvArray("filters")) { + configureFilter(filterName); + } + // Configure any declared formatters. + for (String formatterName : getStringCsvArray("formatters")) { + configureFormatter(formatterName); + } + // Configure any declared error managers. + for (String errorManagerName : getStringCsvArray("errorManagers")) { + configureErrorManager(errorManagerName); + } + configuration.commit(); + } finally { + configuration.forget(); + } + } + + @SuppressWarnings({"ConstantConditions"}) + private void configureLogger(final String loggerName) { + if (configuration.getLoggerConfiguration(loggerName) != null) { + // already configured! + return; + } + + final LoggerConfiguration loggerConfig = configuration.addLoggerConfiguration(loggerName); + // Get logger level + final String levelName = getStringProperty(getKey("logger", loggerName, "level")); + if (levelName != null) { + loggerConfig.setLevel(levelName); + } + + // Get logger filters + final String filterName = getStringProperty(getKey("logger", loggerName, "filter")); + if (filterName != null) { + // TODO (jrp) this is not really the best way to handle filters - + // the trouble is the filter could be an expression, match("value"), or a defined filter + loggerConfig.setFilter(filterName); + final String resolvedFilter = loggerConfig.getFilterValueExpression().getResolvedValue(); + // Check for a filter class + final String filterClassName = getStringProperty(getKey("filter", resolvedFilter)); + // If the filter class is null, assume it's a filter expression + if (filterClassName != null) { + configureFilter(resolvedFilter); + } + } + + // Get logger handlers + final String[] handlerNames = getStringCsvArray(getKey("logger", loggerName, "handlers")); + for (String name : handlerNames) { + if (configureHandler(name)) { + loggerConfig.addHandlerName(name); + } + } + + // Get logger properties + final String useParentHandlersString = getStringProperty(getKey("logger", loggerName, "useParentHandlers")); + if (useParentHandlersString != null) { + loggerConfig.setUseParentHandlers(useParentHandlersString); + } + final String useParentFiltersString = getStringProperty(getKey("logger", loggerName, "useParentFilters")); + if (useParentFiltersString != null) { + loggerConfig.setUseParentFilters(useParentFiltersString); + } + } + + private boolean configureHandler(final String handlerName) { + if (configuration.getHandlerConfiguration(handlerName) != null) { + // already configured! + return true; + } + final String className = getStringProperty(getKey("handler", handlerName), true); + if (className == null) { + StandardOutputStreams.printError("Handler %s is not defined%n", handlerName); + return false; + } + final HandlerConfiguration handlerConfig = configuration.addHandlerConfiguration( + getStringProperty(getKey("handler", handlerName, "module")), + className, + handlerName, + getStringCsvArray(getKey("handler", handlerName, "constructorProperties")) + ); + + // Configure the properties + final String encoding = getStringProperty(getKey("handler", handlerName, "encoding")); + if (encoding != null) { + handlerConfig.setEncoding(encoding); + } + + final String filter = getStringProperty(getKey("handler", handlerName, "filter")); + if (filter != null) { + // TODO (jrp) this is not really the best way to handle filters - + // the trouble is the filter could be an expression, match("value"), or a defined filter + handlerConfig.setFilter(filter); + final String resolvedFilter = handlerConfig.getFilterValueExpression().getResolvedValue(); + // Check for a filter class + final String filterClassName = getStringProperty(getKey("filter", resolvedFilter)); + // If the filter class is null, assume it's a filter expression + if (filterClassName != null) { + configureFilter(resolvedFilter); + } + } + final String levelName = getStringProperty(getKey("handler", handlerName, "level")); + if (levelName != null) { + handlerConfig.setLevel(levelName); + } + final String formatterName = getStringProperty(getKey("handler", handlerName, "formatter")); + if (formatterName != null) { + if (configureFormatter(formatterName)) { + handlerConfig.setFormatterName(formatterName); + } + } + final String errorManagerName = getStringProperty(getKey("handler", handlerName, "errorManager")); + if (errorManagerName != null) { + if (configureErrorManager(errorManagerName)) { + handlerConfig.setErrorManagerName(errorManagerName); + } + } + + final String[] handlerNames = getStringCsvArray(getKey("handler", handlerName, "handlers")); + for (String name : handlerNames) { + if (configureHandler(name)) { + handlerConfig.addHandlerName(name); + } + } + final String[] postConfigurationMethods = getStringCsvArray(getKey("handler", handlerName, "postConfiguration")); + handlerConfig.setPostConfigurationMethods(postConfigurationMethods); + configureProperties(handlerConfig, "handler", handlerName); + return true; + } + + private boolean configureFormatter(final String formatterName) { + if (configuration.getFormatterConfiguration(formatterName) != null) { + // already configured! + return true; + } + final String className = getStringProperty(getKey("formatter", formatterName), true); + if (className == null) { + StandardOutputStreams.printError("Formatter %s is not defined%n", formatterName); + return false; + } + final FormatterConfiguration formatterConfig = configuration.addFormatterConfiguration( + getStringProperty(getKey("formatter", formatterName, "module")), + className, + formatterName, + getStringCsvArray(getKey("formatter", formatterName, "constructorProperties"))); + final String[] postConfigurationMethods = getStringCsvArray(getKey("formatter", formatterName, "postConfiguration")); + formatterConfig.setPostConfigurationMethods(postConfigurationMethods); + configureProperties(formatterConfig, "formatter", formatterName); + return true; + } + + private boolean configureErrorManager(final String errorManagerName) { + if (configuration.getErrorManagerConfiguration(errorManagerName) != null) { + // already configured! + return true; + } + final String className = getStringProperty(getKey("errorManager", errorManagerName), true); + if (className == null) { + StandardOutputStreams.printError("Error manager %s is not defined%n", errorManagerName); + return false; + } + final ErrorManagerConfiguration errorManagerConfig = configuration.addErrorManagerConfiguration( + getStringProperty(getKey("errorManager", errorManagerName, "module")), + className, + errorManagerName, + getStringCsvArray(getKey("errorManager", errorManagerName, "constructorProperties"))); + final String[] postConfigurationMethods = getStringCsvArray(getKey("errorManager", errorManagerName, "postConfiguration")); + errorManagerConfig.setPostConfigurationMethods(postConfigurationMethods); + configureProperties(errorManagerConfig, "errorManager", errorManagerName); + return true; + } + + private boolean configureFilter(final String filterName) { + if (configuration.getFilterConfiguration(filterName) != null) { + return true; + } + final String className = getStringProperty(getKey("filter", filterName)); + if (className == null) { + StandardOutputStreams.printError("Filter %s is not defined%n", filterName); + return false; + } + final FilterConfiguration filterConfig = configuration.addFilterConfiguration( + getStringProperty(getKey("filter", filterName, "module")), + className, + filterName, + getStringCsvArray(getKey("filter", filterName, "constructorProperties"))); + final String[] postConfigurationMethods = getStringCsvArray(getKey("filter", filterName, "postConfiguration")); + filterConfig.setPostConfigurationMethods(postConfigurationMethods); + configureProperties(filterConfig, "filter", filterName); + return true; + } + + private void configurePojos(final String pojoName) { + if (configuration.getPojoConfiguration(pojoName) != null) { + // already configured! + return; + } + final String className = getStringProperty(getKey("pojo", pojoName), true); + if (className == null) { + StandardOutputStreams.printError("POJO %s is not defined%n", pojoName); + return; + } + final PojoConfiguration pojoConfig = configuration.addPojoConfiguration( + getStringProperty(getKey("pojo", pojoName, "module")), + getStringProperty(getKey("pojo", pojoName)), + pojoName, + getStringCsvArray(getKey("pojo", pojoName, "constructorProperties"))); + final String[] postConfigurationMethods = getStringCsvArray(getKey("pojo", pojoName, "postConfiguration")); + pojoConfig.setPostConfigurationMethods(postConfigurationMethods); + configureProperties(pojoConfig, "pojo", pojoName); + } + + private String getStringProperty(final String key) { + return getStringProperty(key, true); + } + + private String getStringProperty(final String key, final boolean trim) { + String value = properties.getProperty(key); + if (value != null && trim) { + value = value.trim(); + } + return value; + } + + private String[] getStringCsvArray(final String key) { + final String property = properties.getProperty(key, ""); + if (property == null) { + return EMPTY_STRINGS; + } + final String value = property.trim(); + if (value.isEmpty()) { + return EMPTY_STRINGS; + } + return value.split("\\s*,\\s*"); + } + + private void configureProperties(final PropertyConfigurable config, final String prefix, final String name) { + // Next configure setter properties + final String[] propertyNames = getStringCsvArray(getKey(prefix, name, "properties")); + for (String propertyName : propertyNames) { + final String valueString = getStringProperty(getKey(prefix, name, propertyName), false); + if (valueString != null) + config.setPropertyValueExpression(propertyName, valueString); + } + } + + private static String getKey(final String prefix, final String objectName) { + return !objectName.isEmpty() ? prefix + "." + objectName : prefix; + } + + private static String getKey(final String prefix, final String objectName, final String key) { + return !objectName.isEmpty() ? prefix + "." + objectName + "." + key : prefix + "." + key; + } + + private static InputStream findConfiguration() { + final String propLoc = System.getProperty("logging.configuration"); + if (propLoc != null) + try { + return new URL(propLoc).openStream(); + } catch (IOException e) { + StandardOutputStreams.printError("Unable to read the logging configuration from '%s' (%s)%n", propLoc, e); + } + final ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + if (tccl != null) + try { + final InputStream stream = tccl.getResourceAsStream("logging.properties"); + if (stream != null) + return stream; + } catch (Exception ignore) { + } + return PropertyLogContextConfigurator.class.getResourceAsStream("logging.properties"); + } +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/ValueExpression.java b/logmanager/src/main/java/org/jboss/logmanager/config/ValueExpression.java new file mode 100644 index 00000000000..a90f88e382c --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/ValueExpression.java @@ -0,0 +1,258 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import java.io.File; + +/** + * Represents a possible value expression. A value is said to be an expression if it matches a + * ${system.property:DEFAULT_VALUE} pattern. + * + * @author James R. Perkins + */ +// TODO (jrp) do we need this or even want to use it? +public interface ValueExpression { + + final ValueExpression NULL_STRING_EXPRESSION = new ValueExpression() { + @Override + public String getResolvedValue() { + return null; + } + + @Override + public boolean isExpression() { + return false; + } + + @Override + public String getValue() { + return null; + } + }; + + final ValueExpression NULL_BOOLEAN_EXPRESSION = new ValueExpression() { + @Override + public Boolean getResolvedValue() { + return null; + } + + @Override + public boolean isExpression() { + return false; + } + + @Override + public String getValue() { + return null; + } + }; + + /** + * The resolved value. + *

+ * If this is an {@link #isExpression() expression} the resolved value will be the value from a system property or + * the default value from the expression if the system property is not set. + *

+ * If this is not an {@link #isExpression() expression} the value returned will be the non-expression value + * or {@code null} if allowed for the property. + * + * @return the resolved value + */ + T getResolvedValue(); + + /** + * Checks whether this is an expression or not. + * + * @return {@code true} if this is an expression, otherwise {@code false} + */ + boolean isExpression(); + + /** + * Gets the value of the value which may or may not be an {@link #isExpression() expression}. + * + * @return the value + */ + String getValue(); + + @Override + String toString(); + + /** + * Resolves the value expression from an expression. + * + * @param the value type + */ + public interface Resolver { + + /** + * Resolves the value of an expression. + * + * @param expression the expression to parse + * + * @return the value expression + */ + ValueExpression resolve(String expression); + } + + public static Resolver STRING_RESOLVER = new Resolver() { + + private static final int INITIAL = 0; + private static final int GOT_DOLLAR = 1; + private static final int GOT_OPEN_BRACE = 2; + private static final int RESOLVED = 3; + private static final int DEFAULT = 4; + + @Override + public ValueExpression resolve(final String expression) { + if (expression == null) return NULL_STRING_EXPRESSION; + boolean isExpression = false; + final StringBuilder builder = new StringBuilder(); + final char[] chars = expression.toCharArray(); + final int len = chars.length; + int state = 0; + int start = -1; + int nameStart = -1; + for (int i = 0; i < len; i++) { + char ch = chars[i]; + switch (state) { + case INITIAL: { + switch (ch) { + case '$': { + state = GOT_DOLLAR; + continue; + } + default: { + builder.append(ch); + continue; + } + } + // not reachable + } + case GOT_DOLLAR: { + switch (ch) { + case '$': { + builder.append(ch); + state = INITIAL; + continue; + } + case '{': { + start = i + 1; + nameStart = start; + state = GOT_OPEN_BRACE; + continue; + } + default: { + // invalid; emit and resume + builder.append('$').append(ch); + state = INITIAL; + continue; + } + } + // not reachable + } + case GOT_OPEN_BRACE: { + switch (ch) { + case ':': + case '}': + case ',': { + final String name = expression.substring(nameStart, i).trim(); + if ("/".equals(name)) { + builder.append(File.separator); + state = ch == '}' ? INITIAL : RESOLVED; + continue; + } else if (":".equals(name)) { + builder.append(File.pathSeparator); + state = ch == '}' ? INITIAL : RESOLVED; + continue; + } + isExpression = true; + String val = System.getProperty(name); + if (val == null && name.startsWith("env.")) { + val = System.getenv(name); + } + if (val != null) { + builder.append(val); + state = ch == '}' ? INITIAL : RESOLVED; + continue; + } else if (ch == ',') { + nameStart = i + 1; + continue; + } else if (ch == ':') { + start = i + 1; + state = DEFAULT; + continue; + } else { + builder.append(expression.substring(start - 2, i + 1)); + state = INITIAL; + continue; + } + } + default: { + continue; + } + } + // not reachable + } + case RESOLVED: { + if (ch == '}') { + state = INITIAL; + } + continue; + } + case DEFAULT: { + if (ch == '}') { + state = INITIAL; + builder.append(expression.substring(start, i)); + } + continue; + } + default: + throw new IllegalStateException(); + } + } + switch (state) { + case GOT_DOLLAR: { + builder.append('$'); + break; + } + case DEFAULT: + case GOT_OPEN_BRACE: { + builder.append(expression.substring(start - 2)); + break; + } + } + final String resolvedValue = builder.toString(); + if (isExpression) { + return new ValueExpressionImpl<>(expression, resolvedValue); + } + return new ValueExpressionImpl<>(null, resolvedValue); + } + }; + + public static Resolver BOOLEAN_RESOLVER = new Resolver() { + @Override + public ValueExpression resolve(final String expression) { + if (expression == null) return NULL_BOOLEAN_EXPRESSION; + final ValueExpression stringResult = STRING_RESOLVER.resolve(expression); + final Boolean value = stringResult.getResolvedValue() == null ? null : Boolean.valueOf(stringResult.getResolvedValue()); + return new ValueExpressionImpl(expression, value); + } + }; +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/ValueExpressionImpl.java b/logmanager/src/main/java/org/jboss/logmanager/config/ValueExpressionImpl.java new file mode 100644 index 00000000000..ec79f67a536 --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/ValueExpressionImpl.java @@ -0,0 +1,54 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +/** + * @author James R. Perkins + */ +class ValueExpressionImpl implements ValueExpression { + + private final String expression; + private final T resolvedValue; + + ValueExpressionImpl(final String expression, final T resolvedValue) { + this.expression = expression; + this.resolvedValue = resolvedValue; + } + + @Override + public T getResolvedValue() { + return resolvedValue; + } + + @Override + public boolean isExpression() { + return expression != null; + } + + @Override + public String getValue() { + return expression == null ? (resolvedValue == null ? null : String.valueOf(resolvedValue)) : expression; + } + + @Override + public String toString() { + return getValue(); + } +} diff --git a/logmanager/src/main/java/org/jboss/logmanager/config/WrappedAction.java b/logmanager/src/main/java/org/jboss/logmanager/config/WrappedAction.java new file mode 100644 index 00000000000..0870de629cb --- /dev/null +++ b/logmanager/src/main/java/org/jboss/logmanager/config/WrappedAction.java @@ -0,0 +1,90 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2021 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.jboss.logmanager.config; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.function.Supplier; + +/** + * @author James R. Perkins + */ +class WrappedAction { + + /** + * Executes the action with the {@linkplain Thread#getContextClassLoader() TCCL} set to the class loader of the type. + * + * @param action the action to run + * @param cl the class loader to use for the TCCL + * @param the return type + * + * @return the value from the action + */ + static R execute(final Supplier action, final ClassLoader cl) { + if (cl == null) { + return action.get(); + } + final ClassLoader current = getTccl(); + try { + setTccl(cl); + return action.get(); + } finally { + setTccl(current); + } + } + + /** + * Executes the action with the {@linkplain Thread#getContextClassLoader() TCCL} set to the class loader of the type. + * + * @param action the action to run + * @param cl the class loader to use for the TCCL + */ + static void execute(final Runnable action, final ClassLoader cl) { + if (cl == null) { + action.run(); + } else { + final ClassLoader current = getTccl(); + try { + setTccl(cl); + action.run(); + } finally { + setTccl(current); + } + } + } + + private static ClassLoader getTccl() { + if (System.getSecurityManager() == null) { + return Thread.currentThread().getContextClassLoader(); + } + return AccessController.doPrivileged((PrivilegedAction) () -> Thread.currentThread().getContextClassLoader()); + } + + private static void setTccl(final ClassLoader cl) { + if (System.getSecurityManager() == null) { + Thread.currentThread().setContextClassLoader(cl); + } else { + AccessController.doPrivileged((PrivilegedAction) () -> { + Thread.currentThread().setContextClassLoader(cl); + return null; + }); + } + } +} diff --git a/logmanager/src/main/java/org/wildfly/core/logmanager/EmbeddedLogContextSelector.java b/logmanager/src/main/java/org/wildfly/core/logmanager/EmbeddedLogContextSelector.java new file mode 100644 index 00000000000..c4977078417 --- /dev/null +++ b/logmanager/src/main/java/org/wildfly/core/logmanager/EmbeddedLogContextSelector.java @@ -0,0 +1,55 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.wildfly.core.logmanager; + +import org.jboss.logmanager.LogContext; +import org.jboss.logmanager.configuration.ContextConfiguration; + +/** + * @author James R. Perkins + */ +class EmbeddedLogContextSelector extends WildFlyLogContextSelectorImpl { + private static final LogContext CONTEXT = LogContext.create(true, new WildFlyLogContextInitializer()); + + EmbeddedLogContextSelector() { + super(CONTEXT); + clearLogContext(); + } + + // TODO (jrp) this should probably just override the close and replace the old ContextConfiguration if there was one + private static void clearLogContext() { + // Remove the configurator and clear the log context + final ContextConfiguration configuration = CONTEXT.detach(ContextConfiguration.CONTEXT_CONFIGURATION_KEY); + if (configuration != null) { + try { + configuration.close(); + } catch (Exception e) { + // TODO (jrp) don't throw this, log something which feels counterintuitive. + throw new RuntimeException(e); + } + } + try { + CONTEXT.close(); + // TODO (jrp) don't throw this, log something which feels counterintuitive. + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/logmanager/src/main/java/org/wildfly/core/logmanager/ObjectBuilder.java b/logmanager/src/main/java/org/wildfly/core/logmanager/ObjectBuilder.java new file mode 100644 index 00000000000..fe430f5dec5 --- /dev/null +++ b/logmanager/src/main/java/org/wildfly/core/logmanager/ObjectBuilder.java @@ -0,0 +1,353 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.wildfly.core.logmanager; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.regex.Pattern; + +import org.jboss.logmanager.configuration.ConfigurationResource; +import org.jboss.logmanager.configuration.ContextConfiguration; +import org.jboss.modules.Module; +import org.jboss.modules.ModuleLoader; + +/** + * Helper to lazily build an object. + * + * @author James R. Perkins + */ +@SuppressWarnings({"UnusedReturnValue"}) +public class ObjectBuilder { + private final Class baseClass; + private final String className; + private final Map constructorProperties; + private final Map properties; + private final Set definedProperties; + private final Set postConstructMethods; + private final Function definedValueResolver; + private final ValueResolver valueResolver; + private String moduleName; + + private ObjectBuilder(final ContextConfiguration contextConfiguration, final Class baseClass, final String className) { + this.baseClass = baseClass; + this.className = className; + constructorProperties = new LinkedHashMap<>(); + properties = new LinkedHashMap<>(); + definedProperties = new LinkedHashSet<>(); + postConstructMethods = new LinkedHashSet<>(); + definedValueResolver = (name) -> { + final PropertyValue propertyValue = findDefinedProperty(name); + return propertyValue == null ? null : propertyValue.value.get(); + }; + valueResolver = ValueResolver.of(contextConfiguration, baseClass); + } + + /** + * Create a new {@link ObjectBuilder}. + * + * @param baseClass the base type + * @param className the name of the class to create + * @param the type being created + * + * @return a new {@link ObjectBuilder} + */ + public static ObjectBuilder of(final ContextConfiguration contextConfiguration, final Class baseClass, final String className) { + return new ObjectBuilder<>(contextConfiguration, baseClass, className); + } + + /** + * Adds a property used for constructing the object. + *

+ * The {@code name} must be the base name for a getter or setter so the type can be determined. + *

+ * + * @param name the name of the property + * @param value a string representing the value + * + * @return this builder + */ + public ObjectBuilder addConstructorProperty(final String name, final String value) { + constructorProperties.put(name, value); + return this; + } + + /** + * Adds a method name to be executed after the object is created and the properties are set. The method must not + * have any parameters. + * + * @param methodNames the name of the method to execute + * + * @return this builder + */ + public ObjectBuilder addPostConstructMethods(final String... methodNames) { + if (methodNames != null) { + Collections.addAll(postConstructMethods, methodNames); + } + return this; + } + + /** + * Adds a property to be set on the object after it has been created. + * + * @param name the name of the property + * @param value a string representing the value + * + * @return this builder + */ + public ObjectBuilder addProperty(final String name, final String value) { + properties.put(name, value); + return this; + } + + /** + * Adds a defined property to be set after the object is created. + * + * @param name the name of the property + * @param type the type of the property + * @param value a supplier for the property value + * + * @return this builder + */ + public ObjectBuilder addDefinedProperty(final String name, final Class type, final Supplier value) { + definedProperties.add(new PropertyValue(name, type, value)); + return this; + } + + /** + * Sets the name of the module used to load the object being created. + * + * @param moduleName the module name or {@code null} to use this class loader + * + * @return this builder + */ + public ObjectBuilder setModuleName(final String moduleName) { + this.moduleName = moduleName; + return this; + } + + /** + * Creates the object when the {@linkplain Supplier#get() supplier} value is accessed. + * + * @return a supplier which can create the object + */ + public ConfigurationResource build() { + if (className == null) { + throw new IllegalArgumentException("className is null"); + } + final Map constructorProperties = Map.copyOf(this.constructorProperties); + final Map properties = Map.copyOf(this.properties); + // TODO (jrp) determine why this needs to be mutable + final Set postConstructMethods = new LinkedHashSet<>(this.postConstructMethods); + final String moduleName = this.moduleName; + return construct(moduleName, constructorProperties, properties, postConstructMethods); + } + + private PropertyValue findDefinedProperty(final String name) { + for (PropertyValue value : definedProperties) { + if (name.equals(value.name)) { + return value; + } + } + return null; + } + + /** + * Creates the object when the {@linkplain Supplier#get() supplier} value is accessed. + * + * @return a supplier which can create the object + */ + private ConfigurationResource construct(final String moduleName, final Map constructorProperties, final Map properties, final Set postConstructMethods) { + final Supplier supplier = () -> { + if (className == null) { + throw new IllegalArgumentException("className is null"); + } + final ClassLoader classLoader; + if (moduleName != null) { + try { + classLoader = ModuleFinder.getClassLoader(moduleName); + } catch (Throwable e) { + throw new IllegalArgumentException(String.format("Failed to load module \"%s\"", moduleName), e); + } + } else { + classLoader = ObjectBuilder.class.getClassLoader(); + } + final Class actualClass; + try { + actualClass = Class.forName(className, true, classLoader).asSubclass(baseClass); + } catch (Exception e) { + throw new IllegalArgumentException(String.format("Failed to load class \"%s\"", className), e); + } + final int length = constructorProperties.size(); + final Class[] paramTypes = new Class[length]; + final Object[] params = new Object[length]; + int i = 0; + for (Map.Entry entry : constructorProperties.entrySet()) { + final String property = entry.getKey(); + final Class type = getConstructorPropertyType(actualClass, property); + if (type == null) { + throw new IllegalArgumentException(String.format("No property named \"%s\" in \"%s\"", property, className)); + } + paramTypes[i] = type; + params[i] = valueResolver.resolve(property, type, entry.getValue(), definedValueResolver); + i++; + } + final Constructor constructor; + try { + constructor = actualClass.getConstructor(paramTypes); + } catch (Exception e) { + throw new IllegalArgumentException(String.format("Failed to locate constructor in class \"%s\"", className), e); + } + + // Get all the setters + final Map setters = new LinkedHashMap<>(); + for (Map.Entry entry : properties.entrySet()) { + final Method method = getPropertySetter(actualClass, entry.getKey()); + if (method == null) { + throw new IllegalArgumentException(String.format("Failed to locate setter for property \"%s\" on type \"%s\"", entry.getKey(), className)); + } + // Get the value type for the setter + Class type = getPropertyType(method); + if (type == null) { + throw new IllegalArgumentException(String.format("Failed to determine type for setter \"%s\" on type \"%s\"", method.getName(), className)); + } + setters.put(method, valueResolver.resolve(entry.getKey(), type, entry.getValue(), definedValueResolver)); + } + + // Define known type parameters + for (PropertyValue value : definedProperties) { + final String methodName = getPropertySetterName(value.name); + try { + final Method method = actualClass.getMethod(methodName, value.type); + setters.put(method, value.value.get()); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException(String.format("Failed to find setter method for property \"%s\" on type \"%s\"", value.name, className), e); + } + } + + // Get all the post construct methods + final Set postConstruct = new LinkedHashSet<>(); + for (String methodName : postConstructMethods) { + try { + postConstruct.add(actualClass.getMethod(methodName)); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException(String.format("Failed to find post construct method \"%s\" on type \"%s\"", methodName, className), e); + } + } + try { + T instance = constructor.newInstance(params); + + // Execute setters + for (Map.Entry entry : setters.entrySet()) { + entry.getKey().invoke(instance, entry.getValue()); + } + + // Execute post construct methods + for (Method method : postConstruct) { + method.invoke(instance); + } + + return instance; + } catch (Exception e) { + throw new IllegalArgumentException(String.format("Failed to instantiate class \"%s\"", className), e); + } + + }; + return ConfigurationResource.of(supplier); + } + + private static Class getPropertyType(Class clazz, String propertyName) { + return getPropertyType(getPropertySetter(clazz, propertyName)); + } + + private static Class getPropertyType(final Method setter) { + return setter != null ? setter.getParameterTypes()[0] : null; + } + + private static Class getConstructorPropertyType(Class clazz, String propertyName) { + final Method getter = getPropertyGetter(clazz, propertyName); + return getter != null ? getter.getReturnType() : getPropertyType(clazz, propertyName); + } + + private static Method getPropertySetter(Class clazz, String propertyName) { + final String set = getPropertySetterName(propertyName); + for (Method method : clazz.getMethods()) { + if ((method.getName() + .equals(set) && Modifier.isPublic(method.getModifiers())) && method.getParameterTypes().length == 1) { + return method; + } + } + return null; + } + + private static Method getPropertyGetter(Class clazz, String propertyName) { + final String upperPropertyName = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); + final Pattern pattern = Pattern.compile("(get|has|is)(" + Pattern.quote(upperPropertyName) + ")"); + for (Method method : clazz.getMethods()) { + if ((pattern.matcher(method.getName()) + .matches() && Modifier.isPublic(method.getModifiers())) && method.getParameterTypes().length == 0) { + return method; + } + } + return null; + } + + private static String getPropertySetterName(final String propertyName) { + return "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); + } + + static class ModuleFinder { + + private ModuleFinder() { + } + + static ClassLoader getClassLoader(final String moduleName) throws Exception { + ModuleLoader moduleLoader = ModuleLoader.forClass(ModuleFinder.class); + if (moduleLoader == null) { + moduleLoader = Module.getBootModuleLoader(); + } + return moduleLoader.loadModule(moduleName).getClassLoader(); + } + } + + private static class PropertyValue implements Comparable { + final String name; + final Class type; + final Supplier value; + + private PropertyValue(final String name, final Class type, final Supplier value) { + this.name = name; + this.type = type; + this.value = value; + } + + @Override + public int compareTo(final PropertyValue o) { + return name.compareTo(o.name); + } + } +} diff --git a/logmanager/src/main/java/org/wildfly/core/logmanager/ObjectUpdater.java b/logmanager/src/main/java/org/wildfly/core/logmanager/ObjectUpdater.java new file mode 100644 index 00000000000..5f06248b0ed --- /dev/null +++ b/logmanager/src/main/java/org/wildfly/core/logmanager/ObjectUpdater.java @@ -0,0 +1,201 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.wildfly.core.logmanager; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.jboss.logmanager.configuration.ContextConfiguration; + +/** + * Helper to lazily build an object. + * + * @author James R. Perkins + */ +@SuppressWarnings({"UnusedReturnValue"}) +public class ObjectUpdater { + private final T instance; + private final Map properties; + private final Set definedProperties; + private final Function definedValueResolver; + private final ValueResolver valueResolver; + + private ObjectUpdater(final ContextConfiguration contextConfiguration, + final Class baseClass, final T instance) { + this.instance = instance; + properties = new LinkedHashMap<>(); + definedProperties = new LinkedHashSet<>(); + definedValueResolver = (name) -> { + final PropertyValue propertyValue = findDefinedProperty(name); + return propertyValue == null ? null : propertyValue.value.get(); + }; + valueResolver = ValueResolver.of(contextConfiguration, baseClass); + } + + /** + * Create a new {@link ObjectUpdater}. + * + * @param baseClass the base type + * @param instance the instance to update + * @param the type being created + * + * @return a new {@link ObjectUpdater} + */ + public static ObjectUpdater of(final ContextConfiguration contextConfiguration, + final Class baseClass, final T instance) { + return new ObjectUpdater<>(contextConfiguration, baseClass, instance); + } + + /** + * Adds a property to be set on the object after it has been created. + * + * @param name the name of the property + * @param value a string representing the value + * + * @return this builder + */ + public ObjectUpdater addProperty(final String name, final String value) { + properties.put(name, value); + return this; + } + + public ObjectUpdater clearProperty(final String name) { + properties.put(name, null); + return this; + } + + /** + * Adds a defined property to be set after the object is created. + * + * @param name the name of the property + * @param type the type of the property + * @param value a supplier for the property value + * + * @return this builder + */ + public ObjectUpdater addDefinedProperty(final String name, final Class type, final Supplier value) { + definedProperties.add(new PropertyValue(name, type, value)); + return this; + } + + /** + * Creates an object when the {@linkplain Supplier#get() supplier} value is accessed. + * + * @return a supplier which can create the object + */ + public T update() { + final Map properties = Map.copyOf(this.properties); + final T instance = this.instance; + final Class actualClass = instance.getClass(); + final String className = instance.getClass().getName(); + // Get all the setters + final Map setters = new LinkedHashMap<>(); + for (Map.Entry entry : properties.entrySet()) { + final Method method = getPropertySetter(actualClass, entry.getKey()); + if (method == null) { + throw new IllegalArgumentException(String + .format("Failed to locate setter for property \"%s\" on type \"%s\"", + entry.getKey(), + className)); + } + // Get the value type for the setter + Class type = getPropertyType(method); + if (type == null) { + throw new IllegalArgumentException(String + .format("Failed to determine type for setter \"%s\" on type \"%s\"", + method.getName(), + className)); + } + setters.put(method, valueResolver.resolve(entry.getKey(), type, entry.getValue(), definedValueResolver)); + } + + // Define known type parameters + for (PropertyValue value : definedProperties) { + final String methodName = getPropertySetterName(value.name); + try { + final Method method = actualClass.getMethod(methodName, value.type); + setters.put(method, value.value.get()); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException(String.format( + "Failed to find setter method for property \"%s\" on type \"%s\"", value.name, className), e); + } + } + try { + // Execute setters + for (Map.Entry entry : setters.entrySet()) { + entry.getKey().invoke(instance, entry.getValue()); + } + return instance; + } catch (Exception e) { + throw new IllegalArgumentException(String.format("Failed to instantiate class \"%s\"", className), e); + } + } + + private PropertyValue findDefinedProperty(final String name) { + for (PropertyValue value : definedProperties) { + if (name.equals(value.name)) { + return value; + } + } + return null; + } + + private static Class getPropertyType(final Method setter) { + return setter != null ? setter.getParameterTypes()[0] : null; + } + + private static Method getPropertySetter(Class clazz, String propertyName) { + final String set = getPropertySetterName(propertyName); + for (Method method : clazz.getMethods()) { + if ((method.getName().equals(set) && Modifier.isPublic(method.getModifiers())) + && method.getParameterTypes().length == 1) { + return method; + } + } + return null; + } + + private static String getPropertySetterName(final String propertyName) { + return "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); + } + + private static class PropertyValue implements Comparable { + final String name; + final Class type; + final Supplier value; + + private PropertyValue(final String name, final Class type, final Supplier value) { + this.name = name; + this.type = type; + this.value = value; + } + + @Override + public int compareTo(final PropertyValue o) { + return name.compareTo(o.name); + } + } +} diff --git a/logmanager/src/main/java/org/wildfly/core/logmanager/ValueResolver.java b/logmanager/src/main/java/org/wildfly/core/logmanager/ValueResolver.java new file mode 100644 index 00000000000..09f1023ac27 --- /dev/null +++ b/logmanager/src/main/java/org/wildfly/core/logmanager/ValueResolver.java @@ -0,0 +1,126 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.wildfly.core.logmanager; + +import java.nio.charset.Charset; +import java.util.TimeZone; +import java.util.function.Function; +import java.util.logging.Level; + +import org.jboss.logmanager.configuration.ContextConfiguration; + +/** + * Resolves values for handlers, filters, formatters or POJO's for the log manager. + * + * @author James R. Perkins + */ +class ValueResolver { + + private final ContextConfiguration contextConfiguration; + // TODO (jrp) should this be required? + private final Class beanClass; + + private ValueResolver(final ContextConfiguration contextConfiguration, final Class beanClass) { + this.contextConfiguration = contextConfiguration; + this.beanClass = beanClass; + } + + static ValueResolver of(final ContextConfiguration contextConfiguration, final Class beanClass) { + return new ValueResolver(contextConfiguration, beanClass); + } + + Object resolve(final String propertyName, final Class paramType, final String value, final Function definedResolver) { + if (value == null) { + if (paramType.isPrimitive()) { + final var result = primitiveDefault(paramType); + if (result != null) { + return result; + } + throw new IllegalArgumentException( + String.format("Cannot assign null value to primitive property \"%s\" of %s", propertyName, beanClass)); + } + return null; + } + final var trimmedValue = value.trim(); + if (paramType == String.class) { + // Don't use the trimmed value for strings + return value; + } else if (paramType == Level.class) { + return contextConfiguration.getContext().getLevelForName(trimmedValue); + } else if (paramType == java.util.logging.Logger.class) { + return contextConfiguration.getContext().getLogger(trimmedValue); + } else if (paramType == boolean.class || paramType == Boolean.class) { + return Boolean.valueOf(trimmedValue); + } else if (paramType == byte.class || paramType == Byte.class) { + return Byte.valueOf(trimmedValue); + } else if (paramType == short.class || paramType == Short.class) { + return Short.valueOf(trimmedValue); + } else if (paramType == int.class || paramType == Integer.class) { + return Integer.valueOf(trimmedValue); + } else if (paramType == long.class || paramType == Long.class) { + return Long.valueOf(trimmedValue); + } else if (paramType == float.class || paramType == Float.class) { + return Float.valueOf(trimmedValue); + } else if (paramType == double.class || paramType == Double.class) { + return Double.valueOf(trimmedValue); + } else if (paramType == char.class || paramType == Character.class) { + return !trimmedValue.isEmpty() ? trimmedValue.charAt(0) : 0; + } else if (paramType == TimeZone.class) { + return TimeZone.getTimeZone(trimmedValue); + } else if (paramType == Charset.class) { + return Charset.forName(trimmedValue); + } else if (paramType.isAssignableFrom(Level.class)) { + return Level.parse(trimmedValue); + } else if (paramType.isEnum()) { + return Enum.valueOf(paramType.asSubclass(Enum.class), trimmedValue); + } else if (contextConfiguration.hasObject(trimmedValue)) { + return contextConfiguration.getObject(trimmedValue); + } else { + if (definedResolver != null) { + final var result = definedResolver.apply(propertyName); + if (result != null) { + return result; + } + } + throw new IllegalArgumentException("Unknown parameter type for property " + propertyName + " on " + beanClass); + } + } + + private Object primitiveDefault(final Class paramType) { + if (paramType == boolean.class) { + return false; + } else if (paramType == byte.class) { + return (byte) 0x00; + } else if (paramType == short.class) { + return (short) 0; + } else if (paramType == int.class) { + return 0; + } else if (paramType == long.class) { + return 0L; + } else if (paramType == float.class) { + return 0.0f; + } else if (paramType == double.class) { + return 0.0d; + } else if (paramType == char.class) { + return '\u0000'; + } + return null; + } +} diff --git a/logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyConfiguratorFactory.java b/logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyConfiguratorFactory.java new file mode 100644 index 00000000000..496b458f1a4 --- /dev/null +++ b/logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyConfiguratorFactory.java @@ -0,0 +1,105 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.wildfly.core.logmanager; + +import java.util.logging.Level; + +import org.jboss.logmanager.ConfiguratorFactory; +import org.jboss.logmanager.LogContext; +import org.jboss.logmanager.LogContextConfigurator; +import org.jboss.logmanager.Logger; +import org.jboss.logmanager.config.LogContextConfiguration; +import org.jboss.logmanager.config.PropertyLogContextConfiguration; +import org.jboss.logmanager.configuration.ContextConfiguration; + +/** + * @author James R. Perkins + */ +// TODO (jrp) do we need this or just the LogContextConfigurator? +// TODO (jrp) we need to consider what happens when we don't execute the runtime stage. Possibly with logging we do the +// TODO (jrp) configuration in the model stage. Or we have a default ConsoleHandler only. This can be handled in the +// TODO (jrp) subsystem though +public class WildFlyConfiguratorFactory implements ConfiguratorFactory { + + @Override + public LogContextConfigurator create() { + return (logContext, inputStream) -> { + // TODO (jrp) testing, this should be thought about + WildFlyLogContextSelector.getContextSelector(); + final LogContext context = logContext == null ? WildFlyLogContextSelector.getContextSelector() + .getLogContext() : logContext; + final LogContextConfiguration configuration = PropertyLogContextConfiguration.configure( + LogContextConfiguration.getInstance(context), inputStream); + + // TODO (jrp) what do we do if there is a logging.properties file? We do need to support this for + // TODO (jrp) legacy reasons. However, we also need to support adding the DelayedHandler. The + // TODO (jrp) PropertiesLogContextConfigurator will attempt to load implementations of LogContextConfigurator + // TODO (jrp) with a service loader. + // TODO (jrp) I think what we do is check if a ContextConfiguration is attached to the current context. + // TODO (jrp) if it is, log a warning/error, if not continue. + + // TODO (jrp) we need a way to set the default log level + //final Level defaultLevel = Level.INFO; + + // Configure a DelayedHandler + //final WildFlyDelayedHandler delayedHandler = new WildFlyDelayedHandler(logContext); + //delayedHandler.setLevel(defaultLevel); + //delayedHandler.setCloseChildren(false); + // Add the handler to the root logger + //final Logger root = logContext.getLogger(""); + //root.addHandler(delayedHandler); + //root.setLevel(defaultLevel); + + // Add this configuration as the context configuration + logContext.attach(ContextConfiguration.CONTEXT_CONFIGURATION_KEY, configuration); + }; + } + + @Override + public int priority() { + return 0; + } + + static void reset() { + final LogContext logContext = LogContext.getLogContext(); + + // Add this configuration to a default ContextConfiguration + var configuration = logContext.detach(ContextConfiguration.CONTEXT_CONFIGURATION_KEY); + if (configuration != null) { + try { + configuration.close(); + } catch (Exception e) { + // TODO (jrp) what do we actually do here? + throw new RuntimeException(e); + } + } + final Level defaultLevel = Level.INFO; + // Configure a DelayedHandler + final WildFlyDelayedHandler delayedHandler = new WildFlyDelayedHandler(logContext); + delayedHandler.setLevel(defaultLevel); + delayedHandler.setCloseChildren(false); + // Add the handler to the root logger + final Logger root = logContext.getLogger(""); + root.addHandler(delayedHandler); + root.setLevel(defaultLevel); + configuration = LogContextConfiguration.getInstance(logContext); + logContext.attach(ContextConfiguration.CONTEXT_CONFIGURATION_KEY, configuration); + } +} diff --git a/logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyDelayedHandler.java b/logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyDelayedHandler.java new file mode 100644 index 00000000000..673849ca692 --- /dev/null +++ b/logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyDelayedHandler.java @@ -0,0 +1,114 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.wildfly.core.logmanager; + +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.stream.Stream; + +import org.jboss.logmanager.ExtHandler; +import org.jboss.logmanager.LogContext; +import org.jboss.logmanager.Logger; +import org.jboss.logmanager.config.LogContextConfiguration; +import org.jboss.logmanager.formatters.PatternFormatter; +import org.jboss.logmanager.handlers.ConsoleHandler; +import org.jboss.logmanager.handlers.DelayedHandler; + +/** + * @author James R. Perkins + */ +public class WildFlyDelayedHandler extends DelayedHandler { + private final Thread shutdownHandler; + + public WildFlyDelayedHandler(final LogContext logContext) { + super(logContext); + shutdownHandler = new Thread(() -> { + Formatter formatter = getFormatter(); + if (formatter == null) { + formatter = new PatternFormatter("%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n"); + } + final ConsoleHandler consoleHandler = new ConsoleHandler(formatter); + super.addHandler(consoleHandler); + }); + Runtime.getRuntime().addShutdownHook(shutdownHandler); + } + + @Override + public void addHandler(final Handler handler) throws SecurityException { + Runtime.getRuntime().removeShutdownHook(shutdownHandler); + super.addHandler(handler); + } + + @Override + public Handler[] setHandlers(final Handler[] newHandlers) throws SecurityException { + Runtime.getRuntime().removeShutdownHook(shutdownHandler); + return super.setHandlers(newHandlers); + } + + // TODO (jrp) this should probably live elsewhere + public static void reset() throws SecurityException { + final var configuration = LogContextConfiguration.getInstance(); + // Find this DelayedHandler on the logger if it exists + final var handlers = configuration.getLogger("").getHandlers(); + if (handlers != null && handlers.length > 0) { + // Find the DelayedHandler + final var delayedHandler = Stream.of(handlers) + .filter(handler -> handler.getClass().getName().equals(WildFlyDelayedHandler.class.getName())) + .findFirst(); + if (delayedHandler.isPresent()) { + final WildFlyDelayedHandler handler = (WildFlyDelayedHandler) delayedHandler.get(); + handler.lock.lock(); + try { + final Handler[] childHandlers = handler.clearHandlers(); + // Safely close each child handler + for (var h : childHandlers) { + try { + h.close(); + } catch (Exception ignore) { + // TODO (jrp) should we actually log something here? + } + } + WildFlyConfiguratorFactory.reset(); + } finally { + handler.lock.unlock(); + } + } + } + } + + public static void setHandlers(final Logger logger, final Handler[] newHandlers) throws SecurityException { + // Find this DelayedHandler on the logger if it exists + final var handlers = logger.getHandlers(); + if (handlers == null || handlers.length == 0) { + // Set the handlers on the logger + logger.setHandlers(newHandlers); + } else { + // Find the DelayedHandler + final var delayedHandler = Stream.of(handlers) + .filter(handler -> handler.getClass().getName().equals(WildFlyDelayedHandler.class.getName())) + .findFirst(); + if (delayedHandler.isPresent()) { + ((ExtHandler) delayedHandler.get()).setHandlers(newHandlers); + } else { + logger.setHandlers(newHandlers); + } + } + } +} diff --git a/logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyLogContextInitializer.java b/logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyLogContextInitializer.java new file mode 100644 index 00000000000..743f3c328e7 --- /dev/null +++ b/logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyLogContextInitializer.java @@ -0,0 +1,53 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.wildfly.core.logmanager; + +import java.util.logging.Handler; +import java.util.logging.Level; + +import org.jboss.logmanager.LogContextInitializer; + +/** + * @author James R. Perkins + */ +public class WildFlyLogContextInitializer implements LogContextInitializer { + @Override + public Level getInitialLevel(final String loggerName) { + // TODO (jrp) we might need a system property for this + return LogContextInitializer.super.getInitialLevel(loggerName); + } + + @Override + public Level getMinimumLevel(final String loggerName) { + // TODO (jrp) we might need a system property for this + return LogContextInitializer.super.getMinimumLevel(loggerName); + } + + @Override + public Handler[] getInitialHandlers(final String loggerName) { + // TODO (jrp) here we put the DelayedHandler. Possibly a static one so we have access to it + return LogContextInitializer.super.getInitialHandlers(loggerName); + } + + @Override + public boolean useStrongReferences() { + return true; + } +} diff --git a/logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyLogContextSelector.java b/logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyLogContextSelector.java new file mode 100644 index 00000000000..349ca9f2d5c --- /dev/null +++ b/logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyLogContextSelector.java @@ -0,0 +1,170 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.wildfly.core.logmanager; + +import org.jboss.logmanager.LogContext; +import org.jboss.logmanager.LogContextSelector; + +/** + * The log context selector to use for the WildFly logging extension. + * + * @author James R. Perkins + */ +public interface WildFlyLogContextSelector extends LogContextSelector { + + /** + * Get and set the log context. + * + * @param newValue the new log context value, or {@code null} to clear + * + * @return the previous log context value, or {@code null} if none was set + * + * @see org.jboss.logmanager.ThreadLocalLogContextSelector#getAndSet(Object, org.jboss.logmanager.LogContext) + */ + // TODO (jrp) shouldn't this be used for logging profiles? Maybe it's not working correctly. + LogContext setLocalContext(LogContext newValue); + + /** + * Register a class loader with a log context. + * + * @param classLoader the class loader + * @param logContext the log context + * + * @throws IllegalArgumentException if the class loader is already associated with a log context + * @see org.jboss.logmanager.ClassLoaderLogContextSelector#registerLogContext(ClassLoader, + * org.jboss.logmanager.LogContext) + */ + void registerLogContext(ClassLoader classLoader, LogContext logContext); + + /** + * Unregister a class loader/log context association. + * + * @param classLoader the class loader + * @param logContext the log context + * + * @return {@code true} if the association exists and was removed, {@code false} otherwise + * + * @see org.jboss.logmanager.ClassLoaderLogContextSelector#unregisterLogContext(ClassLoader, + * org.jboss.logmanager.LogContext) + */ + boolean unregisterLogContext(ClassLoader classLoader, LogContext logContext); + + /** + * Register a class loader which is a known log API, and thus should be skipped over when searching for the + * log context to use for the caller class. + * + * @param apiClassLoader the API class loader + * + * @return {@code true} if this class loader was previously unknown, or {@code false} if it was already + * registered + * + * @see org.jboss.logmanager.ClassLoaderLogContextSelector#addLogApiClassLoader(ClassLoader) + */ + boolean addLogApiClassLoader(ClassLoader apiClassLoader); + + /** + * Remove a class loader from the known log APIs set. + * + * @param apiClassLoader the API class loader + * + * @return {@code true} if the class loader was removed, or {@code false} if it was not known to this selector + * + * @see org.jboss.logmanager.ClassLoaderLogContextSelector#removeLogApiClassLoader(ClassLoader) + */ + boolean removeLogApiClassLoader(ClassLoader apiClassLoader); + + /** + * Returns the number of registered {@link org.jboss.logmanager.LogContext log contexts}. + * + * @return the number of registered log contexts + */ + int registeredCount(); + + /** + * Get or create the log context based on the logging profile. + * + * @param profileName the logging profile to get or create the log context for + * + * @return the log context that was found or a new log context + */ + LogContext getOrCreateProfile(final String profileName); + + /** + * Returns the log context associated with the logging profile or {@code null} if the logging profile does not have + * an associated log context. + * + * @param loggingProfile the logging profile associated with the log context + * + * @return the log context or {@code null} if the logging profile is not associated with a log context + */ + LogContext getProfileContext(String loggingProfile); + + /** + * Checks to see if the logging profile has a log context associated with it. + * + * @param loggingProfile the logging profile to check + * + * @return {@code true} if the logging profile has an associated log context, otherwise {@code false} + */ + boolean profileContextExists(String loggingProfile); + + /** + * Adds the associated log context from the logging profile. + * + * @param loggingProfile the logging profile associated with the log context + * @param context the context to associate the profile to + * + * @return the log context that was removed or {@code null} if there was no log context associated + */ + LogContext addProfileContext(String loggingProfile, LogContext context); + + /** + * Removes the associated log context from the logging profile. + * + * @param loggingProfile the logging profile associated with the log context + * + * @return the log context that was removed or {@code null} if there was no log context associated + */ + LogContext removeProfileContext(String loggingProfile); + + static WildFlyLogContextSelector getContextSelector() { + final var found = LogContext.getLogContextSelector(); + if (found instanceof WildFlyLogContextSelector) { + return (WildFlyLogContextSelector) found; + } + // We need to create a new instance and wrap the current one + // TODO (jrp) this is not thread-safe + final WildFlyLogContextSelector wrapped = new WildFlyLogContextSelectorImpl(found); + LogContext.setLogContextSelector(wrapped); + return wrapped; + } + + static WildFlyLogContextSelector getEmbeddedContextSelector() { + final var found = LogContext.getLogContextSelector(); + if (found instanceof EmbeddedLogContextSelector) { + return (WildFlyLogContextSelector) found; + } + // We need to create a new instance and wrap the current one + // TODO (jrp) this is not thread-safe + final WildFlyLogContextSelector embedded = new EmbeddedLogContextSelector(); + LogContext.setLogContextSelector(embedded); + return embedded; + } +} diff --git a/logging/src/main/java/org/jboss/as/logging/logmanager/WildFlyLogContextSelectorImpl.java b/logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyLogContextSelectorImpl.java similarity index 63% rename from logging/src/main/java/org/jboss/as/logging/logmanager/WildFlyLogContextSelectorImpl.java rename to logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyLogContextSelectorImpl.java index d2197709ce6..f9715637c2b 100644 --- a/logging/src/main/java/org/jboss/as/logging/logmanager/WildFlyLogContextSelectorImpl.java +++ b/logmanager/src/main/java/org/wildfly/core/logmanager/WildFlyLogContextSelectorImpl.java @@ -1,26 +1,29 @@ /* * JBoss, Home of Professional Open Source. - * Copyright 2013, Red Hat, Inc., and individual contributors - * as indicated by the @author tags. See the copyright.txt file in the - * distribution for a full listing of individual contributors. * - * This is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. + * Licensed 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 * - * You should have received a copy of the GNU Lesser General Public - * License along with this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * 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.jboss.as.logging.logmanager; +package org.wildfly.core.logmanager; + +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import org.jboss.logmanager.ClassLoaderLogContextSelector; import org.jboss.logmanager.LogContext; @@ -35,6 +38,9 @@ class WildFlyLogContextSelectorImpl implements WildFlyLogContextSelector { private final ClassLoaderLogContextSelector contextSelector; private final ThreadLocal localContext = new ThreadLocal<>(); + + private final ConcurrentMap profileContexts = new ConcurrentHashMap<>(); + private final Lock lock; private int counter; private int dftCounter; @@ -57,6 +63,7 @@ class WildFlyLogContextSelectorImpl implements WildFlyLogContextSelector { counter = 0; dftCounter = 0; contextSelector = new ClassLoaderLogContextSelector(dft, true); + this.lock = new ReentrantLock(); } @Override @@ -65,9 +72,13 @@ public LogContext getLogContext() { if (localContext != null) { return localContext; } + // Not sure where, but there seems to be a race condition here. final int counter; - synchronized (this) { + lock.lock(); + try { counter = this.counter; + } finally { + lock.unlock(); } // If we have no registered contexts we can just use the default selector. This should improve performance // in most cases as the call stack will not be walked. This does depend on the on what was used for the @@ -93,7 +104,8 @@ public void registerLogContext(final ClassLoader classLoader, final LogContext l // We want to register regardless of the current counter for cases when a different log context is registered // later. contextSelector.registerLogContext(classLoader, logContext); - synchronized (this) { + lock.lock(); + try { if (counter > 0) { counter++; } else if (logContext != defaultLogContextSelector.getLogContext()) { @@ -104,13 +116,16 @@ public void registerLogContext(final ClassLoader classLoader, final LogContext l // We're using the default log context at this point dftCounter++; } + } finally { + lock.unlock(); } } @Override public boolean unregisterLogContext(final ClassLoader classLoader, final LogContext logContext) { if (contextSelector.unregisterLogContext(classLoader, logContext)) { - synchronized (this) { + lock.lock(); + try { if (counter > 0) { counter--; } else if (dftCounter > 0) { @@ -118,6 +133,8 @@ public boolean unregisterLogContext(final ClassLoader classLoader, final LogCont // registered log contexts must be the default log context. dftCounter--; } + } finally { + lock.unlock(); } return true; } @@ -136,8 +153,44 @@ public boolean removeLogApiClassLoader(final ClassLoader apiClassLoader) { @Override public int registeredCount() { - synchronized (this) { + lock.lock(); + try { return counter; + } finally { + lock.unlock(); + } + } + + @Override + public LogContext getOrCreateProfile(final String profileName) { + LogContext result = profileContexts.get(profileName); + if (result == null) { + result = LogContext.create(); + final LogContext current = profileContexts.putIfAbsent(profileName, result); + if (current != null) { + result = current; + } } + return result; + } + + @Override + public LogContext getProfileContext(final String loggingProfile) { + return loggingProfile == null ? null : profileContexts.get(loggingProfile); + } + + @Override + public boolean profileContextExists(final String loggingProfile) { + return loggingProfile != null && profileContexts.containsKey(loggingProfile); + } + + @Override + public LogContext addProfileContext(final String loggingProfile, final LogContext context) { + return profileContexts.put(Objects.requireNonNull(loggingProfile), context); + } + + @Override + public LogContext removeProfileContext(final String loggingProfile) { + return profileContexts.remove(loggingProfile); } } diff --git a/logmanager/src/main/resources/META-INF/services/org.jboss.logmanager.ConfiguratorFactory b/logmanager/src/main/resources/META-INF/services/org.jboss.logmanager.ConfiguratorFactory new file mode 100644 index 00000000000..9ffcb81c751 --- /dev/null +++ b/logmanager/src/main/resources/META-INF/services/org.jboss.logmanager.ConfiguratorFactory @@ -0,0 +1,20 @@ +# +# JBoss, Home of Professional Open Source. +# +# Copyright 2023 Red Hat, Inc., and individual contributors +# as indicated by the @author tags. +# +# Licensed 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. +# + +org.wildfly.core.logmanager.WildFlyConfiguratorFactory \ No newline at end of file diff --git a/logmanager/src/main/resources/META-INF/services/org.jboss.logmanager.LogContextInitializer b/logmanager/src/main/resources/META-INF/services/org.jboss.logmanager.LogContextInitializer new file mode 100644 index 00000000000..fca5b04fc4b --- /dev/null +++ b/logmanager/src/main/resources/META-INF/services/org.jboss.logmanager.LogContextInitializer @@ -0,0 +1,20 @@ +# +# JBoss, Home of Professional Open Source. +# +# Copyright 2023 Red Hat, Inc., and individual contributors +# as indicated by the @author tags. +# +# Licensed 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. +# + +org.wildfly.core.logmanager.WildFlyLogContextInitializer \ No newline at end of file diff --git a/pom.xml b/pom.xml index 595c9e54c25..089274c6dcf 100644 --- a/pom.xml +++ b/pom.xml @@ -230,7 +230,7 @@ 3.4.3.Final 2.2.1.Final 1.0.1.Final - 3.0.0.Beta1 + 3.0.0.Final-SNAPSHOT 1.1.1.Final 2.1.1.Final 2.1.0.Final @@ -293,6 +293,7 @@ embedded event-logger host-controller + logmanager logging management-client-content installation-manager @@ -1067,6 +1068,12 @@ org.jboss.logmanager jboss-logmanager ${version.org.jboss.logmanager.jboss-logmanager} + + + org.eclipse.parsson + jakarta.json + + org.jboss.logmanager @@ -1597,6 +1604,11 @@ wildfly-logging ${project.version} + + org.wildfly.core + wildfly-logmanager + ${project.version} + org.wildfly.core wildfly-management-client-content