001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package com.randomnoun.common.log4j2;
018
019import java.beans.BeanInfo;
020import java.beans.IntrospectionException;
021import java.beans.Introspector;
022import java.beans.PropertyDescriptor;
023import java.io.IOException;
024import java.io.InputStream;
025import java.io.StringWriter;
026import java.util.Arrays;
027import java.util.HashMap;
028import java.util.Map;
029import java.util.Map.Entry;
030import java.util.Objects;
031import java.util.Properties;
032import java.util.TreeMap;
033
034import org.apache.logging.log4j.Level;
035import org.apache.logging.log4j.core.appender.ConsoleAppender;
036import org.apache.logging.log4j.core.appender.FileAppender;
037import org.apache.logging.log4j.core.appender.NullAppender;
038import org.apache.logging.log4j.core.appender.RollingFileAppender;
039import org.apache.logging.log4j.core.config.ConfigurationException;
040import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
041import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder;
042import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
043import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
044import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder;
045import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder;
046import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder;
047import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
048import org.apache.logging.log4j.core.config.plugins.Plugin;
049import org.apache.logging.log4j.core.lookup.StrSubstitutor;
050import org.apache.logging.log4j.status.StatusLogger;
051import org.apache.logging.log4j.util.Strings;
052
053/**
054 * You know how log4j 1 can be configured via XML or property files, and
055 * how log4j 2 can be configured via maliciously and arbitrarily different XML or property files ?
056 * 
057 * <p>This class allows you to configure log4j2 using log4j1-style property configuration files,
058 * which allows you to slowly migrate your clusterfuck of logging frameworks from 
059 * log4j1 to log4j2 without having to redo your entire logging configuration, which by
060 * now is Turing-complete and giving recipe suggestions.
061 * 
062 * <p>It's based on the org.apache.log4j.config.Log4j1ConfigurationParser that's bundled in the 
063 * log4j-1.2-api artifact ( specifically, org.apache.logging.log4j:log4j-1.2-api:2.14.0 ), but:
064 * 
065 * <ul><li>buildConfigurationBuilder() can take a Properties object (as well as an InputStream)
066 *     <li>Unknown log4j1 appenders are constructed (and wrapped in some kind of log4j2 adapter) instead of ignored
067 *     <li>Unknown log4j2 appenders are constructed instead of ignored
068 * </ul> 
069 *
070 * <p>The original Log4j1ConfigurationParser also converts a handful of "well-known" appenders
071 * into their log4j2 equivalents, which still happens in this class.
072 *
073 * <p>To use, run the following:
074 * <pre>
075Log4j1ConfigurationParser lcp = new Log4j1ConfigurationParser();
076ConfigurationBuilder&lt;?&gt; builder = lcp.buildConfigurationBuilder(props);
077Configuration config = builder.build();
078Configurator.reconfigure(config); 
079 * </pre>
080 *
081 * Original comment block:
082 * 
083 * Experimental parser for Log4j 1.2 properties configuration files.
084 *
085 * This class is not thread-safe.
086 * 
087 * <p>
088 * From the Log4j 1.2 Javadocs:
089 * </p>
090 * <p>
091 * All option values admit variable substitution. The syntax of variable substitution is similar to that of Unix shells. The string between
092 * an opening "${" and closing "}" is interpreted as a key. The value of the substituted variable can be defined as a system property or in
093 * the configuration file itself. The value of the key is first searched in the system properties, and if not found there, it is then
094 * searched in the configuration file being parsed. The corresponding value replaces the ${variableName} sequence. For example, if java.home
095 * system property is set to /home/xyz, then every occurrence of the sequence ${java.home} will be interpreted as /home/xyz.
096 * </p>
097 * 
098 * <p>Changes from the Log4j1ConfigurationParser in org.apache.logging.log4j:log4j-1.2-api:2.14.0
099 * <ul><li>buildConfigurationBuilder() can take a Properties object (as well as an InputStream)
100 *     <li>Unknown log4j2 appenders are constructed instead of ignored
101 * </ul> 
102 */
103public class Log4j1ConfigurationParser {
104
105    private static final String COMMA_DELIMITED_RE = "\\s*,\\s*";
106    private static final String ROOTLOGGER = "rootLogger";
107    private static final String ROOTCATEGORY = "rootCategory";
108    private static final String TRUE = "true";
109    private static final String FALSE = "false";
110
111    private final Properties properties = new Properties();
112    private StrSubstitutor strSubstitutorProperties;
113    private StrSubstitutor strSubstitutorSystem;
114
115    private final ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory
116            .newConfigurationBuilder();
117
118    /**
119     * Parses a Log4j 1.2 properties configuration file in ISO 8859-1 encoding into a ConfigurationBuilder.
120     *
121     * @param input
122     *            InputStream to read from is assumed to be ISO 8859-1, and will not be closed.
123     * @return the populated ConfigurationBuilder, never {@literal null}
124     * @throws IOException
125     *             if unable to read the input
126     * @throws ConfigurationException
127     *             if the input does not contain a valid configuration
128     */
129    public ConfigurationBuilder<BuiltConfiguration> buildConfigurationBuilder(final InputStream input)
130        throws IOException 
131    {
132        try {
133                properties.load(input);
134                _buildConfigurationBuilder();
135                return builder;
136        } catch (final IllegalArgumentException e) {
137            throw new ConfigurationException(e);
138        }
139                
140    }
141    
142    public ConfigurationBuilder<BuiltConfiguration> buildConfigurationBuilder(Properties properties) {
143        try {
144                this.properties.putAll(properties);
145                _buildConfigurationBuilder();
146                return builder;
147        } catch (final IllegalArgumentException e) {
148            throw new ConfigurationException(e);
149        }
150    }
151
152    protected ConfigurationBuilder<BuiltConfiguration> _buildConfigurationBuilder() {
153        try {
154            strSubstitutorProperties = new StrSubstitutor(properties);
155            strSubstitutorSystem = new StrSubstitutor(System.getProperties());
156            final String rootCategoryValue = getLog4jValue(ROOTCATEGORY);
157            final String rootLoggerValue = getLog4jValue(ROOTLOGGER);
158            if (rootCategoryValue == null && rootLoggerValue == null) {
159                // This is not a Log4j 1 properties configuration file.
160                warn("Missing " + ROOTCATEGORY + " or " + ROOTLOGGER + " in properties");
161                // throw new ConfigurationException(
162                // "Missing " + ROOTCATEGORY + " or " + ROOTLOGGER + " in " + input);
163            }
164            builder.setConfigurationName("Log4j1");
165            // DEBUG
166            final String debugValue = getLog4jValue("debug");
167            if (Boolean.valueOf(debugValue)) {
168                builder.setStatusLevel(Level.DEBUG);
169            }
170            // Root
171            buildRootLogger(getLog4jValue(ROOTCATEGORY));
172            buildRootLogger(getLog4jValue(ROOTLOGGER));
173            // Appenders
174            final Map<String, String> appenderNameToClassName = buildClassToPropertyPrefixMap();
175            for (final Map.Entry<String, String> entry : appenderNameToClassName.entrySet()) {
176                final String appenderName = entry.getKey();
177                final String appenderClass = entry.getValue();
178                buildAppender(appenderName, appenderClass);
179            }
180            // Loggers
181            buildLoggers("log4j.category.");
182            buildLoggers("log4j.logger.");
183            buildProperties();
184            return builder;
185        } catch (final IllegalArgumentException e) {
186            throw new ConfigurationException(e);
187        }
188    }
189
190    
191    private void buildProperties() {
192        for (final Map.Entry<Object, Object> entry : new TreeMap<Object, Object>(properties).entrySet()) {
193            final String key = entry.getKey().toString();
194            if (!key.startsWith("log4j.") && !key.equals(ROOTCATEGORY) && !key.equals(ROOTLOGGER)) {
195                builder.addProperty(key, Objects.toString(entry.getValue(), Strings.EMPTY));
196            }
197        }
198    }
199
200    private void warn(final String string) {
201        System.err.println(string);
202    }
203
204    private Map<String, String> buildClassToPropertyPrefixMap() {
205        final String prefix = "log4j.appender.";
206        final int preLength = prefix.length();
207        final Map<String, String> map = new HashMap<String, String>();
208        for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
209            final Object keyObj = entry.getKey();
210            if (keyObj != null) {
211                final String key = keyObj.toString();
212                if (key.startsWith(prefix)) {
213                    if (key.indexOf('.', preLength) < 0) {
214                        final String name = key.substring(preLength);
215                        final Object value = entry.getValue();
216                        if (value != null) {
217                            map.put(name, value.toString());
218                        }
219                    }
220                }
221            }
222        }
223        return map;
224    }
225
226    private void buildAppender(final String appenderName, final String appenderClass) {
227        if ("org.apache.log4j.ConsoleAppender".equals(appenderClass)) {
228            buildConsoleAppender(appenderName);
229        } else if ("org.apache.log4j.FileAppender".equals(appenderClass)) {
230            buildFileAppender(appenderName);
231        } else if ("org.apache.log4j.DailyRollingFileAppender".equals(appenderClass)) {
232            buildDailyRollingFileAppender(appenderName);
233        } else if ("org.apache.log4j.RollingFileAppender".equals(appenderClass)) {
234            buildRollingFileAppender(appenderName);
235        } else if ("org.apache.log4j.varia.NullAppender".equals(appenderClass)) {
236            buildNullAppender(appenderName);
237        } else {
238                // old Log4j1ConfigurationParser did this:
239            //   reportWarning("Unknown appender class: " + appenderClass + "; ignoring appender: " + appenderName);
240                // which isn't helpful
241                
242                Class<?> c;
243                try {
244                                c = Class.forName(appenderClass);
245                        } catch (ClassNotFoundException e) {
246                                throw new ConfigurationException("Cannot find appender class '" + appenderClass + "'", e);
247                        }
248                // org.apache.log4j.ConsoleAppender c1;
249                if (org.apache.log4j.Appender.class.isAssignableFrom(c)) {
250                        /* log4j1 appender, use an adapter once I fathom how that's supposed to work */
251                        // throw new ConfigurationException("Cannot handle log4j1 appender class '" + appenderClass + "'"); 
252                        
253                        /*
254                        // construct the log4j1 appender. this is soooooooooooo going to work.
255                final String prefix = "log4j.appender." + appenderName;
256                final String layoutPrefix = "log4j.appender." + appenderName + ".layout";
257                final String filterPrefix = "log4j.appender." + appenderName + ".filter.";
258                final org.apache.log4j.Appender log4j1Appender = buildLog4j1Appender(appenderName, c.getName(), prefix, layoutPrefix, filterPrefix, properties);
259                        
260                // this should work but doesn't because DefaultComponentBuilder.addAttribute() converts every value it receives into a string, 
261                // when then has to be converted back into an object using a TypeConverter (which needs it's own TypeConverter registry),
262                // and a log4j1 appender won't round-trip through that properly.
263
264                                AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, Log4j1WrapperAppender.PLUGIN_NAME);
265                                appenderBuilder.addAttribute("log4j1Appender", log4j1Appender);
266                                builder.add(appenderBuilder);
267                                // ^ the attribute collection in that builder is a Map<String, String>
268                
269                // so not only that, you can't create your own "non-default" DefaultComponentBuilder 
270                // because the Components that these builders are building are also composed of Strings.
271                // I suppose you could create a Component which consists of the entire property file that we're configuring this with
272                // and then extract the appender properties out of that, but frankly, this is the point at which I'm giving up.
273                        */
274                        
275                        // ok, so now the Log4jWrapperAppender constructs the appender, we don't construct it here
276                        
277                        StringWriter w = new StringWriter();
278                        try {
279                                        properties.store(w,  null);
280                                } catch (IOException e) {
281                                        throw new IllegalStateException("IOException in StringWriter");
282                                }
283                        
284                        AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, Log4j1WrapperAppender.PLUGIN_NAME);
285                        appenderBuilder.addAttribute("className", c.getName());
286                        appenderBuilder.addAttribute("properties", w.toString());
287                        builder.add(appenderBuilder);
288                        
289                        
290                } else if (org.apache.logging.log4j.core.Appender.class.isAssignableFrom(c)) {
291                        /* log4j2 appender, because wheels aren't going to reinvent themselves */
292
293                        // no idea what these are for or why they're necessary, if they are, which they probably aren't
294                        Plugin log4jPlugin = c.getAnnotation(org.apache.logging.log4j.core.config.plugins.Plugin.class);
295                        String pluginName = log4jPlugin.name();
296                        if (pluginName == null) {
297                                pluginName = "generatedPluginName_" + c.getName();
298                        }
299                        final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, pluginName);
300
301                        // set the props ( from "log4j.appender." + appenderName + "." + attributeName );
302                        // we're checking for getter/setter methods on the Appender to work out what's settable
303                        // even though we end up using a Builder class to construct the thing for some reason
304                                BeanInfo b;
305                                try {
306                                        b = Introspector.getBeanInfo(c);
307                                } catch (IntrospectionException e1) {
308                                        throw new ConfigurationException("Could not determine properties of appender class '" + appenderClass + "'", e1);
309                                }
310                                PropertyDescriptor[] pds = b.getPropertyDescriptors();
311                                Map<String, PropertyDescriptor> pdMap = new HashMap<String, PropertyDescriptor>();
312                                for (PropertyDescriptor pd : pds) {
313                                        pdMap.put(pd.getName().toLowerCase(), pd); // case-insensitive match 
314                                }
315                                String prefix = "log4j.appender." + appenderName + ".";
316                        for (Entry<Object, Object> e : properties.entrySet()) {
317                                String k = (String) e.getKey();
318                                String v = (String) e.getValue().toString();
319                                if (k.startsWith(prefix)) {
320                                        String rest = k.substring(prefix.length());
321                                        PropertyDescriptor pd = pdMap.get(rest.toLowerCase());
322                                        if (pd == null) {
323                                                throw new ConfigurationException("Appender class '" + appenderClass + "' has no property '" + rest + "'");
324                                        } else if (pd.getPropertyType().equals(String.class)) {
325                                                appenderBuilder.addAttribute(pd.getName(), v);
326                                        } else if (pd.getPropertyType().equals(Integer.class) || pd.getPropertyType().equals(int.class)) {
327                                                appenderBuilder.addAttribute(pd.getName(), Integer.valueOf(v).intValue());
328                                        } else {
329                                                // all the other types, but let's see if Strings work, which they won't.
330                                                appenderBuilder.addAttribute(pd.getName(), v);
331                                        }
332                                }
333                        }
334                        
335                        builder.add(appenderBuilder);
336                }
337        }
338    }
339    
340    
341    private void buildConsoleAppender(final String appenderName) {
342        final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, ConsoleAppender.PLUGIN_NAME);
343        final String targetValue = getLog4jAppenderValue(appenderName, "Target", "System.out");
344        if (targetValue != null) {
345            final ConsoleAppender.Target target;
346            if (targetValue.equals("System.out")) {
347                target = ConsoleAppender.Target.SYSTEM_OUT;
348            } else if (targetValue.equals("System.err")) {
349                target = ConsoleAppender.Target.SYSTEM_ERR;
350            } else {
351                reportWarning("Unknown value for console Target: " + targetValue);
352                target = null;
353            }
354            if (target != null) {
355                appenderBuilder.addAttribute("target", target);
356            }
357        }
358        buildAttribute(appenderName, appenderBuilder, "Follow", "follow");
359        if (FALSE.equalsIgnoreCase(getLog4jAppenderValue(appenderName, "ImmediateFlush"))) {
360            reportWarning("ImmediateFlush=false is not supported on Console appender");
361        }
362        buildAppenderLayout(appenderName, appenderBuilder);
363        builder.add(appenderBuilder);
364    }
365
366    private void buildFileAppender(final String appenderName) {
367        final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, FileAppender.PLUGIN_NAME);
368        buildFileAppender(appenderName, appenderBuilder);
369        builder.add(appenderBuilder);
370    }
371
372    private void buildFileAppender(final String appenderName, final AppenderComponentBuilder appenderBuilder) {
373        buildMandatoryAttribute(appenderName, appenderBuilder, "File", "fileName");
374        buildAttribute(appenderName, appenderBuilder, "Append", "append");
375        buildAttribute(appenderName, appenderBuilder, "BufferedIO", "bufferedIo");
376        buildAttribute(appenderName, appenderBuilder, "BufferSize", "bufferSize");
377        buildAttribute(appenderName, appenderBuilder, "ImmediateFlush", "immediateFlush");
378        buildAppenderLayout(appenderName, appenderBuilder);
379    }
380
381    private void buildDailyRollingFileAppender(final String appenderName) {
382        final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName,
383                RollingFileAppender.PLUGIN_NAME);
384        buildFileAppender(appenderName, appenderBuilder);
385        final String fileName = getLog4jAppenderValue(appenderName, "File");
386        final String datePattern = getLog4jAppenderValue(appenderName, "DatePattern", fileName + "'.'yyyy-MM-dd");
387        appenderBuilder.addAttribute("filePattern", fileName + "%d{" + datePattern + "}");
388        final ComponentBuilder<?> triggeringPolicy = builder.newComponent("Policies")
389                .addComponent(builder.newComponent("TimeBasedTriggeringPolicy").addAttribute("modulate", true));
390        appenderBuilder.addComponent(triggeringPolicy);
391        appenderBuilder
392                .addComponent(builder.newComponent("DefaultRolloverStrategy").addAttribute("max", Integer.MAX_VALUE));
393        builder.add(appenderBuilder);
394    }
395
396    private void buildRollingFileAppender(final String appenderName) {
397        final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName,
398                RollingFileAppender.PLUGIN_NAME);
399        buildFileAppender(appenderName, appenderBuilder);
400        final String fileName = getLog4jAppenderValue(appenderName, "File");
401        appenderBuilder.addAttribute("filePattern", fileName + ".%i");
402        final String maxFileSizeString = getLog4jAppenderValue(appenderName, "MaxFileSize", "10485760");
403        final String maxBackupIndexString = getLog4jAppenderValue(appenderName, "MaxBackupIndex", "1");
404        final ComponentBuilder<?> triggeringPolicy = builder.newComponent("Policies").addComponent(
405                builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", maxFileSizeString));
406        appenderBuilder.addComponent(triggeringPolicy);
407        appenderBuilder.addComponent(
408                builder.newComponent("DefaultRolloverStrategy").addAttribute("max", maxBackupIndexString));
409        builder.add(appenderBuilder);
410    }
411
412    private void buildAttribute(final String componentName, final ComponentBuilder componentBuilder,
413            final String sourceAttributeName, final String targetAttributeName) {
414        final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName);
415        if (attributeValue != null) {
416            componentBuilder.addAttribute(targetAttributeName, attributeValue);
417        }
418    }
419
420    private void buildAttributeWithDefault(final String componentName, final ComponentBuilder componentBuilder,
421            final String sourceAttributeName, final String targetAttributeName, final String defaultValue) {
422        final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName, defaultValue);
423        componentBuilder.addAttribute(targetAttributeName, attributeValue);
424    }
425
426    private void buildMandatoryAttribute(final String componentName, final ComponentBuilder componentBuilder,
427            final String sourceAttributeName, final String targetAttributeName) {
428        final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName);
429        if (attributeValue != null) {
430            componentBuilder.addAttribute(targetAttributeName, attributeValue);
431        } else {
432            reportWarning("Missing " + sourceAttributeName + " for " + componentName);
433        }
434    }
435
436    private void buildNullAppender(final String appenderName) {
437        final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, NullAppender.PLUGIN_NAME);
438        builder.add(appenderBuilder);
439    }
440
441    private void buildAppenderLayout(final String name, final AppenderComponentBuilder appenderBuilder) {
442        final String layoutClass = getLog4jAppenderValue(name, "layout", null);
443        if (layoutClass != null) {
444            if ("org.apache.log4j.PatternLayout".equals(layoutClass) ||
445                "org.apache.log4j.EnhancedPatternLayout".equals(layoutClass)) {
446                final String pattern = getLog4jAppenderValue(name, "layout.ConversionPattern", null)
447
448                        // Log4j 2's %x (NDC) is not compatible with Log4j 1's
449                        // %x
450                        // Log4j 1: "foo bar baz"
451                        // Log4j 2: "[foo, bar, baz]"
452                        // Use %ndc to get the Log4j 1 format
453                        .replace("%x", "%ndc")
454
455                        // Log4j 2's %X (MDC) is not compatible with Log4j 1's
456                        // %X
457                        // Log4j 1: "{{foo,bar}{hoo,boo}}"
458                        // Log4j 2: "{foo=bar,hoo=boo}"
459                        // Use %properties to get the Log4j 1 format
460                        .replace("%X", "%properties");
461
462                appenderBuilder.add(newPatternLayout(pattern));
463            } else if ("org.apache.log4j.SimpleLayout".equals(layoutClass)) {
464                appenderBuilder.add(newPatternLayout("%level - %m%n"));
465            } else if ("org.apache.log4j.TTCCLayout".equals(layoutClass)) {
466                String pattern = "%r ";
467                if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.ThreadPrinting", TRUE))) {
468                    pattern += "[%t] ";
469                }
470                pattern += "%p ";
471                if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.CategoryPrefixing", TRUE))) {
472                    pattern += "%c ";
473                }
474                if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.ContextPrinting", TRUE))) {
475                    pattern += "%notEmpty{%ndc }";
476                }
477                pattern += "- %m%n";
478                appenderBuilder.add(newPatternLayout(pattern));
479            
480            } else if ("org.apache.log4j.HTMLLayout".equals(layoutClass)) {
481                final LayoutComponentBuilder htmlLayout = builder.newLayout("HtmlLayout");
482                htmlLayout.addAttribute("title", getLog4jAppenderValue(name, "layout.Title", "Log4J Log Messages"));
483                htmlLayout.addAttribute("locationInfo",
484                        Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.LocationInfo", FALSE)));
485                appenderBuilder.add(htmlLayout);
486            } else if ("org.apache.log4j.xml.XMLLayout".equals(layoutClass)) {
487                final LayoutComponentBuilder xmlLayout = builder.newLayout("Log4j1XmlLayout");
488                xmlLayout.addAttribute("locationInfo",
489                        Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.LocationInfo", FALSE)));
490                xmlLayout.addAttribute("properties",
491                        Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.Properties", FALSE)));
492                appenderBuilder.add(xmlLayout);
493            } else {
494                reportWarning("Unknown layout class: " + layoutClass);
495            }
496        }
497    }
498
499    private LayoutComponentBuilder newPatternLayout(final String pattern) {
500        final LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout");
501        if (pattern != null) {
502            layoutBuilder.addAttribute("pattern", pattern);
503        }
504        return layoutBuilder;
505    }
506
507    private void buildRootLogger(final String rootLoggerValue) {
508        if (rootLoggerValue == null) {
509            return;
510        }
511        final String[] rootLoggerParts = rootLoggerValue.split(COMMA_DELIMITED_RE);
512        final String rootLoggerLevel = getLevelString(rootLoggerParts, Level.ERROR.name());
513        final RootLoggerComponentBuilder loggerBuilder = builder.newRootLogger(rootLoggerLevel);
514        //
515        final String[] sortedAppenderNames = Arrays.copyOfRange(rootLoggerParts, 1, rootLoggerParts.length);
516        Arrays.sort(sortedAppenderNames);
517        for (final String appender : sortedAppenderNames) {
518            loggerBuilder.add(builder.newAppenderRef(appender));
519        }
520        builder.add(loggerBuilder);
521    }
522
523    private String getLevelString(final String[] loggerParts, final String defaultLevel) {
524        return loggerParts.length > 0 ? loggerParts[0] : defaultLevel;
525    }
526
527    private void buildLoggers(final String prefix) {
528        final int preLength = prefix.length();
529        for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
530            final Object keyObj = entry.getKey();
531            if (keyObj != null) {
532                final String key = keyObj.toString();
533                if (key.startsWith(prefix)) {
534                    final String name = key.substring(preLength);
535                    final Object value = entry.getValue();
536                    if (value != null) {
537                        // a Level may be followed by a list of Appender refs.
538                        final String valueStr = value.toString();
539                        final String[] split = valueStr.split(COMMA_DELIMITED_RE);
540                        final String level = getLevelString(split, null);
541                        if (level == null) {
542                            warn("Level is missing for entry " + entry);
543                        } else {
544                            final LoggerComponentBuilder newLogger = builder.newLogger(name, level);
545                            if (split.length > 1) {
546                                // Add Appenders to this logger
547                                final String[] sortedAppenderNames = Arrays.copyOfRange(split, 1, split.length);
548                                Arrays.sort(sortedAppenderNames);
549                                for (final String appenderName : sortedAppenderNames) {
550                                    newLogger.add(builder.newAppenderRef(appenderName));
551                                }
552                            }
553                            builder.add(newLogger);
554                        }
555                    }
556                }
557            }
558        }
559    }
560
561    private String getLog4jAppenderValue(final String appenderName, final String attributeName) {
562        return getProperty("log4j.appender." + appenderName + "." + attributeName);
563    }
564
565    private String getProperty(final String key) {
566        final String value = properties.getProperty(key);
567        final String sysValue = strSubstitutorSystem.replace(value);
568        return strSubstitutorProperties.replace(sysValue);
569    }
570
571    private String getProperty(final String key, final String defaultValue) {
572        final String value = getProperty(key);
573        return value == null ? defaultValue : value;
574    }
575
576    private String getLog4jAppenderValue(final String appenderName, final String attributeName,
577            final String defaultValue) {
578        return getProperty("log4j.appender." + appenderName + "." + attributeName, defaultValue);
579    }
580
581    private String getLog4jValue(final String key) {
582        return getProperty("log4j." + key);
583    }
584
585    private void reportWarning(final String msg) {
586        StatusLogger.getLogger().warn("Log4j 1 configuration parser: " + msg);
587    }
588
589    
590    
591    /* *******************************************************************
592     * Everything from here onwards was rewritten from org.apache.log4j.config.PropertiesConfiguration,
593     * 
594     */ 
595
596    
597
598    
599    
600}