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.logging.log4j.core.config.builder.impl;
018
019import java.io.IOException;
020import java.io.OutputStream;
021import java.io.StringReader;
022import java.io.StringWriter;
023import java.lang.reflect.Constructor;
024import java.util.List;
025import java.util.Map;
026import java.util.concurrent.TimeUnit;
027
028import javax.xml.stream.XMLOutputFactory;
029import javax.xml.stream.XMLStreamException;
030import javax.xml.stream.XMLStreamWriter;
031import javax.xml.transform.OutputKeys;
032import javax.xml.transform.Result;
033import javax.xml.transform.Source;
034import javax.xml.transform.Transformer;
035import javax.xml.transform.TransformerConfigurationException;
036import javax.xml.transform.TransformerException;
037import javax.xml.transform.TransformerFactory;
038import javax.xml.transform.TransformerFactoryConfigurationError;
039import javax.xml.transform.stream.StreamResult;
040import javax.xml.transform.stream.StreamSource;
041
042import org.apache.logging.log4j.Level;
043import org.apache.logging.log4j.core.Filter;
044import org.apache.logging.log4j.core.LoggerContext;
045import org.apache.logging.log4j.core.config.Configuration;
046import org.apache.logging.log4j.core.config.ConfigurationException;
047import org.apache.logging.log4j.core.config.ConfigurationSource;
048import org.apache.logging.log4j.core.config.LoggerConfig;
049import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
050import org.apache.logging.log4j.core.config.builder.api.AppenderRefComponentBuilder;
051import org.apache.logging.log4j.core.config.builder.api.Component;
052import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder;
053import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
054import org.apache.logging.log4j.core.config.builder.api.CustomLevelComponentBuilder;
055import org.apache.logging.log4j.core.config.builder.api.FilterComponentBuilder;
056import org.apache.logging.log4j.core.config.builder.api.KeyValuePairComponentBuilder;
057import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder;
058import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder;
059import org.apache.logging.log4j.core.config.builder.api.PropertyComponentBuilder;
060import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder;
061import org.apache.logging.log4j.core.config.builder.api.ScriptComponentBuilder;
062import org.apache.logging.log4j.core.config.builder.api.ScriptFileComponentBuilder;
063import org.apache.logging.log4j.core.util.Throwables;
064
065/**
066 * @param <T> The BuiltConfiguration type.
067 * @since 2.4
068 */
069public class DefaultConfigurationBuilder<T extends BuiltConfiguration> implements ConfigurationBuilder<T> {
070
071    private static final String INDENT = "  ";
072
073    private final Component root = new Component();
074    private Component loggers;
075    private Component appenders;
076    private Component filters;
077    private Component properties;
078    private Component customLevels;
079    private Component scripts;
080    private final Class<T> clazz;
081    private ConfigurationSource source;
082    private int monitorInterval;
083    private Level level;
084    private String verbosity;
085    private String destination;
086    private String packages;
087    private String shutdownFlag;
088    private long shutdownTimeoutMillis;
089    private String advertiser;
090    private LoggerContext loggerContext;
091    private String name;
092
093    public static void formatXml(final Source source, final Result result)
094        throws TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException {
095        final Transformer transformer = TransformerFactory.newInstance().newTransformer();
096        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", Integer.toString(INDENT.length()));
097        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
098        transformer.transform(source, result);
099    }
100
101    @SuppressWarnings("unchecked")
102    public DefaultConfigurationBuilder() {
103        this((Class<T>) BuiltConfiguration.class);
104        root.addAttribute("name", "Built");
105    }
106
107    public DefaultConfigurationBuilder(final Class<T> clazz) {
108        if (clazz == null) {
109            throw new IllegalArgumentException("A Configuration class must be provided");
110        }
111        this.clazz = clazz;
112        final List<Component> components = root.getComponents();
113        properties = new Component("Properties");
114        components.add(properties);
115        scripts = new Component("Scripts");
116        components.add(scripts);
117        customLevels = new Component("CustomLevels");
118        components.add(customLevels);
119        filters = new Component("Filters");
120        components.add(filters);
121        appenders = new Component("Appenders");
122        components.add(appenders);
123        loggers = new Component("Loggers");
124        components.add(loggers);
125    }
126
127    protected ConfigurationBuilder<T> add(final Component parent, final ComponentBuilder<?> builder) {
128        parent.getComponents().add(builder.build());
129        return this;
130    }
131
132    @Override
133    public ConfigurationBuilder<T> add(final AppenderComponentBuilder builder) {
134        return add(appenders, builder);
135    }
136
137    @Override
138    public ConfigurationBuilder<T> add(final CustomLevelComponentBuilder builder) {
139        return add(customLevels, builder);
140    }
141
142    @Override
143    public ConfigurationBuilder<T> add(final FilterComponentBuilder builder) {
144        return add(filters, builder);
145    }
146
147    @Override
148    public ConfigurationBuilder<T> add(final ScriptComponentBuilder builder) {
149        return add(scripts, builder);
150    }
151
152    @Override
153    public ConfigurationBuilder<T> add(final ScriptFileComponentBuilder builder) {
154        return add(scripts, builder);
155    }
156
157    @Override
158    public ConfigurationBuilder<T> add(final LoggerComponentBuilder builder) {
159        return add(loggers, builder);
160    }
161
162    @Override
163    public ConfigurationBuilder<T> add(final RootLoggerComponentBuilder builder) {
164        for (final Component c : loggers.getComponents()) {
165            if (c.getPluginType().equals(LoggerConfig.ROOT)) {
166                throw new ConfigurationException("Root Logger was previously defined");
167            }
168        }
169        return add(loggers, builder);
170    }
171
172    @Override
173    public ConfigurationBuilder<T> addProperty(final String key, final String value) {
174        properties.addComponent(newComponent(key, "Property", value).build());
175        return this;
176    }
177
178    @Override
179    public T build() {
180        return build(true);
181    }
182
183    @Override
184    public T build(final boolean initialize) {
185        T configuration;
186        try {
187            if (source == null) {
188                source = ConfigurationSource.NULL_SOURCE;
189            }
190            final Constructor<T> constructor = clazz.getConstructor(LoggerContext.class, ConfigurationSource.class, Component.class);
191            configuration = constructor.newInstance(loggerContext, source, root);
192            configuration.getRootNode().getAttributes().putAll(root.getAttributes());
193            if (name != null) {
194                configuration.setName(name);
195            }
196            if (level != null) {
197                configuration.getStatusConfiguration().withStatus(level);
198            }
199            if (verbosity != null) {
200                configuration.getStatusConfiguration().withVerbosity(verbosity);
201            }
202            if (destination != null) {
203                configuration.getStatusConfiguration().withDestination(destination);
204            }
205            if (packages != null) {
206                configuration.setPluginPackages(packages);
207            }
208            if (shutdownFlag != null) {
209                configuration.setShutdownHook(shutdownFlag);
210            }
211            if (shutdownTimeoutMillis > 0) {
212                configuration.setShutdownTimeoutMillis(shutdownTimeoutMillis);
213            }
214            if (advertiser != null) {
215                configuration.createAdvertiser(advertiser, source);
216            }
217            configuration.setMonitorInterval(monitorInterval);
218        } catch (final Exception ex) {
219            throw new IllegalArgumentException("Invalid Configuration class specified", ex);
220        }
221        configuration.getStatusConfiguration().initialize();
222        if (initialize) {
223            configuration.initialize();
224        }
225        return configuration;
226    }
227
228    private String formatXml(String xml)
229        throws TransformerConfigurationException, TransformerException, TransformerFactoryConfigurationError {
230        final StringWriter writer = new StringWriter();
231        formatXml(new StreamSource(new StringReader(xml)), new StreamResult(writer));
232        return writer.toString();
233    }
234
235    @Override
236    public void writeXmlConfiguration(final OutputStream output) throws IOException {
237        try {
238            final XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(output);
239            writeXmlConfiguration(xmlWriter);
240            xmlWriter.close();
241        } catch (final XMLStreamException e) {
242            if (e.getNestedException() instanceof IOException) {
243                throw (IOException) e.getNestedException();
244            }
245            Throwables.rethrow(e);
246        }
247    }
248
249    @Override
250    public String toXmlConfiguration() {
251        final StringWriter writer = new StringWriter();
252        try {
253            final XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(writer);
254            writeXmlConfiguration(xmlWriter);
255            xmlWriter.close();
256            return formatXml(writer.toString());
257        } catch (final XMLStreamException | TransformerException e) {
258            Throwables.rethrow(e);
259        }
260        return writer.toString();
261    }
262
263    private void writeXmlConfiguration(final XMLStreamWriter xmlWriter) throws XMLStreamException {
264        xmlWriter.writeStartDocument();
265        xmlWriter.writeStartElement("Configuration");
266        if (name != null) {
267            xmlWriter.writeAttribute("name", name);
268        }
269        if (level != null) {
270            xmlWriter.writeAttribute("status", level.name());
271        }
272        if (verbosity != null) {
273            xmlWriter.writeAttribute("verbose", verbosity);
274        }
275        if (destination != null) {
276            xmlWriter.writeAttribute("dest", destination);
277        }
278        if (packages != null) {
279            xmlWriter.writeAttribute("packages", packages);
280        }
281        if (shutdownFlag != null) {
282            xmlWriter.writeAttribute("shutdownHook", shutdownFlag);
283        }
284        if (shutdownTimeoutMillis > 0) {
285            xmlWriter.writeAttribute("shutdownTimeout", String.valueOf(shutdownTimeoutMillis));
286        }
287        if (advertiser != null) {
288            xmlWriter.writeAttribute("advertiser", advertiser);
289        }
290        if (monitorInterval > 0) {
291            xmlWriter.writeAttribute("monitorInterval", String.valueOf(monitorInterval));
292        }
293
294        writeXmlSection(xmlWriter, properties);
295        writeXmlSection(xmlWriter, scripts);
296        writeXmlSection(xmlWriter, customLevels);
297        if (filters.getComponents().size() == 1) {
298            writeXmlComponent(xmlWriter, filters.getComponents().get(0));
299        } else if (filters.getComponents().size() > 1) {
300            writeXmlSection(xmlWriter, filters);
301        }
302        writeXmlSection(xmlWriter, appenders);
303        writeXmlSection(xmlWriter, loggers);
304
305        xmlWriter.writeEndElement(); // "Configuration"
306        xmlWriter.writeEndDocument();
307    }
308
309    private void writeXmlSection(final XMLStreamWriter xmlWriter, final Component component) throws XMLStreamException {
310        if (!component.getAttributes().isEmpty() || !component.getComponents().isEmpty() || component.getValue() != null) {
311            writeXmlComponent(xmlWriter, component);
312        }
313    }
314
315    private void writeXmlComponent(final XMLStreamWriter xmlWriter, final Component component) throws XMLStreamException {
316        if (!component.getComponents().isEmpty() || component.getValue() != null) {
317            xmlWriter.writeStartElement(component.getPluginType());
318            writeXmlAttributes(xmlWriter, component);
319            for (final Component subComponent : component.getComponents()) {
320                writeXmlComponent(xmlWriter, subComponent);
321            }
322            if (component.getValue() != null) {
323                xmlWriter.writeCharacters(component.getValue());
324            }
325            xmlWriter.writeEndElement();
326        } else {
327            xmlWriter.writeEmptyElement(component.getPluginType());
328            writeXmlAttributes(xmlWriter, component);
329        }
330    }
331
332    private void writeXmlAttributes(final XMLStreamWriter xmlWriter, final Component component) throws XMLStreamException {
333        for (final Map.Entry<String, String> attribute : component.getAttributes().entrySet()) {
334            xmlWriter.writeAttribute(attribute.getKey(), attribute.getValue());
335        }
336    }
337
338    @Override
339    public ScriptComponentBuilder newScript(final String name, final String language, final String text) {
340        return new DefaultScriptComponentBuilder(this, name, language, text);
341    }
342
343
344    @Override
345    public ScriptFileComponentBuilder newScriptFile(final String path) {
346        return new DefaultScriptFileComponentBuilder(this, path, path);
347    }
348
349    @Override
350    public ScriptFileComponentBuilder newScriptFile(final String name, final String path) {
351        return new DefaultScriptFileComponentBuilder(this, name, path);
352    }
353
354    @Override
355    public AppenderComponentBuilder newAppender(final String name, final String type) {
356        return new DefaultAppenderComponentBuilder(this, name, type);
357    }
358
359    @Override
360    public AppenderRefComponentBuilder newAppenderRef(final String ref) {
361        return new DefaultAppenderRefComponentBuilder(this, ref);
362    }
363
364    @Override
365    public LoggerComponentBuilder newAsyncLogger(final String name) {
366        return new DefaultLoggerComponentBuilder(this, name, null, "AsyncLogger");
367    }
368
369    @Override
370    public LoggerComponentBuilder newAsyncLogger(final String name, final boolean includeLocation) {
371        return new DefaultLoggerComponentBuilder(this, name, null, "AsyncLogger", includeLocation);
372    }
373
374    @Override
375    public LoggerComponentBuilder newAsyncLogger(final String name, final Level level) {
376        return new DefaultLoggerComponentBuilder(this, name, level.toString(), "AsyncLogger");
377    }
378
379    @Override
380    public LoggerComponentBuilder newAsyncLogger(final String name, final Level level, final boolean includeLocation) {
381        return new DefaultLoggerComponentBuilder(this, name, level.toString(), "AsyncLogger", includeLocation);
382    }
383
384    @Override
385    public LoggerComponentBuilder newAsyncLogger(final String name, final String level) {
386        return new DefaultLoggerComponentBuilder(this, name, level, "AsyncLogger");
387    }
388
389    @Override
390    public LoggerComponentBuilder newAsyncLogger(final String name, final String level, final boolean includeLocation) {
391        return new DefaultLoggerComponentBuilder(this, name, level, "AsyncLogger", includeLocation);
392    }
393
394    @Override
395    public RootLoggerComponentBuilder newAsyncRootLogger() {
396        return new DefaultRootLoggerComponentBuilder(this, "AsyncRoot");
397    }
398
399    @Override
400    public RootLoggerComponentBuilder newAsyncRootLogger(final boolean includeLocation) {
401        return new DefaultRootLoggerComponentBuilder(this, null, "AsyncRoot", includeLocation);
402    }
403
404    @Override
405    public RootLoggerComponentBuilder newAsyncRootLogger(final Level level) {
406        return new DefaultRootLoggerComponentBuilder(this, level.toString(), "AsyncRoot");
407    }
408
409    @Override
410    public RootLoggerComponentBuilder newAsyncRootLogger(final Level level, final boolean includeLocation) {
411        return new DefaultRootLoggerComponentBuilder(this, level.toString(), "AsyncRoot", includeLocation);
412    }
413
414    @Override
415    public RootLoggerComponentBuilder newAsyncRootLogger(final String level) {
416        return new DefaultRootLoggerComponentBuilder(this, level, "AsyncRoot");
417    }
418
419    @Override
420    public RootLoggerComponentBuilder newAsyncRootLogger(final String level, final boolean includeLocation) {
421        return new DefaultRootLoggerComponentBuilder(this, level, "AsyncRoot", includeLocation);
422    }
423
424
425    @Override
426    public <B extends ComponentBuilder<B>> ComponentBuilder<B> newComponent(final String type) {
427        return new DefaultComponentBuilder<>(this, type);
428    }
429
430    @Override
431    public <B extends ComponentBuilder<B>> ComponentBuilder<B> newComponent(final String name, final String type) {
432        return new DefaultComponentBuilder<>(this, name, type);
433    }
434
435    @Override
436    public <B extends ComponentBuilder<B>> ComponentBuilder<B> newComponent(final String name, final String type,
437                                                                            final String value) {
438        return new DefaultComponentBuilder<>(this, name, type, value);
439    }
440
441    @Override
442    public PropertyComponentBuilder newProperty(final String name, final String value) {
443        return new DefaultPropertyComponentBuilder(this, name, value);
444    }
445
446    @Override
447    public KeyValuePairComponentBuilder newKeyValuePair(final String key, final String value) {
448        return new DefaultKeyValuePairComponentBuilder(this, key, value);
449    }
450
451    @Override
452    public CustomLevelComponentBuilder newCustomLevel(final String name, final int level) {
453        return new DefaultCustomLevelComponentBuilder(this, name, level);
454    }
455
456    @Override
457    public FilterComponentBuilder newFilter(final String type, final Filter.Result onMatch,
458                                            final Filter.Result onMismatch) {
459        return new DefaultFilterComponentBuilder(this, type, onMatch.name(), onMismatch.name());
460    }
461
462    @Override
463    public FilterComponentBuilder newFilter(final String type, final String onMatch, final String onMismatch) {
464        return new DefaultFilterComponentBuilder(this, type, onMatch, onMismatch);
465    }
466
467    @Override
468    public LayoutComponentBuilder newLayout(final String type) {
469        return new DefaultLayoutComponentBuilder(this, type);
470    }
471
472    @Override
473    public LoggerComponentBuilder newLogger(final String name) {
474        return new DefaultLoggerComponentBuilder(this, name, null);
475    }
476
477    @Override
478    public LoggerComponentBuilder newLogger(final String name, final boolean includeLocation) {
479        return new DefaultLoggerComponentBuilder(this, name, null, includeLocation);
480    }
481
482    @Override
483    public LoggerComponentBuilder newLogger(final String name, final Level level) {
484        return new DefaultLoggerComponentBuilder(this, name, level.toString());
485    }
486
487    @Override
488    public LoggerComponentBuilder newLogger(final String name, final Level level, final boolean includeLocation) {
489        return new DefaultLoggerComponentBuilder(this, name, level.toString(), includeLocation);
490    }
491
492    @Override
493    public LoggerComponentBuilder newLogger(final String name, final String level) {
494        return new DefaultLoggerComponentBuilder(this, name, level);
495    }
496
497    @Override
498    public LoggerComponentBuilder newLogger(final String name, final String level, final boolean includeLocation) {
499        return new DefaultLoggerComponentBuilder(this, name, level, includeLocation);
500    }
501
502    @Override
503    public RootLoggerComponentBuilder newRootLogger() {
504        return new DefaultRootLoggerComponentBuilder(this, null);
505    }
506
507    @Override
508    public RootLoggerComponentBuilder newRootLogger(final boolean includeLocation) {
509        return new DefaultRootLoggerComponentBuilder(this, null, includeLocation);
510    }
511
512    @Override
513    public RootLoggerComponentBuilder newRootLogger(final Level level) {
514        return new DefaultRootLoggerComponentBuilder(this, level.toString());
515    }
516
517    @Override
518    public RootLoggerComponentBuilder newRootLogger(final Level level, final boolean includeLocation) {
519        return new DefaultRootLoggerComponentBuilder(this, level.toString(), includeLocation);
520    }
521
522    @Override
523    public RootLoggerComponentBuilder newRootLogger(final String level) {
524        return new DefaultRootLoggerComponentBuilder(this, level);
525    }
526
527    @Override
528    public RootLoggerComponentBuilder newRootLogger(final String level, final boolean includeLocation) {
529        return new DefaultRootLoggerComponentBuilder(this, level, includeLocation);
530    }
531
532    @Override
533    public ConfigurationBuilder<T> setAdvertiser(final String advertiser) {
534        this.advertiser = advertiser;
535        return this;
536    }
537
538    /**
539     * Set the name of the configuration.
540     *
541     * @param name the name of the {@link Configuration}. By default is {@code "Assembled"}.
542     * @return this builder instance
543     */
544    @Override
545    public ConfigurationBuilder<T> setConfigurationName(final String name) {
546        this.name = name;
547        return this;
548    }
549
550    /**
551     * Set the ConfigurationSource.
552     *
553     * @param configurationSource the {@link ConfigurationSource}
554     * @return this builder instance
555     */
556    @Override
557    public ConfigurationBuilder<T> setConfigurationSource(final ConfigurationSource configurationSource) {
558        source = configurationSource;
559        return this;
560    }
561
562    @Override
563    public ConfigurationBuilder<T> setMonitorInterval(final String intervalSeconds) {
564        monitorInterval = Integer.parseInt(intervalSeconds);
565        return this;
566    }
567
568    @Override
569    public ConfigurationBuilder<T> setPackages(final String packages) {
570        this.packages = packages;
571        return this;
572    }
573
574    @Override
575    public ConfigurationBuilder<T> setShutdownHook(final String flag) {
576        this.shutdownFlag = flag;
577        return this;
578    }
579
580    @Override
581    public ConfigurationBuilder<T> setShutdownTimeout(final long timeout, final TimeUnit timeUnit) {
582        this.shutdownTimeoutMillis = timeUnit.toMillis(timeout);
583        return this;
584    }
585
586    @Override
587    public ConfigurationBuilder<T> setStatusLevel(final Level level) {
588        this.level = level;
589        return this;
590    }
591
592    @Override
593    public ConfigurationBuilder<T> setVerbosity(final String verbosity) {
594        this.verbosity = verbosity;
595        return this;
596    }
597
598    @Override
599    public ConfigurationBuilder<T> setDestination(final String destination) {
600        this.destination = destination;
601        return this;
602    }
603
604    @Override
605    public void setLoggerContext(final LoggerContext loggerContext) {
606        this.loggerContext = loggerContext;
607    }
608
609    @Override
610    public ConfigurationBuilder<T> addRootProperty(final String key, final String value) {
611        root.getAttributes().put(key, value);
612        return this;
613    }
614
615}