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