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 org.apache.log4j.config;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.io.OutputStream;
022import java.nio.file.FileVisitResult;
023import java.nio.file.Files;
024import java.nio.file.Path;
025import java.nio.file.SimpleFileVisitor;
026import java.nio.file.attribute.BasicFileAttributes;
027import java.util.concurrent.atomic.AtomicInteger;
028
029import org.apache.logging.log4j.core.config.ConfigurationException;
030import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
031import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
032import org.apache.logging.log4j.core.tools.BasicCommandLineArguments;
033import org.apache.logging.log4j.core.tools.picocli.CommandLine;
034import org.apache.logging.log4j.core.tools.picocli.CommandLine.Command;
035import org.apache.logging.log4j.core.tools.picocli.CommandLine.Option;
036
037/**
038 * Tool for converting a Log4j 1.x properties configuration file to Log4j 2.x XML configuration file.
039 *
040 * <p>
041 * Run with "--help" on the command line.
042 * </p>
043 *
044 * <p>
045 * Example:
046 * </p>
047 *
048 * <pre>
049 * java org.apache.log4j.config.Log4j1ConfigurationConverter --recurse
050 * E:\vcs\git\apache\logging\logging-log4j2\log4j-1.2-api\src\test\resources\config-1.2\hadoop --in log4j.properties --verbose
051 * </pre>
052 */
053public final class Log4j1ConfigurationConverter {
054
055    @Command(name = "Log4j1ConfigurationConverter")
056    public static class CommandLineArguments extends BasicCommandLineArguments implements Runnable {
057
058        @Option(names = { "--failfast", "-f" }, description = "Fails on the first failure in recurse mode.")
059        private boolean failFast;
060
061        @Option(names = { "--in", "-i" }, description = "Specifies the input file.")
062        private Path pathIn;
063
064        @Option(names = { "--out", "-o" }, description = "Specifies the output file.")
065        private Path pathOut;
066
067        @Option(names = { "--recurse", "-r" }, description = "Recurses into this folder looking for the input file")
068        private Path recurseIntoPath;
069
070        @Option(names = { "--verbose", "-v" }, description = "Be verbose.")
071        private boolean verbose;
072
073        public Path getPathIn() {
074            return pathIn;
075        }
076
077        public Path getPathOut() {
078            return pathOut;
079        }
080
081        public Path getRecurseIntoPath() {
082            return recurseIntoPath;
083        }
084
085        public boolean isFailFast() {
086            return failFast;
087        }
088
089        public boolean isVerbose() {
090            return verbose;
091        }
092
093        public void setFailFast(final boolean failFast) {
094            this.failFast = failFast;
095        }
096
097        public void setPathIn(final Path pathIn) {
098            this.pathIn = pathIn;
099        }
100
101        public void setPathOut(final Path pathOut) {
102            this.pathOut = pathOut;
103        }
104
105        public void setRecurseIntoPath(final Path recurseIntoPath) {
106            this.recurseIntoPath = recurseIntoPath;
107        }
108
109        public void setVerbose(final boolean verbose) {
110            this.verbose = verbose;
111        }
112
113        @Override
114        public void run() {
115            if (isHelp()) {
116                CommandLine.usage(this, System.err);
117                return;
118            }
119            new Log4j1ConfigurationConverter(this).run();
120        }
121
122        @Override
123        public String toString() {
124            return "CommandLineArguments [recurseIntoPath=" + recurseIntoPath + ", verbose=" + verbose + ", pathIn="
125                    + pathIn + ", pathOut=" + pathOut + "]";
126        }
127    }
128
129    private static final String FILE_EXT_XML = ".xml";
130
131    public static void main(final String[] args) {
132        CommandLine.run(new CommandLineArguments(), System.err, args);
133    }
134
135    public static Log4j1ConfigurationConverter run(final CommandLineArguments cla) {
136        final Log4j1ConfigurationConverter log4j1ConfigurationConverter = new Log4j1ConfigurationConverter(cla);
137        log4j1ConfigurationConverter.run();
138        return log4j1ConfigurationConverter;
139    }
140
141    private final CommandLineArguments cla;
142
143    private Log4j1ConfigurationConverter(final CommandLineArguments cla) {
144        this.cla = cla;
145    }
146
147    protected void convert(final InputStream input, final OutputStream output) throws IOException {
148        final ConfigurationBuilder<BuiltConfiguration> builder = new Log4j1ConfigurationParser()
149                .buildConfigurationBuilder(input);
150        builder.writeXmlConfiguration(output);
151    }
152
153    InputStream getInputStream() throws IOException {
154        final Path pathIn = cla.getPathIn();
155        return pathIn == null ? System.in : new InputStreamWrapper(Files.newInputStream(pathIn), pathIn.toString());
156    }
157
158    OutputStream getOutputStream() throws IOException {
159        final Path pathOut = cla.getPathOut();
160        return pathOut == null ? System.out : Files.newOutputStream(pathOut);
161    }
162
163    private void run() {
164        if (cla.getRecurseIntoPath() != null) {
165            final AtomicInteger countOKs = new AtomicInteger();
166            final AtomicInteger countFails = new AtomicInteger();
167            try {
168                Files.walkFileTree(cla.getRecurseIntoPath(), new SimpleFileVisitor<Path>() {
169                    @Override
170                    public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs)
171                            throws IOException {
172                        if (cla.getPathIn() == null || file.getFileName().equals(cla.getPathIn())) {
173                            verbose("Reading %s", file);
174                            String newFile = file.getFileName().toString();
175                            final int lastIndex = newFile.lastIndexOf(".");
176                            newFile = lastIndex < 0 ? newFile + FILE_EXT_XML
177                                    : newFile.substring(0, lastIndex) + FILE_EXT_XML;
178                            final Path resolved = file.resolveSibling(newFile);
179                            try (final InputStream input = new InputStreamWrapper(Files.newInputStream(file), file.toString());
180                                    final OutputStream output = Files.newOutputStream(resolved)) {
181                                try {
182                                    convert(input, output);
183                                    countOKs.incrementAndGet();
184                                } catch (ConfigurationException | IOException e) {
185                                    countFails.incrementAndGet();
186                                    if (cla.isFailFast()) {
187                                        throw e;
188                                    }
189                                    e.printStackTrace();
190                                }
191                                verbose("Wrote %s", resolved);
192                            }
193                        }
194                        return FileVisitResult.CONTINUE;
195                    }
196                });
197            } catch (final IOException e) {
198                throw new ConfigurationException(e);
199            } finally {
200                verbose("OK = %,d, Failures = %,d, Total = %,d", countOKs.get(), countFails.get(),
201                        countOKs.get() + countFails.get());
202            }
203        } else {
204            verbose("Reading %s", cla.getPathIn());
205            try (final InputStream input = getInputStream(); final OutputStream output = getOutputStream()) {
206                convert(input, output);
207            } catch (final IOException e) {
208                throw new ConfigurationException(e);
209            }
210            verbose("Wrote %s", cla.getPathOut());
211        }
212    }
213
214    private void verbose(final String template, final Object... args) {
215        if (cla.isVerbose()) {
216            System.err.println(String.format(template, args));
217        }
218    }
219
220}