Index: D:/home/wsargent/javawork/layered-configurator-extra/src/test/java/org/apache/log4j/spi/MockAppender.java =================================================================== --- D:/home/wsargent/javawork/layered-configurator-extra/src/test/java/org/apache/log4j/spi/MockAppender.java (revision 0) +++ D:/home/wsargent/javawork/layered-configurator-extra/src/test/java/org/apache/log4j/spi/MockAppender.java (revision 0) @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.spi; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.Level; + +/** + * A mock appender that provides a nice and easy way to verify that logging + * events passed through a logger end up where they are supposed to go. + *
+ * The basic structure of this class is taken from EasyMock. You tell it to + * expect some number of events, then set it to "replay", run the test and then + * call "verify" to see that the expected result occured. + *
+ * This class is written solely for testing the LayeredConfigurator, and may or
+ * may not be suitable for other tests.
+ *
+ * @author Will Sargent
+ *
+ */
+public class MockAppender extends AppenderSkeleton {
+
+ private List expectedEvents = new ArrayList();
+
+ private List actualEvents = new ArrayList();
+
+ private boolean replayMode = false;
+
+ private static MockAppender sSingleton;
+
+ /**
+ * Gets the singleton mock appender. This is inelegant, but we need it so that
+ * we can get at the appender instance defined in the base-appenders.xml file.
+ *
+ * @return the mock appender defined.
+ */
+ public static MockAppender getMockAppender() {
+ return sSingleton;
+ }
+
+ /**
+ * A no-args constructor that initializes the singleton. Probably not
+ * thread-safe.
+ */
+ public MockAppender() {
+ sSingleton = this;
+ }
+
+ public void expectEvent(LoggingEvent expectedEvent) {
+ if (replayMode) {
+ throw new IllegalStateException("Cannot expect events in replay mode");
+ }
+
+ expectedEvents.add(expectedEvent);
+ }
+
+ /**
+ * Sets replayMode to false.
+ */
+ public void replay() {
+ replayMode = true;
+ }
+
+ /**
+ * Resets the lists and sets replayMode to false.
+ */
+ public void reset() {
+ expectedEvents.clear();
+ actualEvents.clear();
+ replayMode = false;
+ }
+
+ public void verify() {
+ // Run through the expected events and see if they match the actual events.
+ if (!replayMode) {
+ throw new IllegalStateException("verify() called before replay()");
+ }
+
+ // Check we've got the same number...
+ if (actualEvents.size() != expectedEvents.size()) {
+ String msg = "Expected " + expectedEvents.size()
+ + " events, but received " + actualEvents.size() + ".";
+ throw new AssertionError(msg);
+ }
+
+ // Only check that the level and message are the same...
+ for (int i = 0; i < expectedEvents.size(); i++) {
+ LoggingEvent expectedEvent = (LoggingEvent) expectedEvents.get(i);
+ LoggingEvent actualEvent = (LoggingEvent) actualEvents.get(i);
+
+ final Level expectedLevel = expectedEvent.getLevel();
+ final Level actualLevel = actualEvent.getLevel();
+ if (!expectedLevel.equals(actualLevel)) {
+ String msg = "Expected " + expectedLevel + ", but was " + actualLevel;
+ throw new AssertionError(msg);
+ }
+
+ final Object expectedMessage = expectedEvent.getMessage();
+ final Object actualMessage = actualEvent.getMessage();
+ if (!expectedMessage.equals(actualMessage)) {
+ String msg = "Expected " + expectedMessage + ", but was "
+ + actualMessage;
+ throw new AssertionError(msg);
+ }
+ }
+ }
+
+ protected void append(LoggingEvent event) {
+ // Check that we're okay to get events...
+ if (!replayMode) {
+ throw new IllegalStateException(
+ "Event appended before replay() was called");
+ }
+
+ // Save this for later.
+ actualEvents.add(event);
+ }
+
+ public void close() {
+ ; // do nothing.
+ }
+
+ public boolean requiresLayout() {
+ return false;
+ }
+
+}
Index: D:/home/wsargent/javawork/layered-configurator-extra/src/test/java/org/apache/log4j/spi/LayeredConfiguratorTest.java
===================================================================
--- D:/home/wsargent/javawork/layered-configurator-extra/src/test/java/org/apache/log4j/spi/LayeredConfiguratorTest.java (revision 0)
+++ D:/home/wsargent/javawork/layered-configurator-extra/src/test/java/org/apache/log4j/spi/LayeredConfiguratorTest.java (revision 0)
@@ -0,0 +1,234 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.ParseException;
+
+import junit.framework.TestCase;
+
+import org.apache.log4j.Hierarchy;
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.helpers.Loader;
+
+/**
+ * Unit tests for the layered configurator. Most of these have to do with
+ * parsing the ${system.property} syntax.
+ *
+ * @author Will Sargent
+ */
+public class LayeredConfiguratorTest extends TestCase {
+ private static final Object GUARD = new Object();
+
+ LayeredConfigurator configurator;
+
+ LoggerRepository repository = new Hierarchy(new RootLogger(Level.DEBUG));
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ configurator = new LayeredConfigurator();
+ }
+
+ /**
+ * Tests that a normal line makes it through parsing.
+ */
+ public final void testParseLineWithNoParameter() {
+ String expected = "http://example.org";
+ try {
+ String actual = configurator.parseLine(expected);
+ assertEquals("Line should have been seen.", expected, actual);
+ } catch (ParseException e) {
+ fail(e.toString());
+ }
+ }
+
+ /**
+ * Tests that a line with substitution makes it through parsing.
+ */
+ public final void testParseLineWithParameter() {
+ String expected = "http://${my.hostname}.org";
+ try {
+ String actual = configurator.parseLine(expected);
+ assertEquals("Line should have been seen.", expected, actual);
+ } catch (ParseException e) {
+ fail(e.toString());
+ }
+ }
+
+ /**
+ * Tests that a line with no terminator does NOT make it through parsing.
+ */
+ public final void testParseLineWithNoParameterTermination() {
+ try {
+ String url = "http://${my.hostname.org";
+ configurator.parseLine(url);
+ fail("Should have thrown parse exception");
+ } catch (ParseException pe) {
+ }
+ }
+
+ /**
+ * Comments should show up as null through the parser.
+ */
+ public final void testParseLineWithComment() {
+ try {
+ String url = "# http://${my.hostname.org";
+ String actual = configurator.parseLine(url);
+ assertNull("Line should have been null.", actual);
+ } catch (ParseException pe) {
+ fail(pe.toString());
+ }
+ }
+
+ /**
+ * Tests that a line containing a raw dollar makes it through parsing.
+ */
+ public final void testParseLineWithDollar() {
+ try {
+ String url = "http://$my.hostname.org";
+ configurator.parseLine(url);
+ } catch (ParseException pe) {
+ fail("Should not have thrown parse exception");
+ }
+ }
+
+ /**
+ * Tests that parameters are substituted appropriately.
+ */
+ public final void testSubstituteParameters() {
+ // Currently only system properties are substituted.
+ System.setProperty("my.hostname", "example");
+
+ String url = "http://${my.hostname}.org";
+ String expected = "http://example.org";
+ String actual = configurator.substituteParameters(url);
+
+ assertEquals("Parameter should have been substituted", expected, actual);
+ }
+
+ /**
+ * Tests the layered configurator in the development configuration. This should
+ * run all the way through debug to fatal.
+ *
+ * @throws MalformedURLException
+ */
+ public final void testDevelopmentConfiguration() throws MalformedURLException {
+
+ // Run through the log4j configuration using the development settings.
+ // The LogManager has all of the initialization logic tied up in a
+ // static method, so we have to set things up so that we can switch
+ // it out on the fly.
+
+ // Set the property so it will resolve to "dev-loggers.properties"
+ System.setProperty("my.environment", "dev");
+
+ URL url = Loader
+ .getResource("org/apache/log4j/spi/log4j-config.properties");
+ configurator.doConfigure(url, repository);
+
+ // Set up the log manager to have the appropriate repository selector...
+ RepositorySelector selector = new DefaultRepositorySelector(repository);
+
+ LogManager.setRepositorySelector(selector, GUARD);
+
+ // Call up a logger, and run it through everything.
+ String categoryClass = "com.tersesystems.log4j";
+ Logger logger = LogManager.getLogger(categoryClass);
+
+ // Look at the appender and see if we got all the messages we expect.
+ MockAppender appender = MockAppender.getMockAppender();
+ appender.reset();
+
+ final String debugMessage = "Debug Message";
+ final String infoMessage = "Info message";
+ final String warnMessage = "Warning message";
+ final String errorMessage = "Error Message";
+ final String fatalMessage = "Fatal message";
+
+ appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.DEBUG, debugMessage, null));
+ appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.INFO, infoMessage, null));
+ appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.WARN, warnMessage, null));
+ appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.ERROR, errorMessage, null));
+ appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.FATAL, fatalMessage, null));
+
+ appender.replay();
+ logger.debug(debugMessage);
+ logger.info(infoMessage);
+ logger.warn(warnMessage);
+ logger.error(errorMessage);
+ logger.fatal(fatalMessage);
+ appender.verify();
+ }
+
+ /**
+ * Tests the layered configurator in the development configuration. This should
+ * run all the way through debug to fatal.
+ *
+ * @throws MalformedURLException
+ */
+ public final void testProductionConfiguration() throws MalformedURLException {
+
+ // Run through the log4j configuration using the development settings.
+ // The LogManager has all of the initialization logic tied up in a
+ // static method, so we have to set things up so that we can switch
+ // it out on the fly.
+
+ // Set the property so it will resolve to "prod-loggers.properties"
+ System.setProperty("my.environment", "prod");
+
+ URL url = Loader
+ .getResource("org/apache/log4j/spi/log4j-config.properties");
+ configurator.doConfigure(url, repository);
+
+ // Set up the log manager to have the appropriate repository selector...
+ RepositorySelector selector = new DefaultRepositorySelector(repository);
+
+ LogManager.setRepositorySelector(selector, GUARD);
+
+ // Call up a logger, and run it through everything.
+ String categoryClass = "com.tersesystems.log4j";
+ Logger logger = LogManager.getLogger(categoryClass);
+
+ // Look at the appender and see if we got all the messages we expect.
+ MockAppender appender = MockAppender.getMockAppender();
+ appender.reset();
+
+ final String debugMessage = "Debug Message";
+ final String infoMessage = "Info message";
+ final String warnMessage = "Warning message";
+ final String errorMessage = "Error Message";
+ final String fatalMessage = "Fatal message";
+
+ // Only expect WARN and above to actually be appended.
+ appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.WARN, warnMessage, null));
+ appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.ERROR, errorMessage, null));
+ appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.FATAL, fatalMessage, null));
+
+ // Play through everything, including the (disabled) debug and info messages.
+ appender.replay();
+ logger.debug(debugMessage);
+ logger.info(infoMessage);
+ logger.warn(warnMessage);
+ logger.error(errorMessage);
+ logger.fatal(fatalMessage);
+ appender.verify();
+ }
+}
Index: D:/home/wsargent/javawork/layered-configurator-extra/src/test/java/org/apache/log4j/spi/ConfiguratorFactoryTest.java
===================================================================
--- D:/home/wsargent/javawork/layered-configurator-extra/src/test/java/org/apache/log4j/spi/ConfiguratorFactoryTest.java (revision 0)
+++ D:/home/wsargent/javawork/layered-configurator-extra/src/test/java/org/apache/log4j/spi/ConfiguratorFactoryTest.java (revision 0)
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import java.net.URL;
+
+import org.apache.log4j.PropertyConfigurator;
+import org.apache.log4j.xml.DOMConfigurator;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the configurator factory.
+ *
+ * @author Will Sargent
+ * @see org.apache.log4j.spi.ConfiguratorFactory
+ */
+public class ConfiguratorFactoryTest extends TestCase {
+
+ private ConfiguratorFactory configuratorFactory;
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ configuratorFactory = new ConfiguratorFactory();
+ }
+
+ /**
+ * Tests adding a fake configurator.
+ * @throws IllegalAccessException
+ * @throws InstantiationException
+ */
+ public final void testAddConfigurator() throws InstantiationException, IllegalAccessException {
+ configuratorFactory.addConfigurator("yaml", YamlConfigurator.class);
+ Configurator actual = configuratorFactory.findConfiguratorByType("yaml");
+
+ assertTrue("Expected yaml configurator here", actual instanceof YamlConfigurator);
+ }
+
+ /**
+ * Checks that we can get a properties configurator.
+ */
+ public final void testGetPropertiesConfigurator() {
+ Configurator actual = configuratorFactory
+ .getConfigurator("log4j.properties");
+ assertTrue("Expected a property configurator",
+ actual instanceof PropertyConfigurator);
+ }
+
+ /**
+ * Tests getting the XML configurator.
+ */
+ public final void testGetDOMConfigurator() {
+ Configurator actual = configuratorFactory.getConfigurator("log4j.xml");
+ assertTrue("Expected a property configurator",
+ actual instanceof DOMConfigurator);
+ }
+
+ /**
+ * Tests that an exception is thrown if we don't find a configurator.
+ */
+ public final void testGetMissingConfigurator() {
+ try {
+ configuratorFactory.getConfigurator("log4j.yaml");
+ fail("An illegal state exception should have been thrown");
+ } catch (IllegalStateException e) {
+ }
+ }
+
+ /**
+ * A fake YAML configurator to test things out.
+ */
+ static class YamlConfigurator implements Configurator {
+
+ public void doConfigure(URL url, LoggerRepository repository) {
+ ; // do nothing
+ }
+ }
+}
Index: D:/home/wsargent/javawork/layered-configurator-extra/src/test/resources/org/apache/log4j/spi/prod-loggers.properties
===================================================================
--- D:/home/wsargent/javawork/layered-configurator-extra/src/test/resources/org/apache/log4j/spi/prod-loggers.properties (revision 0)
+++ D:/home/wsargent/javawork/layered-configurator-extra/src/test/resources/org/apache/log4j/spi/prod-loggers.properties (revision 0)
@@ -0,0 +1,2 @@
+# Define loggers with production properties
+log4j.logger.com.tersesystems.log4j=WARN
\ No newline at end of file
Index: D:/home/wsargent/javawork/layered-configurator-extra/src/test/resources/org/apache/log4j/spi/base-appenders.xml
===================================================================
--- D:/home/wsargent/javawork/layered-configurator-extra/src/test/resources/org/apache/log4j/spi/base-appenders.xml (revision 0)
+++ D:/home/wsargent/javawork/layered-configurator-extra/src/test/resources/org/apache/log4j/spi/base-appenders.xml (revision 0)
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+ * This doesn't mean much in theory, so let's explain why this class exists, and + * give an example. + *
+ * Log4J lacks a way to spread configuration over several files. You have one + * configuration file, either log4j.properties or log4j.xml, and that's it. + *
+ * In projects where you have several environments to keep track of, you may + * want to have some loggers set to DEBUG in the development environment, but + * set to WARN on the production environment. However, you usually want to keep + * all the appenders and logging infrastructure the same. + *
+ * Because Log4J defines appenders and loggers in the same file, you either have + * to define several almost identical files with the appropriate changes for the + * environment, or you have to have an Ant script that goes through and replaces + * tokens for the appropriate environment. + *
+ * The file syntax for the configurators is simple. Here's an example + * log4j-config.properties file: + * + *
+ * # The complicated appenders are rendered in XML. + * base-appenders.xml + * + * # The logging levels can be defined in properties, and can use system properties + * # as parameters. + * ${my.environment}-loggers.properties + *+ * + *
+ * The class is called LayeredConfigurator because it's meant to work in layers. + * The configurators are not reset, so all the settings from the previous + * configurator will still apply unless you override them. + *
+ * When you define -Dmy.environment=dev
, then your development
+ * settings will be loaded. When you define -Dmy.environment=prod
,
+ * then your production settings will be loaded. Either way, your binary
+ * distribution is exactly the same, with only the environment specific
+ * properties
+ *
+ * To enable this class, you must start the JVM with the following system + * properties: + * + *
+ * -Dlog4j.configuratorClass=com.tersesystems.log4j.LayeredConfigurator + * -Dlog4j.configuration=log4j-config.properties + *+ * + *
+ * This class has been written using Java 1.3 syntax, lots of internal methods, + * and with the internal methods protected, so that it can be subclassed and + * modified as needed. + * + * @author Will Sargent + * + * @see org.apache.log4j.spi.Configurator + * @see org.apache.log4j.spi.ConfiguratorFactory + */ +public class LayeredConfigurator implements Configurator { + + protected static final String PARAMETER_START = "${"; + + protected static final String PARAMETER_END = "}"; + + protected static final String COMMENT = "#"; + + /** + * A static version of {@link #doConfigure(URL, LoggerRepository)}. + */ + public static void configure(URL url) { + new LayeredConfigurator() + .doConfigure(url, LogManager.getLoggerRepository()); + } + + /* + * (non-Javadoc) + * + * @see org.apache.log4j.spi.Configurator#doConfigure(java.net.URL, + * org.apache.log4j.spi.LoggerRepository) + */ + public void doConfigure(URL configURL, LoggerRepository repository) { + LogLog.debug("Reading configuration from URL " + configURL); + + if (configURL == null) { + throw new IllegalArgumentException("null configURL"); + } + + if (repository == null) { + throw new IllegalArgumentException("null repository"); + } + + InputStream istream = null; + try { + istream = configURL.openStream(); + doConfigure(istream, repository); + } catch (Exception e) { + LogLog.error("Could not read configuration file from URL [" + configURL + + "].", e); + LogLog.error("Ignoring configuration file [" + configURL + "]."); + return; + } + } + + /** + * Converts the input stream into a reader and then passes to the reader + * method. + * + * @param istream + * @param repository + * @throws IOException + * @throws ParseException + */ + protected void doConfigure(InputStream istream, LoggerRepository repository) + throws IOException, ParseException { + if (istream == null) { + return; + } + + BufferedReader reader = new BufferedReader(new InputStreamReader(istream)); + List configurationReferences = parseConfigurationList(reader); + + LogLog.debug("list = " + configurationReferences); + + doConfigure(configurationReferences, repository); + } + + /** + * Parses the lines of text from the reader. This method deals with the + * opening and closing of IO handles. + * + * @param reader + * the reader to pull in the file. + * @return a list containing the parsed strings. + */ + protected List parseConfigurationList(BufferedReader reader) + throws IOException, ParseException { + if (reader == null) { + throw new IllegalArgumentException("null reader"); + } + + List configurationList = new ArrayList(); + String line = null; + try { + while ((line = reader.readLine()) != null) { + String parsedLine = parseLine(line); + if (parsedLine != null) { + configurationList.add(parsedLine); + } + } + } finally { + reader.close(); + } + return configurationList; + } + + /** + * See if the line starts with #. If it does, then it's a comment. + * + * We don't assume that anything after # can be a comment, because there's + * always the possibility that someone has factored in a URL with a bookmark. + * + * This method does not do any substitution of variables; we want to make sure + * we can parse everything before we move any further. + * + * @throws ParseException + * if the line cannot be parsed. + */ + protected String parseLine(String rawLine) throws ParseException { + // If nothing, then nothing. + if (rawLine == null) { + return null; + } + + String line = rawLine; + String trimmedLine = line.trim(); + + // Ignore a completely empty line. + if (trimmedLine.length() == 0) // NOPMD + { + return null; + } + + // If the line contains nothing but whitespace and then "#" then we + // consider it a comment. + if (trimmedLine.startsWith(COMMENT)) { + return null; + } + + // If the line contains "${" but not "}" then throw an exception. + int fromIndex = line.indexOf(PARAMETER_START); + if (fromIndex != -1) { + int endIndex = line.indexOf(PARAMETER_END, fromIndex + 1); + if (endIndex == -1) { + throw new ParseException("Cannot parse line: " + line, fromIndex); + } + + // For simplicity's sake, let's not nest. + int newStartIndex = line.indexOf(PARAMETER_START, fromIndex + 1); + if (newStartIndex != -1 && newStartIndex < endIndex) { + throw new ParseException("Cannot parse line: " + line, newStartIndex); + } + } + + return rawLine; + } + + /** + * Returns a configurator factory. + * + * @return a new configurator factory. + */ + protected ConfiguratorFactory getConfiguratorFactory() { + return new ConfiguratorFactory(); + } + + /** + * Configures a list of configuration references, substituting parameters as + * needed. + * + * @param configurationReferences + * @param repository + */ + protected void doConfigure(List configurationReferences, + LoggerRepository repository) { + // Run through any substitutions of parameters. Parameters are defined + // in the syntax ${parameter}. They must resolve to strings. + List parsedReferences = substituteListParameters(configurationReferences); + + ConfiguratorFactory configuratorFactory = getConfiguratorFactory(); + + // Go through the list of references, using a configurator factory to + // pull out the appropriate configurator. + for (Iterator iter = parsedReferences.iterator(); iter.hasNext();) { + String reference = (String) iter.next(); + Configurator c = configuratorFactory.getConfigurator(reference); + + URL url; + try { + url = new URL(reference); + } catch (MalformedURLException ex) { + // so, resource is not a URL: + // attempt to get the resource from the class path + url = Loader.getResource(reference); + } + + // The internal reference doesn't resolve to anything either... + if (url == null) { + String msg = "The internal reference " + reference + " does not resolve to a resource"; + throw new IllegalArgumentException(msg); + } + + // Tell the configurator to go do its stuff. + c.doConfigure(url, repository); + } + } + + /** + * Goes through a list of configuration references that may contain + * ${parameter} in the string, and resolves the parameters. + * + * @param configurationReferences + * a list of configuration references with parameters. + * @return a list of configuration files with the parameters replaced by text. + */ + protected List substituteListParameters(List configurationReferences) { + List resolvedList = new ArrayList(); + for (Iterator iter = configurationReferences.iterator(); iter.hasNext();) { + String configRef = (String) iter.next(); + String resolvedRef = substituteParameters(configRef); + resolvedList.add(resolvedRef); + } + + return resolvedList; + } + + /** + * Substitute all the parameters in a reference. + * + * @param configRef + * @return a reference with all the parameters replaced by text. + */ + protected String substituteParameters(String configRef) { + // If I assume Java 1.4 or 1.5, this could be much easier. But let's do + // it the old fashioned way. + + // state can be 1 {dollar), 2 (parsing), or 0 (raw). + // We assume that the string cannot be malformed here. + int state = 0; + StringBuffer line = new StringBuffer(); + StringBuffer parameterName = new StringBuffer(); + byte[] characters = configRef.getBytes(); + for (int i = 0; i < characters.length; i++) { + char ch = (char) characters[i]; + switch (ch) { + case '$': + state = 1; + break; + + case '{': + if (state == 1) { + state = 2; + parameterName.setLength(0); // clear the param name. + } + break; + + case '}': + state = 0; + // look up the parameter name, and append to the line. + String parameterValue = substituteParameter(parameterName.toString()); + line.append(parameterValue); + break; + + default: + if (state == 0) // a raw char + { + line.append(ch); + } else if (state == 1) // just a raw dollar + { + state = 0; + line.append(ch); + } else if (state == 2) // this is a parameter name + { + parameterName.append(ch); + } + } + } + + return line.toString(); + } + + /** + * Substitutes a single parameter. A null value will be replaced by the empty + * string. + * + * @param parameterName + * the parameter's name. + * @return the parameter value. + */ + protected String substituteParameter(String parameterName) { + LogLog.debug("substituteParameter: found " + parameterName); + + String parameterValue = OptionConverter + .getSystemProperty(parameterName, ""); + LogLog.debug("substituteParameter: replacing " + parameterName + " with " + + parameterValue); + return parameterValue; + } +} Index: D:/home/wsargent/javawork/layered-configurator-extra/src/main/java/org/apache/log4j/spi/ConfiguratorFactory.java =================================================================== --- D:/home/wsargent/javawork/layered-configurator-extra/src/main/java/org/apache/log4j/spi/ConfiguratorFactory.java (revision 0) +++ D:/home/wsargent/javawork/layered-configurator-extra/src/main/java/org/apache/log4j/spi/ConfiguratorFactory.java (revision 0) @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.spi; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.log4j.PropertyConfigurator; +import org.apache.log4j.spi.Configurator; +import org.apache.log4j.xml.DOMConfigurator; + +/** + * This is a simple class to abstract the instantation of a configurator. + *
+ * The factory contains a map of types, along with the configurator objects that + * will be used for those types. The factory is configured for references ending + * in ".properties" or ".xml", but other configurators can be added as need be. + *
+ * This class may or may not be thread-safe. No attempt has been made to ensure + * thread-safety. + * + * @author Will Sargent + * @see org.apache.log4j.PropertyConfigurator + * @see org.apache.log4j.xml.DOMConfigurator + */ +public class ConfiguratorFactory { + + /** The string that will return a Properties Configurator. */ + public static final String PROPERTIES_TYPE = "properties"; + + /** The string that will return a DOM configurator. */ + public static final String XML_TYPE = "xml"; + + /** + * A map of types to configurator instances. + */ + private Map configuratorMap = new HashMap(); + + /** + * A default constructor that will call the initialize() method. + */ + public ConfiguratorFactory() { + initialize(); + } + + /** + * Initializes the configurator map. + */ + protected void initialize() { + addConfigurator(PROPERTIES_TYPE, PropertyConfigurator.class); + addConfigurator(XML_TYPE, DOMConfigurator.class); + } + + /** + * Adds a configurator class with the given type. + * + * @param type + * the type of configurator to use. + * @param configuratorClass + * the configurator class. Must take a no-args constructor. + */ + public void addConfigurator(String type, Class configuratorClass) { + if (type == null) { + throw new IllegalArgumentException("Null type"); + } + + if (configuratorClass == null) { + throw new IllegalArgumentException("Null configurator"); + } + + configuratorMap.put(type, configuratorClass); + } + + /** + * Given a reference to a configurator, returns the appropriate configurator + * instance. For example, if the reference is "log4j.properties", then it has + * the "properties" type and a PropertyConfigurator is used. + * + * @param reference + * a reference to a name (typically a file or classpath reference) + * @return the appropriate Configurator object for the reference. + * @throw IllegalStateException if no configurator is found, or the + * configurator could not be instantiated. + */ + public Configurator getConfigurator(String reference) { + if (reference == null) { + throw new IllegalArgumentException("Null reference found."); + } + + // Find the last index of dot. We'll use this as a lookup table. + int lastIndex = reference.lastIndexOf('.'); + + // No dot found. + if (lastIndex == -1) { + throw new IllegalArgumentException("No dot suffix found"); + } + + String suffix = reference.substring(lastIndex + 1); + if (suffix.length() == 0) { + throw new IllegalArgumentException("Null or empty suffix found."); + } + + // Get the configurator. + try { + Configurator c = findConfiguratorByType(suffix); + return c; + } catch (InstantiationException ie) { + String msg = "Could not instantiate configurator from reference " + reference; + throw new IllegalStateException(msg, ie); + } catch (IllegalAccessException iae) { + String msg = "Could not instantiate configurator from reference " + reference; + throw new IllegalStateException(msg, iae); + } + } + + /** + * Instantiates a new configurator given the appropriate type. + * + * @param type + * the type of configurator to use. + * @return the configurator object for that type. + * @throws IllegalAccessException + * @throws InstantiationException + * @throw IllegalStateException if no configurator is found for the reference. + */ + protected Configurator findConfiguratorByType(String type) + throws InstantiationException, IllegalAccessException { + Class configuratorClass = (Class) configuratorMap.get(type); + if (configuratorClass == null) { + throw new IllegalStateException("No configurator class found for type " + + type); + } + return (Configurator) configuratorClass.newInstance(); + } +}