From e07fb8360a89b70385ede95ae2b49cdd41d690aa Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 20 Dec 2023 21:17:17 +0100 Subject: [PATCH] Add Schema generator --- log4j-docgen/pom.xml | 39 ++ .../internal/DefaultSchemaGenerator.java | 305 ++++++++++ .../logging/log4j/docgen/package-info.java | 22 + .../logging/log4j/docgen/util/TypeLookup.java | 74 +++ .../log4j/docgen/util/package-info.java | 22 + .../log4j/docgen/xsd/package-info.java | 22 + log4j-docgen/src/main/mdo/plugins-model.xml | 96 +++- .../log4j/docgen/internal/configuration.xml | 165 ++++++ .../docgen/internal/SchemaGeneratorTest.java | 66 +++ .../resources/expected/xsd/log4j-config.xsd | 519 ++++++++++++++++++ 10 files changed, 1329 insertions(+), 1 deletion(-) create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/package-info.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/util/TypeLookup.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/util/package-info.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/xsd/package-info.java create mode 100644 log4j-docgen/src/main/resources/org/apache/logging/log4j/docgen/internal/configuration.xml create mode 100644 log4j-docgen/src/test/java/org/apache/logging/log4j/docgen/internal/SchemaGeneratorTest.java create mode 100644 log4j-docgen/src/test/resources/expected/xsd/log4j-config.xsd 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