View | Details | Raw Unified | Return to bug 43820
Collapse All | Expand All

(-)D:/home/wsargent/javawork/layered-configurator-extra/src/test/java/org/apache/log4j/spi/MockAppender.java (+146 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
package org.apache.log4j.spi;
19
20
import java.util.ArrayList;
21
import java.util.List;
22
23
import org.apache.log4j.AppenderSkeleton;
24
import org.apache.log4j.Level;
25
26
/**
27
 * A mock appender that provides a nice and easy way to verify that logging
28
 * events passed through a logger end up where they are supposed to go.
29
 * <p>
30
 * The basic structure of this class is taken from EasyMock. You tell it to
31
 * expect some number of events, then set it to "replay", run the test and then
32
 * call "verify" to see that the expected result occured.
33
 * <p>
34
 * This class is written solely for testing the LayeredConfigurator, and may or
35
 * may not be suitable for other tests.
36
 *
37
 * @author Will Sargent
38
 *
39
 */
40
public class MockAppender extends AppenderSkeleton {
41
42
  private List expectedEvents = new ArrayList();
43
44
  private List actualEvents = new ArrayList();
45
46
  private boolean replayMode = false;
47
48
  private static MockAppender sSingleton;
49
50
  /**
51
   * Gets the singleton mock appender. This is inelegant, but we need it so that
52
   * we can get at the appender instance defined in the base-appenders.xml file.
53
   *
54
   * @return the mock appender defined.
55
   */
56
  public static MockAppender getMockAppender() {
57
    return sSingleton;
58
  }
59
60
  /**
61
   * A no-args constructor that initializes the singleton. Probably not
62
   * thread-safe.
63
   */
64
  public MockAppender() {
65
    sSingleton = this;
66
  }
67
68
  public void expectEvent(LoggingEvent expectedEvent) {
69
    if (replayMode) {
70
      throw new IllegalStateException("Cannot expect events in replay mode");
71
    }
72
73
    expectedEvents.add(expectedEvent);
74
  }
75
76
  /**
77
   * Sets replayMode to false.
78
   */
79
  public void replay() {
80
    replayMode = true;
81
  }
82
83
  /**
84
   * Resets the lists and sets replayMode to false.
85
   */
86
  public void reset() {
87
    expectedEvents.clear();
88
    actualEvents.clear();
89
    replayMode = false;
90
  }
91
92
  public void verify() {
93
    // Run through the expected events and see if they match the actual events.
94
    if (!replayMode) {
95
      throw new IllegalStateException("verify() called before replay()");
96
    }
97
98
    // Check we've got the same number...
99
    if (actualEvents.size() != expectedEvents.size()) {
100
      String msg = "Expected " + expectedEvents.size()
101
          + " events, but received " + actualEvents.size() + ".";
102
      throw new AssertionError(msg);
103
    }
104
105
    // Only check that the level and message are the same...
106
    for (int i = 0; i < expectedEvents.size(); i++) {
107
      LoggingEvent expectedEvent = (LoggingEvent) expectedEvents.get(i);
108
      LoggingEvent actualEvent = (LoggingEvent) actualEvents.get(i);
109
110
      final Level expectedLevel = expectedEvent.getLevel();
111
      final Level actualLevel = actualEvent.getLevel();
112
      if (!expectedLevel.equals(actualLevel)) {
113
        String msg = "Expected " + expectedLevel + ", but was " + actualLevel;
114
        throw new AssertionError(msg);
115
      }
116
117
      final Object expectedMessage = expectedEvent.getMessage();
118
      final Object actualMessage = actualEvent.getMessage();
119
      if (!expectedMessage.equals(actualMessage)) {
120
        String msg = "Expected " + expectedMessage + ", but was "
121
            + actualMessage;
122
        throw new AssertionError(msg);
123
      }
124
    }
125
  }
126
127
  protected void append(LoggingEvent event) {
128
    // Check that we're okay to get events...
129
    if (!replayMode) {
130
      throw new IllegalStateException(
131
          "Event appended before replay() was called");
132
    }
133
134
    // Save this for later.
135
    actualEvents.add(event);
136
  }
137
138
  public void close() {
139
    ; // do nothing.
140
  }
141
142
  public boolean requiresLayout() {
143
    return false;
144
  }
145
146
}
(-)D:/home/wsargent/javawork/layered-configurator-extra/src/test/java/org/apache/log4j/spi/LayeredConfiguratorTest.java (+234 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
package org.apache.log4j.spi;
19
20
import java.net.MalformedURLException;
21
import java.net.URL;
22
import java.text.ParseException;
23
24
import junit.framework.TestCase;
25
26
import org.apache.log4j.Hierarchy;
27
import org.apache.log4j.Level;
28
import org.apache.log4j.LogManager;
29
import org.apache.log4j.Logger;
30
import org.apache.log4j.helpers.Loader;
31
32
/**
33
 * Unit tests for the layered configurator. Most of these have to do with
34
 * parsing the ${system.property} syntax.
35
 *
36
 * @author Will Sargent
37
 */
38
public class LayeredConfiguratorTest extends TestCase {
39
  private static final Object GUARD = new Object();
40
41
  LayeredConfigurator configurator;
42
43
  LoggerRepository repository = new Hierarchy(new RootLogger(Level.DEBUG));
44
45
  protected void setUp() throws Exception {
46
    super.setUp();
47
    configurator = new LayeredConfigurator();
48
  }
49
50
  /**
51
   * Tests that a normal line makes it through parsing.
52
   */
53
  public final void testParseLineWithNoParameter() {
54
    String expected = "http://example.org";
55
    try {
56
      String actual = configurator.parseLine(expected);
57
      assertEquals("Line should have been seen.", expected, actual);
58
    } catch (ParseException e) {
59
      fail(e.toString());
60
    }
61
  }
62
63
  /**
64
   * Tests that a line with substitution makes it through parsing.
65
   */
66
  public final void testParseLineWithParameter() {
67
    String expected = "http://${my.hostname}.org";
68
    try {
69
      String actual = configurator.parseLine(expected);
70
      assertEquals("Line should have been seen.", expected, actual);
71
    } catch (ParseException e) {
72
      fail(e.toString());
73
    }
74
  }
75
76
  /**
77
   * Tests that a line with no terminator does NOT make it through parsing.
78
   */
79
  public final void testParseLineWithNoParameterTermination() {
80
    try {
81
      String url = "http://${my.hostname.org";
82
      configurator.parseLine(url);
83
      fail("Should have thrown parse exception");
84
    } catch (ParseException pe) {
85
    }
86
  }
87
88
  /**
89
   * Comments should show up as null through the parser.
90
   */
91
  public final void testParseLineWithComment() {
92
    try {
93
      String url = "# http://${my.hostname.org";
94
      String actual = configurator.parseLine(url);
95
      assertNull("Line should have been null.", actual);
96
    } catch (ParseException pe) {
97
      fail(pe.toString());
98
    }
99
  }
100
101
  /**
102
   * Tests that a line containing a raw dollar makes it through parsing.
103
   */
104
  public final void testParseLineWithDollar() {
105
    try {
106
      String url = "http://$my.hostname.org";
107
      configurator.parseLine(url);
108
    } catch (ParseException pe) {
109
      fail("Should not have thrown parse exception");
110
    }
111
  }
112
113
  /**
114
   * Tests that parameters are substituted appropriately.
115
   */
116
  public final void testSubstituteParameters() {
117
    // Currently only system properties are substituted.
118
    System.setProperty("my.hostname", "example");
119
120
    String url = "http://${my.hostname}.org";
121
    String expected = "http://example.org";
122
    String actual = configurator.substituteParameters(url);
123
124
    assertEquals("Parameter should have been substituted", expected, actual);
125
  }
126
127
  /**
128
   * Tests the layered configurator in the development configuration.  This should
129
   * run all the way through debug to fatal.
130
   *
131
   * @throws MalformedURLException
132
   */
133
  public final void testDevelopmentConfiguration() throws MalformedURLException {
134
135
    // Run through the log4j configuration using the development settings.
136
    // The LogManager has all of the initialization logic tied up in a
137
    // static method, so we have to set things up so that we can switch
138
    // it out on the fly.
139
140
    // Set the property so it will resolve to "dev-loggers.properties"
141
    System.setProperty("my.environment", "dev");
142
143
    URL url = Loader
144
        .getResource("org/apache/log4j/spi/log4j-config.properties");
145
    configurator.doConfigure(url, repository);
146
147
    // Set up the log manager to have the appropriate repository selector...
148
    RepositorySelector selector = new DefaultRepositorySelector(repository);
149
150
    LogManager.setRepositorySelector(selector, GUARD);
151
152
    // Call up a logger, and run it through everything.
153
    String categoryClass = "com.tersesystems.log4j";
154
    Logger logger = LogManager.getLogger(categoryClass);
155
156
    // Look at the appender and see if we got all the messages we expect.
157
    MockAppender appender = MockAppender.getMockAppender();
158
    appender.reset();
159
160
    final String debugMessage = "Debug Message";
161
    final String infoMessage = "Info message";
162
    final String warnMessage = "Warning message";
163
    final String errorMessage = "Error Message";
164
    final String fatalMessage = "Fatal message";
165
166
    appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.DEBUG, debugMessage, null));
167
    appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.INFO, infoMessage, null));
