diff --git a/log4j-docgen/pom.xml b/log4j-docgen/pom.xml
index d8db3447..d8d735f4 100644
--- a/log4j-docgen/pom.xml
+++ b/log4j-docgen/pom.xml
@@ -30,6 +30,33 @@
false
+
+
+
+ jakarta.inject
+ jakarta.inject-api
+ provided
+
+
+
+ org.freemarker
+ freemarker
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
+ org.xmlunit
+ xmlunit-assertj3
+ test
+
+
+
+
@@ -57,6 +84,18 @@
+
+ org.eclipse.sisu
+ sisu-maven-plugin
+
+
+ generate-sisu-descriptor
+
+ main-index
+
+
+
+
diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java
new file mode 100644
index 00000000..0ea942fc
--- /dev/null
+++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java
@@ -0,0 +1,305 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.docgen.internal;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import javax.xml.XMLConstants;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.apache.logging.log4j.docgen.AbstractType;
+import org.apache.logging.log4j.docgen.Description;
+import org.apache.logging.log4j.docgen.PluginAttribute;
+import org.apache.logging.log4j.docgen.PluginElement;
+import org.apache.logging.log4j.docgen.PluginSet;
+import org.apache.logging.log4j.docgen.PluginType;
+import org.apache.logging.log4j.docgen.ScalarType;
+import org.apache.logging.log4j.docgen.ScalarValue;
+import org.apache.logging.log4j.docgen.Type;
+import org.apache.logging.log4j.docgen.io.stax.PluginBundleStaxReader;
+import org.apache.logging.log4j.docgen.util.TypeLookup;
+import org.apache.logging.log4j.docgen.xsd.SchemaGenerator;
+import org.apache.logging.log4j.docgen.xsd.SchemaGeneratorRequest;
+
+@Singleton
+@Named("default")
+public class DefaultSchemaGenerator implements SchemaGenerator {
+
+ private static final String LOG4J_PREFIX = "log4j";
+ private static final String LOG4J_NAMESPACE = "http://logging.apache.org/log4j/2.0/config";
+ private static final String XSD_NAMESPACE = XMLConstants.W3C_XML_SCHEMA_NS_URI;
+ private static final String MULTIPLICITY_UNBOUNDED = "*";
+
+ @Override
+ public void generateSchema(final SchemaGeneratorRequest request) throws XMLStreamException {
+ try {
+ final PluginSet configurationSet =
+ new PluginBundleStaxReader().read(getClass().getResourceAsStream("configuration.xml"));
+ final List extendedSets = new ArrayList<>(request.getPluginSets());
+ extendedSets.add(configurationSet);
+ final TypeLookup lookup = TypeLookup.of(extendedSets);
+ final XMLOutputFactory factory = XMLOutputFactory.newFactory();
+ final Path schemaPath = request.getOutputDirectory().resolve(request.getFileName());
+ final XMLStreamWriter writer = factory.createXMLStreamWriter(Files.newOutputStream(schemaPath));
+ try {
+ writeSchema(lookup, writer);
+ } finally {
+ writer.close();
+ }
+ } catch (IOException e) {
+ throw new XMLStreamException(e);
+ }
+ }
+
+ private static void writeSchema(final TypeLookup lookup, final XMLStreamWriter writer) throws XMLStreamException {
+ writer.writeStartDocument("UTF-8", "1.0");
+ writer.setDefaultNamespace(XSD_NAMESPACE);
+ writer.writeStartElement(XSD_NAMESPACE, "schema");
+ writer.writeDefaultNamespace(XSD_NAMESPACE);
+ writer.writeNamespace(LOG4J_PREFIX, LOG4J_NAMESPACE);
+ writer.writeAttribute("elementFormDefault", "qualified");
+ writer.writeAttribute("targetNamespace", LOG4J_NAMESPACE);
+
+ // The root element
+ writer.writeEmptyElement(XSD_NAMESPACE, "element");
+ writer.writeAttribute("type", LOG4J_PREFIX + ":org.apache.logging.log4j.core.config.Configuration");
+ writer.writeAttribute("name", "Configuration");
+
+ // Write all types in alphabetical order
+ writeTypes(lookup, writer);
+
+ writer.writeEndElement();
+ writer.writeEndDocument();
+ }
+
+ private static void writeTypes(final TypeLookup lookup, final XMLStreamWriter writer) throws XMLStreamException {
+ for (final Type type : lookup.values()) {
+ if (isBuiltinXmlType(type.getClassName())) {
+ continue;
+ }
+ if (type instanceof ScalarType) {
+ writeScalarType((ScalarType) type, writer);
+ }
+ if (type instanceof PluginType) {
+ final PluginType pluginType = (PluginType) type;
+ writePluginType(lookup, pluginType, writer);
+ /*
+ * If a plugin extends another plugin or has multiple aliases
+ * we also need a element.
+ */
+ if (!pluginType.getAliases().isEmpty()
+ || !pluginType.getImplementations().isEmpty()) {
+ writeAbstractType(lookup, pluginType, writer);
+ }
+ } else if (type instanceof AbstractType) {
+ writeAbstractType(lookup, (AbstractType) type, writer);
+ }
+ }
+ }
+
+ private static boolean isBuiltinXmlType(final String className) {
+ switch (className) {
+ case "boolean":
+ case "byte":
+ case "double":
+ case "float":
+ case "int":
+ case "short":
+ case "long":
+ case "java.lang.String":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static void writeScalarType(final ScalarType type, final XMLStreamWriter writer) throws XMLStreamException {
+ writer.writeStartElement(XSD_NAMESPACE, "simpleType");
+ writer.writeAttribute("name", type.getClassName());
+
+ writeDocumentation(type.getDescription(), writer);
+
+ writer.writeStartElement(XSD_NAMESPACE, "restriction");
+ writer.writeAttribute("base", "string");
+
+ for (final ScalarValue value : type.getValues()) {
+ writeScalarValue(value, writer);
+ }
+
+ writer.writeEndElement();
+ writer.writeEndElement();
+ }
+
+ private static void writePluginType(
+ final TypeLookup lookup, final PluginType pluginType, final XMLStreamWriter writer)
+ throws XMLStreamException {
+ writer.writeStartElement(XSD_NAMESPACE, "complexType");
+ writer.writeAttribute("name", pluginType.getClassName());
+
+ writeDocumentation(pluginType.getDescription(), writer);
+
+ final boolean hasComplexContent = !pluginType.getElements().isEmpty();
+
+ if (hasComplexContent) {
+ writer.writeStartElement(XSD_NAMESPACE, "sequence");
+ for (final PluginElement element : pluginType.getElements()) {
+ writePluginElement(lookup, element, writer);
+ }
+ writer.writeEndElement();
+ }
+
+ for (final PluginAttribute attribute : pluginType.getAttributes()) {
+ writePluginAttribute(lookup, attribute, writer);
+ }
+
+ writer.writeEndElement();
+ }
+
+ private static void writeAbstractType(
+ final TypeLookup lookup, final AbstractType abstractType, final XMLStreamWriter writer)
+ throws XMLStreamException {
+ writer.writeStartElement(XSD_NAMESPACE, "group");
+ writer.writeAttribute("name", abstractType.getClassName());
+ final Description description = abstractType.getDescription();
+ if (description != null) {
+ writeDocumentation(description, writer);
+ }
+ writer.writeStartElement(XSD_NAMESPACE, "choice");
+
+ final Set implementations = new TreeSet<>(abstractType.getImplementations());
+ if (abstractType instanceof PluginType) {
+ implementations.add(abstractType.getClassName());
+ }
+ for (final String implementation : implementations) {
+ final PluginType pluginType = (PluginType) lookup.get(implementation);
+ final Collection keys = new TreeSet<>(pluginType.getAliases());
+ keys.add(pluginType.getName());
+ for (final String key : keys) {
+ writer.writeEmptyElement(XSD_NAMESPACE, "element");
+ writer.writeAttribute("name", key);
+ writer.writeAttribute("type", LOG4J_PREFIX + ":" + pluginType.getClassName());
+ }
+ }
+
+ writer.writeEndElement();
+ writer.writeEndElement();
+ }
+
+ private static void writeDocumentation(final Description description, final XMLStreamWriter writer)
+ throws XMLStreamException {
+ writer.writeStartElement(XSD_NAMESPACE, "annotation");
+ writer.writeStartElement(XSD_NAMESPACE, "documentation");
+ writer.writeCharacters(description.getText());
+ writer.writeEndElement();
+ writer.writeEndElement();
+ }
+
+ private static void writeScalarValue(final ScalarValue value, final XMLStreamWriter writer)
+ throws XMLStreamException {
+ writer.writeStartElement(XSD_NAMESPACE, "enumeration");
+ writer.writeAttribute("value", value.getName());
+
+ writeDocumentation(value.getDescription(), writer);
+
+ writer.writeEndElement();
+ }
+
+ private static void writePluginElement(
+ final TypeLookup lookup, final PluginElement element, final XMLStreamWriter writer)
+ throws XMLStreamException {
+ final String type = element.getType();
+ final String xmlType = getXmlType(lookup, type);
+ final AbstractType abstractType = (AbstractType) lookup.get(type);
+ final PluginType pluginType = abstractType instanceof PluginType ? (PluginType) abstractType : null;
+ /*
+ * If a plugin extends another plugin or has multiple aliases
+ * we use a element.
+ */
+ if (pluginType == null
+ || !pluginType.getAliases().isEmpty()
+ || !pluginType.getImplementations().isEmpty()) {
+ writer.writeStartElement(XSD_NAMESPACE, "group");
+ writer.writeAttribute("ref", xmlType);
+ writeMultiplicity(element.isRequired(), element.getMultiplicity(), writer);
+ writeDocumentation(element.getDescription(), writer);
+ writer.writeEndElement();
+ } else {
+ final Collection keys = new TreeSet<>(pluginType.getAliases());
+ keys.add(pluginType.getName());
+ for (final String key : keys) {
+ writer.writeStartElement(XSD_NAMESPACE, "element");
+ writer.writeAttribute("name", key);
+ writer.writeAttribute("type", xmlType);
+ writeMultiplicity(element.isRequired(), element.getMultiplicity(), writer);
+ writeDocumentation(element.getDescription(), writer);
+ writer.writeEndElement();
+ }
+ }
+ }
+
+ private static void writePluginAttribute(
+ final TypeLookup lookup, final PluginAttribute attribute, final XMLStreamWriter writer)
+ throws XMLStreamException {
+ writer.writeStartElement(XSD_NAMESPACE, "attribute");
+ writer.writeAttribute("name", attribute.getName());
+ writer.writeAttribute("type", getXmlType(lookup, attribute.getType()));
+ final Description description = attribute.getDescription();
+ if (description != null) {
+ writeDocumentation(description, writer);
+ }
+ writer.writeEndElement();
+ }
+
+ private static String getXmlType(final TypeLookup lookup, final String className) {
+ switch (className) {
+ case "boolean":
+ case "byte":
+ case "double":
+ case "float":
+ case "int":
+ case "short":
+ case "long":
+ return className;
+ case "java.lang.String":
+ return "string";
+ }
+ final Type type = lookup.get(className);
+ if (type != null) {
+ return LOG4J_PREFIX + ":" + className;
+ }
+ throw new IllegalArgumentException("Unknown Java type '" + className + "'.");
+ }
+
+ private static void writeMultiplicity(
+ final boolean required, final String multiplicity, final XMLStreamWriter writer) throws XMLStreamException {
+ if (!required) {
+ writer.writeAttribute("minOccurs", "0");
+ }
+ if (MULTIPLICITY_UNBOUNDED.equals(multiplicity)) {
+ writer.writeAttribute("maxOccurs", "unbounded");
+ }
+ }
+}
diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/package-info.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/package-info.java
new file mode 100644
index 00000000..59793b39
--- /dev/null
+++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@Export
+@Version("0.8.0")
+package org.apache.logging.log4j.docgen;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/util/TypeLookup.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/util/TypeLookup.java
new file mode 100644
index 00000000..8ba6ddac
--- /dev/null
+++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/util/TypeLookup.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.docgen.util;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Predicate;
+import org.apache.logging.log4j.docgen.AbstractType;
+import org.apache.logging.log4j.docgen.PluginSet;
+import org.apache.logging.log4j.docgen.PluginType;
+import org.apache.logging.log4j.docgen.ScalarType;
+import org.apache.logging.log4j.docgen.Type;
+
+public class TypeLookup extends TreeMap {
+
+ private static final Predicate HAS_CORE_NAMESPACE = p -> "Core".equals(p.getNamespace());
+
+ private final Map pluginTypeMap = new TreeMap<>();
+
+ public static TypeLookup of(final Iterable sets) {
+ return new TypeLookup(sets);
+ }
+
+ private TypeLookup(final Iterable sets) {
+ final Map scalarTypeMap = new TreeMap<>();
+ final Map abstractTypeMap = new TreeMap<>();
+ // Round 1: Merge all the information from the sets
+ sets.forEach(set -> {
+ set.getScalars().forEach(scalar -> put(scalar.getClassName(), scalar));
+ set.getAbstractTypes().forEach(abstractType -> put(abstractType.getClassName(), abstractType));
+ set.getPlugins().stream().filter(HAS_CORE_NAMESPACE).forEach(plugin -> put(plugin.getClassName(), plugin));
+ });
+ // Round 2: fill in the set of abstract types used in elements and the list of their possible implementations
+ final Set requiredAbstractTypes = new HashSet<>();
+ sets.forEach(set -> {
+ set.getPlugins().stream().filter(HAS_CORE_NAMESPACE).forEach(plugin -> {
+ plugin.getSupertypes().forEach(supertype -> ((AbstractType)
+ computeIfAbsent(supertype, TypeLookup::createAbstractType))
+ .addImplementation(plugin.getClassName()));
+ plugin.getElements().forEach(element -> {
+ final String type = element.getType();
+ if (!scalarTypeMap.containsKey(type) && !pluginTypeMap.containsKey(type)) {
+ requiredAbstractTypes.add(type);
+ }
+ });
+ });
+ });
+ // Round 3: remove the types that are not required and do not have a description
+ values().removeIf(
+ type -> !requiredAbstractTypes.contains(type.getClassName()) && type.getDescription() == null);
+ }
+
+ private static AbstractType createAbstractType(final String className) {
+ final AbstractType abstractType = new AbstractType();
+ abstractType.setClassName(className);
+ return abstractType;
+ }
+}
diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/util/package-info.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/util/package-info.java
new file mode 100644
index 00000000..9b5a4fd2
--- /dev/null
+++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/util/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@Export
+@Version("0.8.0")
+package org.apache.logging.log4j.docgen.util;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
\ No newline at end of file
diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/xsd/package-info.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/xsd/package-info.java
new file mode 100644
index 00000000..26361aae
--- /dev/null
+++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/xsd/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@Export
+@Version("0.8.0")
+package org.apache.logging.log4j.docgen.xsd;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
\ No newline at end of file
diff --git a/log4j-docgen/src/main/mdo/plugins-model.xml b/log4j-docgen/src/main/mdo/plugins-model.xml
index d598ac67..f3687d31 100644
--- a/log4j-docgen/src/main/mdo/plugins-model.xml
+++ b/log4j-docgen/src/main/mdo/plugins-model.xml
@@ -19,7 +19,7 @@
xsi:schemaLocation="http://codehaus-plexus.github.io/MODELLO/2.0.0 https://codehaus-plexus.github.io/modello/xsd/modello-2.0.0.xsd"
xml.namespace="https://logging.apache.org/log4j/plugins">
plugins
- PluginSet
+ PluginBundle
Documents a set of Log4j plugins.
field
@@ -361,9 +361,103 @@ If the type is an array or collection, this returns the type of the element.
+
+ org.apache.logging.log4j.docgen.xsd
+ SchemaGeneratorRequest
+ DocumentationRequest
+
+
+ fileName
+ String
+ log4j-config.xml
+ The path to the generated schema file. If it is relative, it will be resolved against `outputDirectory`.
+
+
+
+
+ FreeMarkerGeneratorRequest
+ org.apache.logging.log4j.docgen.freemarker
+ DocumentationRequest
+
+
+ templateDirectory
+
+ java.nio.file.Path
+
+ true
+
+ The directory containing the FreeMarker templates.
+
+
+
+ pluginTemplate
+ String
+ plugin.ftl
+ A FreeMarker template to apply for to each {@link PluginType}.
+<p>
+Template parameters:
+</p>
+<ul>
+ <li>`type`: the {@link PluginType} to format,</li>
+ <li>`lookup`: a {@link org.apache.logging.log4j.docgen.util.TypeLookup} containing type information for other plugins.</li>
+</ul>
+
+
+ interfaceTemplate
+ String
+ interface.ftl
+ A FreeMarker template to apply for to each {@link AbstractType}.
+<p>
+Template parameters:
+</p>
+<ul>
+ <li>`type`: the {@link AbstractType} to format,</li>
+ <li>`lookup`: a {@link org.apache.logging.log4j.docgen.util.TypeLookup} containing type information for other plugins.</li>
+</ul>
+
+
+ scalarsTemplate
+ String
+ scalars.ftl
+ A FreeMarker template to describe all {@link ScalarType} types.
+<p>
+Template parameters:
+</p>
+<ul>
+ <li>`scalars`: a list of all {@link ScalarType} elements to format.</li>
+</ul>
+
+
+
java.nio.file.Path
+
+
+ org.apache.logging.log4j.docgen.xsd
+ SchemaGenerator
+
+
+ Generates an XML schema.
+
+@param request configuration data for the generated schema.
+ void generateSchema(SchemaGeneratorRequest request) throws javax.xml.stream.XMLStreamException;
+
+
+
+
+ org.apache.logging.log4j.docgen.freemarker
+ FreeMarkerGenerator
+
+
+ Generates documentation using FreeMarker.
+
+@param request configuration data for the generated documentation.
+ void generateDocumentation(FreeMarkerGeneratorRequest request) throws java.io.IOException;
+
+
+
+
diff --git a/log4j-docgen/src/main/resources/org/apache/logging/log4j/docgen/internal/configuration.xml b/log4j-docgen/src/main/resources/org/apache/logging/log4j/docgen/internal/configuration.xml
new file mode 100644
index 00000000..572baea6
--- /dev/null
+++ b/log4j-docgen/src/main/resources/org/apache/logging/log4j/docgen/internal/configuration.xml
@@ -0,0 +1,165 @@
+
+
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.22.0
+ Reference implementation of the Log4j API.
+
+
+
+
+
+ A boolean value.
+
+
+ false
+
+
+ true
+
+
+
+
+ An 8-bit signed integer from -128 to 127.
+
+
+ A 16-bit signed integer from -32768 to 32767.
+
+
+ A 32-bit signed integer from -2147483648 to 2147483647.
+
+
+ A 64-bit signed integer from -9223372036854775808 to 9223372036854775807.
+
+
+ A single precision floating number from 1.40239846e-45 to 3.40282347e+38.
+
+
+ A double precision floating number from 4.94065645841246544e-324 to 1.79769313486231570e+308.
+
+
+ A string of characters.
+
+
+
+ Represents a logging level.
+**Note**: the Log4j API supports custom levels, the following list contains only the standard ones.
+
+
+ Special level that disables logging. No events should be logged at this level.
+
+
+ A fatal event that will prevent the application from continuing.
+
+
+ An error in the application, possibly recoverable.
+
+
+ An event that might possible lead to an error.
+
+
+ An event for informational purposes.
+
+
+ A general debugging event.
+
+
+ A fine-grained debug message, typically capturing the flow through the application.
+
+
+ All events should be logged.
+
+
+
+
+
+
+
+
+ A Log4j 2.x configuration contains many components, two of them `Appenders` and `Loggers` are required.
+
+
+ If set it should contain the name of an `Advertiser` plugin
+(cf. https://logging.apache.org/log4j/2.x/manual/configuration.html#chainsaw-can-automatically-process-your-log-files-advertising-ap[documentation] ).
+Log4j Core provides a single implementation based on JmDNS called `MulticastDns`.
+
+
+ Specifies the destination for StatusLogger events.
+The possible values are:
+* `out` for using standard out (default),
+* `err` for using standard error,
+* a string that is interpreted in order as URI, URL or the path to a local file.
+
+If the provided value is invalid, then the default destination of standard out will be used.
+
+
+ Name of the configuration.
+
+
+ Number of seconds between polls for configuration changes.
+
+
+ The name of a classpath resource to use to validate the configuration.
+
+
+ Specifies whether Log4j should automatically shut down when the JVM shuts down.
+Possible values:
+* `enable`: to enable unconditionally the hook,
+* `disable`: to disable unconditionally the hook.
+
+The shutdown hook is enabled by default, unless Log4j Core detects the presence of the Servlet API.
+
+
+ Timeout in milliseconds of the logger context shut down.
+
+
+ Sets the level of the status logger.
+
+
+ If set to `true` the configuration file will be validated using an XML schema.
+
+
+ Specifies the verbosity level for the status logger.
+This only applies to classes configured by `StatusConfiguration#setVerboseClasses`.
+
+
+
+
+ Wrapper element for a list of properties.
+
+If present, this element must be the **first** child of the configuration.
+
+
+ Wrapper element for a list of appenders.
+
+
+ Wrapper element for a list of custom levels.
+
+
+ Wrapper element for a list of logger configurations.
+
+
+
+
+
diff --git a/log4j-docgen/src/test/java/org/apache/logging/log4j/docgen/internal/SchemaGeneratorTest.java b/log4j-docgen/src/test/java/org/apache/logging/log4j/docgen/internal/SchemaGeneratorTest.java
new file mode 100644
index 00000000..3fbbcb8d
--- /dev/null
+++ b/log4j-docgen/src/test/java/org/apache/logging/log4j/docgen/internal/SchemaGeneratorTest.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.docgen.internal;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.Source;
+import org.apache.logging.log4j.docgen.PluginSet;
+import org.apache.logging.log4j.docgen.io.stax.PluginBundleStaxReader;
+import org.apache.logging.log4j.docgen.xsd.SchemaGenerator;
+import org.apache.logging.log4j.docgen.xsd.SchemaGeneratorRequest;
+import org.junit.jupiter.api.Test;
+import org.xmlunit.assertj3.XmlAssert;
+import org.xmlunit.builder.Input;
+
+public class SchemaGeneratorTest {
+
+ @Test
+ void schemaGeneration() throws Exception {
+ final Source xmlSchema =
+ Input.fromURI("https://www.w3.org/2001/XMLSchema.xsd").build();
+ final URL expectedSchema = SchemaGenerator.class.getResource("/expected/xsd/log4j-config.xsd");
+ final Path actualSchema = assertDoesNotThrow(() -> generateSchema());
+ XmlAssert.assertThat(actualSchema)
+ .isValidAgainst(xmlSchema)
+ .and(expectedSchema)
+ .ignoreComments()
+ .ignoreWhitespace()
+ .areIdentical();
+ }
+
+ private static Path generateSchema() throws XMLStreamException, IOException {
+ final PluginBundleStaxReader reader = new PluginBundleStaxReader();
+ final PluginSet set =
+ reader.read(SchemaGeneratorTest.class.getResourceAsStream("/META-INF/log4j/plugins-sample.xml"));
+ final SchemaGenerator generator = new DefaultSchemaGenerator();
+ final SchemaGeneratorRequest request = new SchemaGeneratorRequest();
+ final Path outputDirectory = Paths.get("target/test-site/xsd");
+ Files.createDirectories(outputDirectory);
+ request.addPluginSet(set);
+ request.setOutputDirectory(outputDirectory);
+
+ generator.generateSchema(request);
+ return outputDirectory.resolve(request.getFileName());
+ }
+}
diff --git a/log4j-docgen/src/test/resources/expected/xsd/log4j-config.xsd b/log4j-docgen/src/test/resources/expected/xsd/log4j-config.xsd
new file mode 100644
index 00000000..02f50c20
--- /dev/null
+++ b/log4j-docgen/src/test/resources/expected/xsd/log4j-config.xsd
@@ -0,0 +1,519 @@
+
+
+
+
+
+
+
+ Represents a logging level.
+**Note**: the Log4j API supports custom levels, the following list contains only the standard ones.
+
+
+
+
+ Special level that disables logging. No events should be logged at this level.
+
+
+
+
+ A fatal event that will prevent the application from continuing.
+
+
+
+
+ An error in the application, possibly recoverable.
+
+
+
+
+ An event that might possible lead to an error.
+
+
+
+
+ An event for informational purposes.
+
+
+
+
+ A general debugging event.
+
+
+
+
+ A fine-grained debug message, typically capturing the flow through the application.
+
+
+
+
+ All events should be logged.
+
+
+
+
+
+
+ Appends log events.
+An Appender can contain a `Layout` if applicable as well as an `ErrorHandler`.
+Typical Appender implementations coordinate with an implementation of `AbstractManager` to handle external resources such as streams, connections, and other shared state.
+As Appenders are plugins, concrete implementations need to be annotated with `@Plugin` and need to provide a static factory method annotated with `@PluginFactory`.
+
+Most core plugins are written using a related Manager class that handle the actual task of serializing a `LogEvent` to some output location.
+For instance, many Appenders can take advantage of the `@OutputStreamManager` class.
+
+It is recommended that Appenders don't do any heavy lifting since there can be many instances of the class being used at any given time.
+When resources require locking (e.g., through `@FileLock`), it is important to isolate synchronized code to prevent concurrency issues.
+
+
+
+
+
+
+
+ Interface that must be implemented to allow custom event filtering.
+It is highly recommended that applications make use of the filters provided with this Log4j Core before creating their own.
+
+This interface supports "global" filters (i.e. - all events must pass through them first), attached to specific loggers and associated with Appenders.
+It is recommended that, where possible, `Filter` implementations create a generic filtering method that can be called from any of the filter methods.
+
+
+
+
+
+
+
+ The result that can returned from a filter method call.
+
+
+
+
+ The event will be processed without further filtering based on the log Level.
+
+
+
+
+ No decision could be made, further filtering should occur.
+
+
+
+
+ The event should not be processed.
+
+
+
+
+
+
+ A layout is responsible for formatting a `LogEvent` as a string.
+
+
+
+
+
+
+
+ Write log events to the console.
+
+
+
+
+ A filter to apply to events before sending them to destination.
+
+
+
+
+ The layout to be used with the appender.
+
+
+
+
+
+ The name of the appender used in appender references.
+Must be unique.
+
+
+
+
+ If set to `false` logging exceptions will be forwarded to the caller.
+
+
+
+
+ If set to `true` (default) the appender will buffer messages before sending them.
+This attribute is ignored if `immediateFlush` is set to `true`.
+
+
+
+
+ Size in bytes of the appender's buffer.
+
+
+
+
+ If set to `true`, the appender flushes the output stream at each message and
+buffering is disabled regardless of the value of `bufferedIo`.
+
+
+
+
+ Specifies the target of the appender.
+
+
+
+
+
+ Specifies the target of a console appender.
+
+
+
+
+ Logs to the standard output.
+
+
+
+
+ Logs to the standard error.
+
+
+
+
+
+
+ Reference to an appender defined in the `Appenders` section.
+
+
+
+
+ The filter to use.
+
+
+
+
+
+ The name of an appender.
+
+
+
+
+ The level to filter against.
+
+
+
+
+
+ Reference to an appender defined in the `Appenders` section.
+
+
+
+
+
+
+
+
+ A wrapper for a list of appenders.
+
+
+
+
+ A list of appender.
+
+
+
+
+
+
+ A Log4j 2.x configuration contains many components, two of them `Appenders` and `Loggers` are required.
+
+
+
+
+ Wrapper element for a list of properties.
+
+If present, this element must be the **first** child of the configuration.
+
+
+
+
+ Wrapper element for a list of appenders.
+
+
+
+
+ Wrapper element for a list of custom levels.
+
+
+
+
+ Wrapper element for a list of logger configurations.
+
+
+
+
+
+ If set it should contain the name of an `Advertiser` plugin
+(cf. https://logging.apache.org/log4j/2.x/manual/configuration.html#chainsaw-can-automatically-process-your-log-files-advertising-ap[documentation] ).
+Log4j Core provides a single implementation based on JmDNS called `MulticastDns`.
+
+
+
+
+ Specifies the destination for StatusLogger events.
+The possible values are:
+* `out` for using standard out (default),
+* `err` for using standard error,
+* a string that is interpreted in order as URI, URL or the path to a local file.
+
+If the provided value is invalid, then the default destination of standard out will be used.
+
+
+
+
+ Name of the configuration.
+
+
+
+
+ Number of seconds between polls for configuration changes.
+
+
+
+
+ The name of a classpath resource to use to validate the configuration.
+
+
+
+
+ Specifies whether Log4j should automatically shut down when the JVM shuts down.
+Possible values:
+* `enable`: to enable unconditionally the hook,
+* `disable`: to disable unconditionally the hook.
+
+The shutdown hook is enabled by default, unless Log4j Core detects the presence of the Servlet API.
+
+
+
+
+ Timeout in milliseconds of the logger context shut down.
+
+
+
+
+ Sets the level of the status logger.
+
+
+
+
+ If set to `true` the configuration file will be validated using an XML schema.
+
+
+
+
+ Specifies the verbosity level for the status logger.
+This only applies to classes configured by `StatusConfiguration#setVerboseClasses`.
+
+
+
+
+
+ Configures a custom level.
+
+
+
+ The name of the level.
+
+
+
+
+ An integer determines the strength of the custom level relative to the built-in levels.
+
+
+
+
+
+ A wrapper for a list of custom level configurations.
+
+
+
+
+ A list of custom level configurations.
+
+
+
+
+
+
+ Configures a particular logger
+
+
+
+
+ A list of appenders to use with this logger.
+
+
+
+
+ A filter to filter events, before calling the appenders.
+
+
+
+
+
+ The name of the logger.
+
+
+
+
+ The level of the logger.
+
+
+
+
+ When set to `false` location information will **not** be computed.
+
+The default value depends on the logger context implementation: it is `false` for `AsyncLoggerContext` and `true` for `LoggerContext`.
+
+
+
+
+
+ Configures a particular logger
+
+
+
+
+
+
+
+
+ Configures the root logger
+
+
+
+
+ A list of appenders to use with this logger.
+
+
+
+
+ A filter to filter events, before calling the appenders.
+
+
+
+
+
+ The level of the logger.
+
+
+
+
+ When set to `false` location information will **not** be computed.
+
+The default value depends on the logger context implementation: it is `false` for `AsyncLoggerContext` and `true` for `LoggerContext`.
+
+
+
+
+
+ A wrapper for a list of logger configurations.
+
+
+
+
+ A list of logger configurations.
+
+
+
+
+
+
+ A wrapper for a list of properties.
+
+
+
+
+ A list of properties.
+
+
+
+
+
+
+ Represents a key/value pair in the configuration.
+
+
+
+ Name of the property.
+
+
+
+
+ Value of the property.
+
+
+
+
+
+ The BurstFilter is a logging filter that regulates logging traffic.
+Use this filter when you want to control the mean rate and maximum burst of log statements that can be sent to an appender.
+
+
+
+ Action to perform if the filter matches.
+
+
+
+
+ Action to perform if the filter does not match.
+
+
+
+
+ Log events less specific than this level are filtered, while events with level equal or more specific always match.
+
+
+
+
+ Sets the average number of events per second to allow.
+
+
+
+
+ Sets the maximum number of events that can occur before events are filtered for exceeding the average rate.
+
+
+
+
+
+ A flexible layout configurable with pattern string.
+
+The goal of this class is to {@link org.apache.logging.log4j.core.Layout#toByteArray format} a {@link LogEvent} and return the results.
+The format of the result depends on the _conversion pattern_.
+
+The conversion pattern is closely related to the conversion pattern of the printf function in C.
+A conversion pattern is composed of literal text and format control expressions called _conversion specifiers_.
+
+
+
+ The pattern to use to format log events.
+
+
+
+
\ No newline at end of file