168
    appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.WARN, warnMessage, null));
169
    appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.ERROR, errorMessage, null));
170
    appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.FATAL, fatalMessage, null));
171
172
    appender.replay();
173
    logger.debug(debugMessage);
174
    logger.info(infoMessage);
175
    logger.warn(warnMessage);
176
    logger.error(errorMessage);
177
    logger.fatal(fatalMessage);
178
    appender.verify();
179
  }
180
181
  /**
182
   * Tests the layered configurator in the development configuration.  This should
183
   * run all the way through debug to fatal.
184
   *
185
   * @throws MalformedURLException
186
   */
187
  public final void testProductionConfiguration() throws MalformedURLException {
188
189
    // Run through the log4j configuration using the development settings.
190
    // The LogManager has all of the initialization logic tied up in a
191
    // static method, so we have to set things up so that we can switch
192
    // it out on the fly.
193
194
    // Set the property so it will resolve to "prod-loggers.properties"
195
    System.setProperty("my.environment", "prod");
196
197
    URL url = Loader
198
        .getResource("org/apache/log4j/spi/log4j-config.properties");
199
    configurator.doConfigure(url, repository);
200
201
    // Set up the log manager to have the appropriate repository selector...
202
    RepositorySelector selector = new DefaultRepositorySelector(repository);
203
204
    LogManager.setRepositorySelector(selector, GUARD);
205
206
    // Call up a logger, and run it through everything.
207
    String categoryClass = "com.tersesystems.log4j";
208
    Logger logger = LogManager.getLogger(categoryClass);
209
210
    // Look at the appender and see if we got all the messages we expect.
211
    MockAppender appender = MockAppender.getMockAppender();
212
    appender.reset();
213
214
    final String debugMessage = "Debug Message";
215
    final String infoMessage = "Info message";
216
    final String warnMessage = "Warning message";
217
    final String errorMessage = "Error Message";
218
    final String fatalMessage = "Fatal message";
219
220
    // Only expect WARN and above to actually be appended.
221
    appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.WARN, warnMessage, null));
222
    appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.ERROR, errorMessage, null));
223
    appender.expectEvent(new LoggingEvent(categoryClass, logger, Level.FATAL, fatalMessage, null));
224
225
    // Play through everything, including the (disabled) debug and info messages.
226
    appender.replay();
227
    logger.debug(debugMessage);
228
    logger.info(infoMessage);
229
    logger.warn(warnMessage);
230
    logger.error(errorMessage);
231
    logger.fatal(fatalMessage);
232
    appender.verify();
233
  }
234
}
(-)D:/home/wsargent/javawork/layered-configurator-extra/src/test/java/org/apache/log4j/spi/ConfiguratorFactoryTest.java (+93 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
package org.apache.log4j.spi;
19
20
import java.net.URL;
21
22
import org.apache.log4j.PropertyConfigurator;
23
import org.apache.log4j.xml.DOMConfigurator;
24
25
import junit.framework.TestCase;
26
27
/**
28
 * Tests the configurator factory.
29
 *
30
 * @author Will Sargent
31
 * @see org.apache.log4j.spi.ConfiguratorFactory
32
 */
33
public class ConfiguratorFactoryTest extends TestCase {
34
35
  private ConfiguratorFactory configuratorFactory;
36
37
  protected void setUp() throws Exception {
38
    super.setUp();
39
    configuratorFactory = new ConfiguratorFactory();
40
  }
41
42
  /**
43
   * Tests adding a fake configurator.
44
   * @throws IllegalAccessException
45
   * @throws InstantiationException
46
   */
47
  public final void testAddConfigurator() throws InstantiationException, IllegalAccessException {
48
    configuratorFactory.addConfigurator("yaml", YamlConfigurator.class);
49
    Configurator actual = configuratorFactory.findConfiguratorByType("yaml");
50
51
    assertTrue("Expected yaml configurator here", actual instanceof YamlConfigurator);
52
  }
53
54
  /**
55
   * Checks that we can get a properties configurator.
56
   */
57
  public final void testGetPropertiesConfigurator() {
58
    Configurator actual = configuratorFactory
59
        .getConfigurator("log4j.properties");
60
    assertTrue("Expected a property configurator",
61
        actual instanceof PropertyConfigurator);
62
  }
63
64
  /**
65
   * Tests getting the XML configurator.
66
   */
67
  public final void testGetDOMConfigurator() {
68
    Configurator actual = configuratorFactory.getConfigurator("log4j.xml");
69
    assertTrue("Expected a property configurator",
70
        actual instanceof DOMConfigurator);
71
  }
72
73
  /**
74
   * Tests that an exception is thrown if we don't find a configurator.
75
   */
76
  public final void testGetMissingConfigurator() {
77
    try {
78
      configuratorFactory.getConfigurator("log4j.yaml");
79
      fail("An illegal state exception should have been thrown");
80
    } catch (IllegalStateException e) {
81
    }
82
  }
83
84
  /**
85
   * A fake YAML configurator to test things out.
86
   */
87
  static class YamlConfigurator implements Configurator {
88
89
    public void doConfigure(URL url, LoggerRepository repository) {
90
      ; // do nothing
91
    }
92
  }
93
}
(-)D:/home/wsargent/javawork/layered-configurator-extra/src/test/resources/org/apache/log4j/spi/prod-loggers.properties (+2 lines)
Line 0 Link Here
1
# Define loggers with production properties
2
log4j.logger.com.tersesystems.log4j=WARN
(-)D:/home/wsargent/javawork/layered-configurator-extra/src/test/resources/org/apache/log4j/spi/base-appenders.xml (+45 lines)
Line 0 Link Here
1
<?xml version="1.0" encoding="UTF-8" ?>
2
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/docs/api/org/apache/log4j/xml/log4j.dtd">
3
4
<!--
5
/*
6
 * Licensed to the Apache Software Foundation (ASF) under one or more
7
 * contributor license agreements.  See the NOTICE file distributed with
8
 * this work for additional information regarding copyright ownership.
9
 * The ASF licenses this file to You under the Apache License, Version 2.0
10
 * (the "License"); you may not use this file except in compliance with
11
 * the License.  You may obtain a copy of the License at
12
 *
13
 *      http://www.apache.org/licenses/LICENSE-2.0
14
 *
15
 * Unless required by applicable law or agreed to in writing, software
16
 * distributed under the License is distributed on an "AS IS" BASIS,
17
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
 * See the License for the specific language governing permissions and
19
 * limitations under the License.
20
 */
21
 -->
22
23
<!-- The basic appender that gets used in every situation -->
24
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
25
26
  <!--
27
    Write all events to the mock appender.
28
  -->
29
  <appender name="mock" class="org.apache.log4j.spi.MockAppender">
30
    <param name="Threshold" value="DEBUG" />
31
    <layout class="org.apache.log4j.PatternLayout">
32
      <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
33
    </layout>
34
  </appender>
35
36
  <!--
37
     Tell the root logger that we want a default priority of INFO,
38
     and we want to log to the console and to the asynchronous appender.
39
  -->
40
  <root>
41
    <priority value="INFO" />
42
    <appender-ref ref="mock"/>
43
  </root>
44
45
</log4j:configuration>
(-)D:/home/wsargent/javawork/layered-configurator-extra/src/test/resources/org/apache/log4j/spi/dev-loggers.properties (+2 lines)
Line 0 Link Here
1
# Define loggers with development settings.
2
log4j.logger.com.tersesystems.log4j=DEBUG
(-)D:/home/wsargent/javawork/layered-configurator-extra/src/test/resources/org/apache/log4j/spi/log4j-config.properties (+9 lines)
Line 0 Link Here
1
# This file contains the XML and properties files to load, in order.
2
# It is used by the layered configurator.
3
4
# The complicated appenders are rendered in XML.
5
org/apache/log4j/spi/base-appenders.xml
6
7
# The logging levels can be defined in properties, and can use system properties
8
# as parameters.
9
org/apache/log4j/spi/${my.environment}-loggers.properties
(-)D:/home/wsargent/javawork/layered-configurator-extra/src/main/java/org/apache/log4j/spi/LayeredConfigurator.java (+387 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
package org.apache.log4j.spi;
19
20
import java.io.BufferedReader;
21
import java.io.IOException;
22
import java.io.InputStream;
23
import java.io.InputStreamReader;
24
import java.net.MalformedURLException;
25
import java.net.URL;
26
import java.text.ParseException;
27
import java.util.ArrayList;
28
import java.util.Iterator;
29
import java.util.List;
30
31
import org.apache.log4j.LogManager;
32
import org.apache.log4j.helpers.Loader;
33
import org.apache.log4j.helpers.LogLog;
34
import org.apache.log4j.helpers.OptionConverter;
35
36
/**
37
 * This class will go through a properties files with references to DOM or
38
 * Properties configurator file, and call the appropriate configurator in the
39
 * order they are defined.
40
 * <p>
41
 * This doesn't mean much in theory, so let's explain why this class exists, and
42
 * give an example.
43
 * <p>
44
 * Log4J lacks a way to spread configuration over several files. You have one
45
 * configuration file, either log4j.properties or log4j.xml, and that's it.
46
 * <p>
47
 * In projects where you have several environments to keep track of, you may
48
 * want to have some loggers set to DEBUG in the development environment, but
49
 * set to WARN on the production environment. However, you usually want to keep
50
 * all the appenders and logging infrastructure the same.
51
 * <p>
52
 * Because Log4J defines appenders and loggers in the same file, you either have
53
 * to define several almost identical files with the appropriate changes for the
54
 * environment, or you have to have an Ant script that goes through and replaces
55
 * tokens for the appropriate environment.
56
 * <p>
57
 * The file syntax for the configurators is simple. Here's an example
58
 * log4j-config.properties file:
59
 *
60
 * <pre>
61
 * # The complicated appenders are rendered in XML.
62
 * base-appenders.xml
63
 *
64
 * # The logging levels can be defined in properties, and can use system properties
65
 * # as parameters.
66
 * ${my.environment}-loggers.properties
67
 * </pre>
68
 *
69
 * <p>
70
 * The class is called LayeredConfigurator because it's meant to work in layers.
71
 * The configurators are not reset, so all the settings from the previous
72
 * configurator will still apply unless you override them.
73
 * <p>
74
 * When you define <code>-Dmy.environment=dev</code>, then your development
75
 * settings will be loaded. When you define <code>-Dmy.environment=prod</code>,
76
 * then your production settings will be loaded. Either way, your binary
77
 * distribution is exactly the same, with only the environment specific
78
 * properties
79
 * <p>
80
 * To enable this class, you must start the JVM with the following system
81
 * properties:
82
 *
83
 * <pre>
84
 * -Dlog4j.configuratorClass=com.tersesystems.log4j.LayeredConfigurator
85
 * -Dlog4j.configuration=log4j-config.properties
86
 * </pre>
87
 *
88
 * <p>
89
 * This class has been written using Java 1.3 syntax, lots of internal methods,
90
 * and with the internal methods protected, so that it can be subclassed and
91
 * modified as needed.
92
 *
93
 * @author Will Sargent
94
 *
95
 * @see org.apache.log4j.spi.Configurator
96
 * @see org.apache.log4j.spi.ConfiguratorFactory
97
 */
98
public class LayeredConfigurator implements Configurator {
99
100
  protected static final String PARAMETER_START = "${";
101
102
  protected static final String PARAMETER_END = "}";
103
104
  protected static final String COMMENT = "#";
105
106
  /**
107
   * A static version of {@link #doConfigure(URL, LoggerRepository)}.
108
   */
109
  public static void configure(URL url) {
110
    new LayeredConfigurator()
111
        .doConfigure(url, LogManager.getLoggerRepository());
112
  }
113
114
  /*
115
   * (non-Javadoc)
116
   *
117
   * @see org.apache.log4j.spi.Configurator#doConfigure(java.net.URL,
118
   *      org.apache.log4j.spi.LoggerRepository)
119
   */
120
  public void doConfigure(URL configURL, LoggerRepository repository) {
121
    LogLog.debug("Reading configuration from URL " + configURL);
122
123
    if (configURL == null) {
124
      throw new IllegalArgumentException("null configURL");
125
    }
126
127
    if (repository == null) {
128
      throw new IllegalArgumentException("null repository");
129
    }
130
131
    InputStream istream = null;
132
    try {
133
      istream = configURL.openStream();
134
      doConfigure(istream, repository);
135
    } catch (Exception e) {
136
      LogLog.error("Could not read configuration file from URL [" + configURL
137
          + "].", e);
138
      LogLog.error("Ignoring configuration file [" + configURL + "].");
139
      return;
140
    }
141
  }
142
143
  /**
144
   * Converts the input stream into a reader and then passes to the reader
145
   * method.
146
   *
147
   * @param istream
148
   * @param repository
149
   * @throws IOException
150
   * @throws ParseException
151
   */
152
  protected void doConfigure(InputStream istream, LoggerRepository repository)
153
      throws IOException, ParseException {
154
    if (istream == null) {
155
      return;
156
    }
157
158
    BufferedReader reader = new BufferedReader(new InputStreamReader(istream));
159
    List configurationReferences = parseConfigurationList(reader);
160
161
    LogLog.debug("list = " + configurationReferences);
162
163
    doConfigure(configurationReferences, repository);
164
  }
165
166
  /**
167
   * Parses the lines of text from the reader. This method deals with the
168
   * opening and closing of IO handles.
169
   *
170
   * @param reader
171
   *                the reader to pull in the file.
172
   * @return a list containing the parsed strings.
173
   */
174
  protected List parseConfigurationList(BufferedReader reader)
175
      throws IOException, ParseException {
176
    if (reader == null) {
177
      throw new IllegalArgumentException("null reader");
178
    }
179
180
    List configurationList = new ArrayList();
181
    String line = null;
182
    try {
183
      while ((line = reader.readLine()) != null) {
184
        String parsedLine = parseLine(line);
185
        if (parsedLine != null) {
186
          configurationList.add(parsedLine);
187
        }
188
      }
189
    } finally {
190
      reader.close();
191
    }
192
    return configurationList;
193
  }
194
195
  /**
196
   * See if the line starts with #. If it does, then it's a comment.
197
   *
198
   * We don't assume that anything after # can be a comment, because there's
199
   * always the possibility that someone has factored in a URL with a bookmark.
200
   *
201
   * This method does not do any substitution of variables; we want to make sure
202
   * we can parse everything before we move any further.
203
   *
204
   * @throws ParseException
205
   *                 if the line cannot be parsed.
206
   */
207
  protected String parseLine(String rawLine) throws ParseException {
208
    // If nothing, then nothing.
209
    if (rawLine == null) {
210
      return null;
211
    }
212
213
    String line = rawLine;
214
    String trimmedLine = line.trim();
215
216
    // Ignore a completely empty line.
217
    if (trimmedLine.length() == 0) // NOPMD
218
    {
219
      return null;
220
    }
221
222
    // If the line contains nothing but whitespace and then "#" then we
223
    // consider it a comment.
224
    if (trimmedLine.startsWith(COMMENT)) {
225
      return null;
226
    }
227
228
    // If the line contains "${" but not "}" then throw an exception.
229
    int fromIndex = line.indexOf(PARAMETER_START);
230
    if (fromIndex != -1) {
231
      int endIndex = line.indexOf(PARAMETER_END, fromIndex + 1);
232
      if (endIndex == -1) {
233
        throw new ParseException("Cannot parse line: " + line, fromIndex);
234
      }
235
236
      // For simplicity's sake, let's not nest.
237
      int newStartIndex = line.indexOf(PARAMETER_START, fromIndex + 1);
238
      if (newStartIndex != -1 && newStartIndex < endIndex) {
239
        throw new ParseException("Cannot parse line: " + line, newStartIndex);
240
      }
241
    }
242
243
    return rawLine;
244
  }
245
246
  /**
247
   * Returns a configurator factory.
248
   *
249
   * @return a new configurator factory.
250
   */
251
  protected ConfiguratorFactory getConfiguratorFactory() {
252
    return new ConfiguratorFactory();
253
  }
254
255
  /**
256
   * Configures a list of configuration references, substituting parameters as
257
   * needed.
258
   *
259
   * @param configurationReferences
260
   * @param repository
261
   */
262
  protected void doConfigure(List configurationReferences,
263
      LoggerRepository repository) {
264
    // Run through any substitutions of parameters. Parameters are defined
265
    // in the syntax ${parameter}. They must resolve to strings.
266
    List parsedReferences = substituteListParameters(configurationReferences);
267
268
    ConfiguratorFactory configuratorFactory = getConfiguratorFactory();
269
270
    // Go through the list of references, using a configurator factory to
271
    // pull out the appropriate configurator.
272
    for (Iterator iter = parsedReferences.iterator(); iter.hasNext();) {
273
      String reference = (String) iter.next();
274
      Configurator c = configuratorFactory.getConfigurator(reference);
275
276
      URL url;
277
      try {
278
        url = new URL(reference);
279
      } catch (MalformedURLException ex) {
280
        // so, resource is not a URL:
281
        // attempt to get the resource from the class path
282
        url = Loader.getResource(reference);
283
      }
284
285
      // The internal reference doesn't resolve to anything either...
286
      if (url == null) {
287
        String msg = "The internal reference " + reference + " does not resolve to a resource";
288
        throw new IllegalArgumentException(msg);
289
      }
290
291
      // Tell the configurator to go do its stuff.
292
      c.doConfigure(url, repository);
293
    }
294
  }
295
296
  /**
297
   * Goes through a list of configuration references that may contain
298
   * ${parameter} in the string, and resolves the parameters.
299
   *
300
   * @param configurationReferences
301
   *                a list of configuration references with parameters.
302
   * @return a list of configuration files with the parameters replaced by text.
303
   */
304
  protected List substituteListParameters(List configurationReferences) {
305
    List resolvedList = new ArrayList();
306
    for (Iterator iter = configurationReferences.iterator(); iter.hasNext();) {
307
      String configRef = (String) iter.next();
308
      String resolvedRef = substituteParameters(configRef);
309
      resolvedList.add(resolvedRef);
310
    }
311
312
    return resolvedList;
313
  }
314
315
  /**
316
   * Substitute all the parameters in a reference.
317
   *
318
   * @param configRef
319
   * @return a reference with all the parameters replaced by text.
320
   */
321
  protected String substituteParameters(String configRef) {
322
    // If I assume Java 1.4 or 1.5, this could be much easier. But let's do
323
    // it the old fashioned way.
324
325
    // state can be 1 {dollar), 2 (parsing), or 0 (raw).
326
    // We assume that the string cannot be malformed here.
327
    int state = 0;
328
    StringBuffer line = new StringBuffer();
329
    StringBuffer parameterName = new StringBuffer();
330
    byte[] characters = configRef.getBytes();
331
    for (int i = 0; i < characters.length; i++) {
332
      char ch = (char) characters[i];
333
      switch (ch) {
334
      case '$':
335
        state = 1;
336
        break;
337
338
      case '{':
339
        if (state == 1) {
340
          state = 2;
341
          parameterName.setLength(0); // clear the param name.
342
        }
343
        break;
344
345
      case '}':
346
        state = 0;
347
        // look up the parameter name, and append to the line.
348
        String parameterValue = substituteParameter(parameterName.toString());
349
        line.append(parameterValue);
350
        break;
351
352
      default:
353
        if (state == 0) // a raw char
354
        {
355
          line.append(ch);
356
        } else if (state == 1) // just a raw dollar
357
        {
358
          state = 0;
359
          line.append(ch);
360
        } else if (state == 2) // this is a parameter name
361
        {
362
          parameterName.append(ch);
363
        }
364
      }
365
    }
366
367
    return line.toString();
368
  }
369
370
  /**
371
   * Substitutes a single parameter. A null value will be replaced by the empty
372
   * string.
373
   *
374
   * @param parameterName
375
   *                the parameter's name.
376
   * @return the parameter value.
377
   */
378
  protected String substituteParameter(String parameterName) {
379
    LogLog.debug("substituteParameter: found " + parameterName);
380
381
    String parameterValue = OptionConverter
382
        .getSystemProperty(parameterName, "");
383
    LogLog.debug("substituteParameter: replacing " + parameterName + " with "
384
        + parameterValue);
385
    return parameterValue;
386
  }
387
}
(-)D:/home/wsargent/javawork/layered-configurator-extra/src/main/java/org/apache/log4j/spi/ConfiguratorFactory.java (+150 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
package org.apache.log4j.spi;
19
20
import java.util.HashMap;
21
import java.util.Map;
22
23
import org.apache.log4j.PropertyConfigurator;
24
import org.apache.log4j.spi.Configurator;
25
import org.apache.log4j.xml.DOMConfigurator;
26
27
/**
28
 * This is a simple class to abstract the instantation of a configurator.
29
 * <p>
30
 * The factory contains a map of types, along with the configurator objects that
31
 * will be used for those types. The factory is configured for references ending
32
 * in ".properties" or ".xml", but other configurators can be added as need be.
33
 * <p>
34
 * This class may or may not be thread-safe. No attempt has been made to ensure
35
 * thread-safety.
36
 *
37
 * @author Will Sargent
38
 * @see org.apache.log4j.PropertyConfigurator
39
 * @see org.apache.log4j.xml.DOMConfigurator
40
 */
41
public class ConfiguratorFactory {
42
43
  /** The string that will return a Properties Configurator. */
44
  public static final String PROPERTIES_TYPE = "properties";
45
46
  /** The string that will return a DOM configurator. */
47
  public static final String XML_TYPE = "xml";
48
49
  /**
50
   * A map of types to configurator instances.
51
   */
52
  private Map configuratorMap = new HashMap();
53
54
  /**
55
   * A default constructor that will call the initialize() method.
56
   */
57
  public ConfiguratorFactory() {
58
    initialize();
59
  }
60
61
  /**
62
   * Initializes the configurator map.
63
   */
64
  protected void initialize() {
65
    addConfigurator(PROPERTIES_TYPE, PropertyConfigurator.class);
66
    addConfigurator(XML_TYPE, DOMConfigurator.class);
67
  }
68
69
  /**
70
   * Adds a configurator class with the given type.
71
   *
72
   * @param type
73
   *          the type of configurator to use.
74
   * @param configuratorClass
75
   *          the configurator class. Must take a no-args constructor.
76
   */
77
  public void addConfigurator(String type, Class configuratorClass) {
78
    if (type == null) {
79
      throw new IllegalArgumentException("Null type");
80
    }
81
82
    if (configuratorClass == null) {
83
      throw new IllegalArgumentException("Null configurator");
84
    }
85
86
    configuratorMap.put(type, configuratorClass);
87
  }
88
89
  /**
90
   * Given a reference to a configurator, returns the appropriate configurator
91
   * instance. For example, if the reference is "log4j.properties", then it has
92
   * the "properties" type and a PropertyConfigurator is used.
93
   *
94
   * @param reference
95
   *          a reference to a name (typically a file or classpath reference)
96
   * @return the appropriate Configurator object for the reference.
97
   * @throw IllegalStateException if no configurator is found, or the
98
   *        configurator could not be instantiated.
99
   */
100
  public Configurator getConfigurator(String reference) {
101
    if (reference == null) {
102
      throw new IllegalArgumentException("Null reference found.");
103
    }
104
105
    // Find the last index of dot. We'll use this as a lookup table.
106
    int lastIndex = reference.lastIndexOf('.');
107
108
    // No dot found.
109
    if (lastIndex == -1) {
110
      throw new IllegalArgumentException("No dot suffix found");
111
    }
112
113
    String suffix = reference.substring(lastIndex + 1);
114
    if (suffix.length() == 0) {
115
      throw new IllegalArgumentException("Null or empty suffix found.");
116
    }
117
118
    // Get the configurator.
119
    try {
120
      Configurator c = findConfiguratorByType(suffix);
121
      return c;
122
    } catch (InstantiationException ie) {
123
      String msg = "Could not instantiate configurator from reference " + reference;
124
      throw new IllegalStateException(msg, ie);
125
    } catch (IllegalAccessException iae) {
126
      String msg = "Could not instantiate configurator from reference " + reference;
127
      throw new IllegalStateException(msg, iae);
128
    }
129
  }
130
131
  /**
132
   * Instantiates a new configurator given the appropriate type.
133
   *
134
   * @param type
135
   *          the type of configurator to use.
136
   * @return the configurator object for that type.
137
   * @throws IllegalAccessException
138
   * @throws InstantiationException
139
   * @throw IllegalStateException if no configurator is found for the reference.
140
   */
141
  protected Configurator findConfiguratorByType(String type)
142
      throws InstantiationException, IllegalAccessException {
143
    Class configuratorClass = (Class) configuratorMap.get(type);
144
    if (configuratorClass == null) {
145
      throw new IllegalStateException("No configurator class found for type "
146
          + type);
147
    }
148
    return (Configurator) configuratorClass.newInstance();
149
  }
150
}

Return to bug 43820