vy commented on code in PR #88: URL: https://github.com/apache/logging-log4j-tools/pull/88#discussion_r1426428027
########## log4j-docgen/src/main/mdo/plugins.xml: ########## @@ -0,0 +1,306 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<model xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://codehaus-plexus.github.io/MODELLO/2.0.0" + 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"> + <id>plugins</id> + <name>PluginBundle</name> + <description>Documents a bundle of Log4j plugins.</description> + <versionDefinition> + <type>field</type> + <value>schemaVersion</value> + </versionDefinition> + <defaults> + <default> + <key>package</key> + <value>org.apache.logging.log4j.docgen.model</value> + </default> + </defaults> + + <classes> + <class rootElement="true"> + <name>PluginBundle</name> + <description>Documents a bundle of Log4j plugins.</description> + <fields> + <field xml.attribute="true" xml.tagName="version"> + <name>schemaVersion</name> + <type>String</type> + <required>true</required> + <description>The version of the schema used by the XML document.</description> + </field> + <field> + <name>groupId</name> + <type>String</type> + <description>The group id of the bundle.</description> + </field> + <field> + <name>artifactId</name> + <type>String</type> + <description>The artifact id of the bundle.</description> + </field> + <field> + <name>version</name> + <type>String</type> + <description>The version of the bundle.</description> + </field> + <field> + <name>description</name> + <association> + <type>Description</type> + </association> + <description>Description of the bundle.</description> + </field> + <field> + <name>enums</name> + <association> + <type>EnumType</type> + <multiplicity>*</multiplicity> + </association> + <description>A list of all enums used in properties.</description> + </field> + <field> + <name>plugins</name> + <association> + <type>PluginEntry</type> + <multiplicity>*</multiplicity> + </association> + <description>A list of all plugins in the bundle.</description> + </field> + </fields> + </class> + + <class xml.tagName="plugin"> + <name>PluginEntry</name> + <description>Describes the properties available to plugins.</description> + <fields> + <field xml.attribute="true"> + <name>name</name> + <type>String</type> + <required>true</required> + <description>The unique name of this plugin.</description> + </field> + <field xml.attribute="true"> + <name>namespace</name> + <type>String</type> + <required>true</required> + <description>The namespace of the plugin.</description> + </field> + <field xml.attribute="true"> + <name>className</name> + <type>String</type> + <required>true</required> + <description>Fully qualified name of the class implementing the plugin.</description> + </field> + <field xml.attribute="true"> + <name>deferChildren</name> + <type>boolean</type> + <defaultValue>false</defaultValue> + </field> + <field> + <name>description</name> + <association> + <type>Description</type> + </association> + <description>Description of the plugin.</description> + </field> + <field> + <name>keys</name> + <required>true</required> + <association> + <type>String</type> + <multiplicity>*</multiplicity> + </association> + <description> + The different keys (e.g. XML tag names) under which the plugin can be used. + </description> + </field> + <!-- These are necessary to find all possible nested components --> + <field> + <name>supertypes</name> + <association> + <type>String</type> + <multiplicity>*</multiplicity> + </association> + <description> + List of all the supertypes of a plugin. + </description> + </field> + <field> + <name>attributes</name> + <required>true</required> + <association> + <type>PluginAttribute</type> + <multiplicity>*</multiplicity> + </association> + <description>List of **all** the configuration attributes supported</description> + </field> + <field> + <name>elements</name> + <required>true</required> + <association> + <type>PluginElement</type> + <multiplicity>*</multiplicity> + </association> + <description>List of **all** possible nested components.</description> + </field> Review Comment: Attribute-vs-element distinction feels to me an implementation detail. That is, from the pov of the user, they are all configuration properties of the plugin. As a matter of fact, JSON-, YAML-, and `.properties`-formatted configurations cannot even accommodate the _attribute_ concept, for them, every property is an _element_. Do we really need two different types, i.e., attribute & element? Can't we use a single property/attribute/element/etc. to refer the both? ########## log4j-docgen/src/main/mdo/plugins.xml: ########## @@ -0,0 +1,306 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<model xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://codehaus-plexus.github.io/MODELLO/2.0.0" + 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"> + <id>plugins</id> + <name>PluginBundle</name> + <description>Documents a bundle of Log4j plugins.</description> + <versionDefinition> + <type>field</type> + <value>schemaVersion</value> + </versionDefinition> + <defaults> + <default> + <key>package</key> + <value>org.apache.logging.log4j.docgen.model</value> + </default> + </defaults> + + <classes> + <class rootElement="true"> + <name>PluginBundle</name> + <description>Documents a bundle of Log4j plugins.</description> Review Comment: What is _"a **bundle** of Log4j plugins"_? Does the _"bundle"_ have a particular meaning in this context? Or does it simply refer to a collection of Log4j plugins provided by a particular JAR? ########## log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java: ########## @@ -0,0 +1,351 @@ +/* + * 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.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import javax.xml.XMLConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import org.apache.logging.log4j.docgen.SchemaGenerator; +import org.apache.logging.log4j.docgen.model.Description; +import org.apache.logging.log4j.docgen.model.EnumType; +import org.apache.logging.log4j.docgen.model.EnumValue; +import org.apache.logging.log4j.docgen.model.PluginAttribute; +import org.apache.logging.log4j.docgen.model.PluginBundle; +import org.apache.logging.log4j.docgen.model.PluginElement; +import org.apache.logging.log4j.docgen.model.PluginEntry; +import org.apache.logging.log4j.docgen.model.io.stax.PluginBundleStaxReader; + +public class DefaultSchemaGenerator implements SchemaGenerator { + + private static final String PLUGIN_NAMESPACE = "Core"; + 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(Collection<PluginBundle> bundles, XMLStreamWriter writer) throws XMLStreamException { + try { + final PluginBundle configurationBundle = + new PluginBundleStaxReader().read(getClass().getResourceAsStream("configuration.xml")); + final Set<PluginBundle> extendedBundles = new HashSet<>(bundles); + extendedBundles.add(configurationBundle); + final TypeLookup lookup = new TypeLookup(extendedBundles); + writeSchema(lookup, writer); + } catch (IOException e) { + throw new XMLStreamException(e); + } + } + + private 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"); + + // Simple types + writeEnumTypes(lookup.getRequiredEnums(), writer); + + // Plugins + writePluginEntries(lookup, writer); + + // Interfaces + writeGroups(lookup, lookup.getRequiredGroups(), writer); + + writer.writeEndElement(); + writer.writeEndDocument(); + } + + private void writePluginEntries(final TypeLookup lookup, final XMLStreamWriter writer) throws XMLStreamException { + for (final PluginEntry entry : lookup.getPlugins()) { + writePluginEntry(lookup, entry, writer); + } + } + + private void writePluginEntry(final TypeLookup lookup, final PluginEntry entry, final XMLStreamWriter writer) + throws XMLStreamException { + writer.writeStartElement(XSD_NAMESPACE, "complexType"); + writer.writeAttribute("name", entry.getClassName()); + + writeDocumentation(entry.getDescription(), writer); + + final boolean hasSimpleContent = entry.getElements().isEmpty(); + + if (!hasSimpleContent) { + writer.writeStartElement(XSD_NAMESPACE, "sequence"); + for (final PluginElement element : entry.getElements()) { + writePluginElement(lookup, element, writer); + } + writer.writeEndElement(); + } + + for (final PluginAttribute attribute : entry.getAttributes()) { + writePluginAttribute(lookup, attribute, writer); + } + + writer.writeEndElement(); + } + + private void writePluginElement(final TypeLookup lookup, final PluginElement element, final XMLStreamWriter writer) + throws XMLStreamException { + final String type = element.getType(); + final String xmlType = lookup.getXmlType(type, false); + final PluginEntry entry = lookup.getPluginByType(type); + /* + * If some plugins have `type` as super type or if the type is unknown, + * we use a <group> element. + */ + if (lookup.getPluginsByGroup(type) != null || entry == null) { + writer.writeStartElement(XSD_NAMESPACE, "group"); + writer.writeAttribute("ref", xmlType); + writeMultiplicity(element.isRequired(), element.getMultiplicity(), writer); + writeDocumentation(element.getDescription(), writer); + writer.writeEndElement(); + } else { + final Set<String> actualKeys = new TreeSet<>(entry.getKeys()); + actualKeys.add(entry.getName()); + for (final String key : actualKeys) { + 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 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"); + } + } + + private 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", lookup.getXmlType(attribute.getType(), true)); + final Description description = attribute.getDescription(); + if (description != null) { + writeDocumentation(description, writer); + } + writer.writeEndElement(); + } + + private void writeDocumentation(final Description description, final XMLStreamWriter writer) + throws XMLStreamException { + writer.writeStartElement(XSD_NAMESPACE, "annotation"); + writer.writeStartElement(XSD_NAMESPACE, "documentation"); + writer.writeCharacters(normalizeAsciidoc(description.getText())); + writer.writeEndElement(); + writer.writeEndElement(); + } + + private String normalizeAsciidoc(final String text) { + final StringBuilder sb = new StringBuilder(); + for (final String line : text.split("\r?\n", -1)) { + sb.append(line.trim()).append('\n'); + } + final int length = sb.length(); + if (length > 0) { + sb.setLength(length - 1); + } + return sb.toString(); + } + + private void writeEnumTypes(final Collection<EnumType> types, final XMLStreamWriter writer) + throws XMLStreamException { + for (final EnumType type : types) { + writeEnumType(type, writer); + } + } + + private void writeGroups(final TypeLookup lookup, final Collection<String> groups, final XMLStreamWriter writer) + throws XMLStreamException { + for (final String group : groups) { + writeGroup( + lookup, + group, + Optional.ofNullable(lookup.getPluginsByGroup(group)).orElse(Collections.emptySet()), + writer); + } + } + + private void writeGroup( Review Comment: This method (and some others) can be `private static`. ########## log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java: ########## @@ -0,0 +1,351 @@ +/* + * 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.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import javax.xml.XMLConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import org.apache.logging.log4j.docgen.SchemaGenerator; +import org.apache.logging.log4j.docgen.model.Description; +import org.apache.logging.log4j.docgen.model.EnumType; +import org.apache.logging.log4j.docgen.model.EnumValue; +import org.apache.logging.log4j.docgen.model.PluginAttribute; +import org.apache.logging.log4j.docgen.model.PluginBundle; +import org.apache.logging.log4j.docgen.model.PluginElement; +import org.apache.logging.log4j.docgen.model.PluginEntry; +import org.apache.logging.log4j.docgen.model.io.stax.PluginBundleStaxReader; + +public class DefaultSchemaGenerator implements SchemaGenerator { + + private static final String PLUGIN_NAMESPACE = "Core"; + 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(Collection<PluginBundle> bundles, XMLStreamWriter writer) throws XMLStreamException { + try { + final PluginBundle configurationBundle = + new PluginBundleStaxReader().read(getClass().getResourceAsStream("configuration.xml")); + final Set<PluginBundle> extendedBundles = new HashSet<>(bundles); + extendedBundles.add(configurationBundle); + final TypeLookup lookup = new TypeLookup(extendedBundles); + writeSchema(lookup, writer); + } catch (IOException e) { + throw new XMLStreamException(e); + } + } + + private 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"); + + // Simple types + writeEnumTypes(lookup.getRequiredEnums(), writer); + + // Plugins + writePluginEntries(lookup, writer); + + // Interfaces + writeGroups(lookup, lookup.getRequiredGroups(), writer); + + writer.writeEndElement(); + writer.writeEndDocument(); + } + + private void writePluginEntries(final TypeLookup lookup, final XMLStreamWriter writer) throws XMLStreamException { + for (final PluginEntry entry : lookup.getPlugins()) { + writePluginEntry(lookup, entry, writer); + } + } + + private void writePluginEntry(final TypeLookup lookup, final PluginEntry entry, final XMLStreamWriter writer) + throws XMLStreamException { + writer.writeStartElement(XSD_NAMESPACE, "complexType"); + writer.writeAttribute("name", entry.getClassName()); + + writeDocumentation(entry.getDescription(), writer); + + final boolean hasSimpleContent = entry.getElements().isEmpty(); + + if (!hasSimpleContent) { + writer.writeStartElement(XSD_NAMESPACE, "sequence"); + for (final PluginElement element : entry.getElements()) { + writePluginElement(lookup, element, writer); + } + writer.writeEndElement(); + } + + for (final PluginAttribute attribute : entry.getAttributes()) { + writePluginAttribute(lookup, attribute, writer); + } + + writer.writeEndElement(); + } + + private void writePluginElement(final TypeLookup lookup, final PluginElement element, final XMLStreamWriter writer) + throws XMLStreamException { + final String type = element.getType(); + final String xmlType = lookup.getXmlType(type, false); + final PluginEntry entry = lookup.getPluginByType(type); + /* + * If some plugins have `type` as super type or if the type is unknown, + * we use a <group> element. + */ + if (lookup.getPluginsByGroup(type) != null || entry == null) { + writer.writeStartElement(XSD_NAMESPACE, "group"); + writer.writeAttribute("ref", xmlType); + writeMultiplicity(element.isRequired(), element.getMultiplicity(), writer); + writeDocumentation(element.getDescription(), writer); + writer.writeEndElement(); + } else { + final Set<String> actualKeys = new TreeSet<>(entry.getKeys()); + actualKeys.add(entry.getName()); + for (final String key : actualKeys) { + 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 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"); + } + } + + private 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", lookup.getXmlType(attribute.getType(), true)); + final Description description = attribute.getDescription(); + if (description != null) { + writeDocumentation(description, writer); + } + writer.writeEndElement(); + } + + private void writeDocumentation(final Description description, final XMLStreamWriter writer) + throws XMLStreamException { + writer.writeStartElement(XSD_NAMESPACE, "annotation"); + writer.writeStartElement(XSD_NAMESPACE, "documentation"); + writer.writeCharacters(normalizeAsciidoc(description.getText())); + writer.writeEndElement(); + writer.writeEndElement(); + } + + private String normalizeAsciidoc(final String text) { + final StringBuilder sb = new StringBuilder(); + for (final String line : text.split("\r?\n", -1)) { + sb.append(line.trim()).append('\n'); + } + final int length = sb.length(); + if (length > 0) { + sb.setLength(length - 1); + } + return sb.toString(); + } + + private void writeEnumTypes(final Collection<EnumType> types, final XMLStreamWriter writer) + throws XMLStreamException { + for (final EnumType type : types) { + writeEnumType(type, writer); + } + } + + private void writeGroups(final TypeLookup lookup, final Collection<String> groups, final XMLStreamWriter writer) + throws XMLStreamException { + for (final String group : groups) { + writeGroup( + lookup, + group, + Optional.ofNullable(lookup.getPluginsByGroup(group)).orElse(Collections.emptySet()), + writer); + } + } + + private void writeGroup( + final TypeLookup lookup, + final String groups, + final Collection<PluginEntry> entries, + final XMLStreamWriter writer) + throws XMLStreamException { + writer.writeStartElement(XSD_NAMESPACE, "group"); + writer.writeAttribute("name", groups + ".group"); + writer.writeStartElement(XSD_NAMESPACE, "choice"); + + for (final PluginEntry entry : entries) { + final Set<String> actualKeys = new TreeSet<>(entry.getKeys()); + actualKeys.add(entry.getName()); + for (final String key : actualKeys) { + writer.writeEmptyElement(XSD_NAMESPACE, "element"); + writer.writeAttribute("name", key); + writer.writeAttribute("type", LOG4J_PREFIX + ":" + entry.getClassName()); + } + } + + writer.writeEndElement(); + writer.writeEndElement(); + } + + private void writeEnumType(final EnumType 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 EnumValue value : type.getValues()) { + writeEnumValue(value, writer); + } + + writer.writeEndElement(); + writer.writeEndElement(); + } + + private void writeEnumValue(final EnumValue value, final XMLStreamWriter writer) throws XMLStreamException { + writer.writeStartElement(XSD_NAMESPACE, "enumeration"); + writer.writeAttribute("value", value.getName()); + + writeDocumentation(value, writer); + + writer.writeEndElement(); + } + + private static class TypeLookup { + + private final Map<String, EnumType> enumByName = new HashMap<>(); + private final Map<String, PluginEntry> pluginsByName = new TreeMap<>(); + private final Map<String, Set<PluginEntry>> pluginsByGroup = new HashMap<>(); + private final Set<EnumType> requiredEnums = new TreeSet<>(Comparator.comparing(EnumType::getClassName)); + private final Set<String> requiredGroups = new TreeSet<>(); + + public TypeLookup(final Collection<PluginBundle> bundles) { Review Comment: ```suggestion private TypeLookup(final Collection<PluginBundle> bundles) { ``` ########## log4j-docgen/src/main/mdo/plugins.xml: ########## @@ -0,0 +1,306 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<model xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://codehaus-plexus.github.io/MODELLO/2.0.0" + 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"> + <id>plugins</id> + <name>PluginBundle</name> + <description>Documents a bundle of Log4j plugins.</description> + <versionDefinition> + <type>field</type> + <value>schemaVersion</value> + </versionDefinition> + <defaults> + <default> + <key>package</key> + <value>org.apache.logging.log4j.docgen.model</value> + </default> Review Comment: What is the function of this configuration? ########## log4j-docgen/src/main/mdo/plugins.xml: ########## @@ -0,0 +1,306 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<model xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://codehaus-plexus.github.io/MODELLO/2.0.0" + 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"> + <id>plugins</id> + <name>PluginBundle</name> + <description>Documents a bundle of Log4j plugins.</description> + <versionDefinition> + <type>field</type> + <value>schemaVersion</value> + </versionDefinition> + <defaults> + <default> + <key>package</key> + <value>org.apache.logging.log4j.docgen.model</value> + </default> + </defaults> + + <classes> + <class rootElement="true"> + <name>PluginBundle</name> + <description>Documents a bundle of Log4j plugins.</description> + <fields> + <field xml.attribute="true" xml.tagName="version"> + <name>schemaVersion</name> + <type>String</type> + <required>true</required> + <description>The version of the schema used by the XML document.</description> + </field> + <field> + <name>groupId</name> + <type>String</type> + <description>The group id of the bundle.</description> + </field> + <field> + <name>artifactId</name> + <type>String</type> + <description>The artifact id of the bundle.</description> + </field> + <field> + <name>version</name> + <type>String</type> + <description>The version of the bundle.</description> + </field> + <field> + <name>description</name> + <association> + <type>Description</type> + </association> + <description>Description of the bundle.</description> + </field> + <field> + <name>enums</name> + <association> + <type>EnumType</type> + <multiplicity>*</multiplicity> + </association> + <description>A list of all enums used in properties.</description> + </field> + <field> + <name>plugins</name> + <association> + <type>PluginEntry</type> + <multiplicity>*</multiplicity> + </association> + <description>A list of all plugins in the bundle.</description> + </field> + </fields> + </class> + + <class xml.tagName="plugin"> + <name>PluginEntry</name> + <description>Describes the properties available to plugins.</description> + <fields> + <field xml.attribute="true"> + <name>name</name> + <type>String</type> + <required>true</required> + <description>The unique name of this plugin.</description> + </field> + <field xml.attribute="true"> + <name>namespace</name> + <type>String</type> + <required>true</required> + <description>The namespace of the plugin.</description> + </field> + <field xml.attribute="true"> + <name>className</name> + <type>String</type> + <required>true</required> + <description>Fully qualified name of the class implementing the plugin.</description> + </field> + <field xml.attribute="true"> + <name>deferChildren</name> + <type>boolean</type> + <defaultValue>false</defaultValue> + </field> + <field> + <name>description</name> + <association> + <type>Description</type> + </association> + <description>Description of the plugin.</description> + </field> + <field> + <name>keys</name> + <required>true</required> + <association> + <type>String</type> + <multiplicity>*</multiplicity> + </association> + <description> + The different keys (e.g. XML tag names) under which the plugin can be used. + </description> + </field> + <!-- These are necessary to find all possible nested components --> + <field> + <name>supertypes</name> + <association> + <type>String</type> + <multiplicity>*</multiplicity> + </association> + <description> + List of all the supertypes of a plugin. + </description> + </field> + <field> + <name>attributes</name> + <required>true</required> + <association> + <type>PluginAttribute</type> + <multiplicity>*</multiplicity> + </association> + <description>List of **all** the configuration attributes supported</description> + </field> + <field> + <name>elements</name> + <required>true</required> + <association> + <type>PluginElement</type> + <multiplicity>*</multiplicity> + </association> + <description>List of **all** possible nested components.</description> + </field> + </fields> + </class> + + <class xml.tagName="attribute"> + <name>PluginAttribute</name> + <description>A scalar configuration value for the plugin.</description> + <fields> + <field xml.attribute="true"> + <name>name</name> + <type>String</type> + <required>true</required> + <description>The name of the property.</description> + </field> + <field xml.attribute="true"> + <name>type</name> + <type>String</type> + <defaultValue>java.lang.String</defaultValue> + <description>The Java name of this attribute's type, e.g. `boolean`, `java.lang.String`, + fully qualified name of an enum. The type must be an enum or must have a type converter. + </description> + </field> + <field xml.attribute="true"> + <name>required</name> + <type>boolean</type> + <defaultValue>false</defaultValue> + <description>If set to `true` the attribute is required.</description> + </field> + <field xml.attribute="true"> + <name>defaultValue</name> + <type>String</type> + <description>The default value of this attribute as string.</description> + </field> + <field xml.attribute="true"> + <name>defaultProperty</name> + <type>String</type> + <description>The Log4j property that contains the default value of this attribute.</description> + </field> + <field> + <name>description</name> + <association> + <type>Description</type> + </association> + <description>A description of the property.</description> + </field> + </fields> + </class> + + <class xml.tagName="enum" xsd.compositor="sequence"> + <name>EnumType</name> + <description>Describes a Java enum type.</description> + <fields> + <field xml.attribute="true"> + <name>className</name> + <type>String</type> + <required>true</required> + <description>The Java class name of the enum.</description> + </field> + <field> + <name>description</name> + <association> + <type>Description</type> + </association> + <description>An HTML description of the property.</description> + </field> + <field> + <name>values</name> + <required>true</required> + <association xml.itemsStyle="flat"> + <type>EnumValue</type> + <multiplicity>*</multiplicity> + </association> + <description>The possible values of this enum.</description> + </field> + </fields> + </class> + + <class xml.tagName="description"> + <name>Description</name> + <description>General documentation tag.</description> + <fields> + <field xml.attribute="true"> + <name>format</name> + <type>String</type> + <defaultValue>asciidoc</defaultValue> + <description>Format used by the documentation text. Currently it **must** be "asciidoc". Review Comment: ```suggestion <description>Format used by the documentation text. Currently it **must** be `asciidoc`. ``` ########## log4j-docgen/src/main/mdo/plugins.xml: ########## @@ -0,0 +1,306 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<model xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://codehaus-plexus.github.io/MODELLO/2.0.0" + 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"> + <id>plugins</id> + <name>PluginBundle</name> + <description>Documents a bundle of Log4j plugins.</description> + <versionDefinition> + <type>field</type> + <value>schemaVersion</value> + </versionDefinition> + <defaults> + <default> + <key>package</key> + <value>org.apache.logging.log4j.docgen.model</value> + </default> + </defaults> + + <classes> + <class rootElement="true"> + <name>PluginBundle</name> + <description>Documents a bundle of Log4j plugins.</description> + <fields> + <field xml.attribute="true" xml.tagName="version"> + <name>schemaVersion</name> + <type>String</type> + <required>true</required> + <description>The version of the schema used by the XML document.</description> + </field> + <field> + <name>groupId</name> + <type>String</type> + <description>The group id of the bundle.</description> + </field> + <field> + <name>artifactId</name> + <type>String</type> + <description>The artifact id of the bundle.</description> + </field> + <field> + <name>version</name> + <type>String</type> + <description>The version of the bundle.</description> + </field> + <field> + <name>description</name> + <association> + <type>Description</type> + </association> + <description>Description of the bundle.</description> + </field> + <field> + <name>enums</name> + <association> + <type>EnumType</type> + <multiplicity>*</multiplicity> + </association> + <description>A list of all enums used in properties.</description> + </field> + <field> + <name>plugins</name> + <association> + <type>PluginEntry</type> + <multiplicity>*</multiplicity> + </association> + <description>A list of all plugins in the bundle.</description> + </field> + </fields> + </class> + + <class xml.tagName="plugin"> + <name>PluginEntry</name> + <description>Describes the properties available to plugins.</description> + <fields> + <field xml.attribute="true"> + <name>name</name> + <type>String</type> + <required>true</required> + <description>The unique name of this plugin.</description> + </field> + <field xml.attribute="true"> + <name>namespace</name> + <type>String</type> + <required>true</required> + <description>The namespace of the plugin.</description> + </field> + <field xml.attribute="true"> + <name>className</name> + <type>String</type> + <required>true</required> + <description>Fully qualified name of the class implementing the plugin.</description> + </field> + <field xml.attribute="true"> + <name>deferChildren</name> + <type>boolean</type> + <defaultValue>false</defaultValue> + </field> + <field> + <name>description</name> + <association> + <type>Description</type> + </association> + <description>Description of the plugin.</description> + </field> + <field> + <name>keys</name> + <required>true</required> + <association> + <type>String</type> + <multiplicity>*</multiplicity> + </association> + <description> + The different keys (e.g. XML tag names) under which the plugin can be used. + </description> + </field> + <!-- These are necessary to find all possible nested components --> + <field> + <name>supertypes</name> + <association> + <type>String</type> + <multiplicity>*</multiplicity> + </association> + <description> + List of all the supertypes of a plugin. + </description> + </field> + <field> + <name>attributes</name> + <required>true</required> + <association> + <type>PluginAttribute</type> + <multiplicity>*</multiplicity> + </association> + <description>List of **all** the configuration attributes supported</description> + </field> + <field> + <name>elements</name> + <required>true</required> + <association> + <type>PluginElement</type> + <multiplicity>*</multiplicity> + </association> + <description>List of **all** possible nested components.</description> + </field> + </fields> + </class> + + <class xml.tagName="attribute"> + <name>PluginAttribute</name> + <description>A scalar configuration value for the plugin.</description> + <fields> + <field xml.attribute="true"> + <name>name</name> + <type>String</type> + <required>true</required> + <description>The name of the property.</description> + </field> + <field xml.attribute="true"> + <name>type</name> + <type>String</type> + <defaultValue>java.lang.String</defaultValue> + <description>The Java name of this attribute's type, e.g. `boolean`, `java.lang.String`, + fully qualified name of an enum. The type must be an enum or must have a type converter. + </description> + </field> + <field xml.attribute="true"> + <name>required</name> + <type>boolean</type> + <defaultValue>false</defaultValue> + <description>If set to `true` the attribute is required.</description> + </field> + <field xml.attribute="true"> + <name>defaultValue</name> + <type>String</type> + <description>The default value of this attribute as string.</description> + </field> + <field xml.attribute="true"> + <name>defaultProperty</name> + <type>String</type> + <description>The Log4j property that contains the default value of this attribute.</description> + </field> + <field> + <name>description</name> + <association> + <type>Description</type> + </association> + <description>A description of the property.</description> + </field> + </fields> + </class> + + <class xml.tagName="enum" xsd.compositor="sequence"> + <name>EnumType</name> + <description>Describes a Java enum type.</description> + <fields> + <field xml.attribute="true"> + <name>className</name> + <type>String</type> + <required>true</required> + <description>The Java class name of the enum.</description> + </field> + <field> + <name>description</name> + <association> + <type>Description</type> + </association> + <description>An HTML description of the property.</description> + </field> + <field> + <name>values</name> + <required>true</required> + <association xml.itemsStyle="flat"> + <type>EnumValue</type> + <multiplicity>*</multiplicity> + </association> + <description>The possible values of this enum.</description> + </field> + </fields> + </class> + + <class xml.tagName="description"> + <name>Description</name> + <description>General documentation tag.</description> + <fields> + <field xml.attribute="true"> + <name>format</name> + <type>String</type> + <defaultValue>asciidoc</defaultValue> + <description>Format used by the documentation text. Currently it **must** be "asciidoc". + </description> + </field> + <field xml.content="true"> + <name>text</name> + <type>String</type> + <required>true</required> + <description>Description of the element.</description> + </field> + </fields> + </class> + + <class xml.tagName="value"> + <name>EnumValue</name> + <superClass>Description</superClass> + <description>One of the possible values of an enum.</description> + <fields> + <field xml.attribute="true"> + <name>name</name> + <type>String</type> + <required>true</required> + <description>The name of the property.</description> + </field> + </fields> + </class> + + <class xml.tagName="element"> + <name>PluginElement</name> + <description>Describes a nested configuration component.</description> + <fields> + <field xml.attribute="true"> + <name>multiplicity</name> + <type>String</type> + <defaultValue>1</defaultValue> + <description>Either '*' if the field accepts a collection of elements or '1'.</description> Review Comment: ```suggestion <description>Either `*`, if the field accepts a collection of elements, or `1`.</description> ``` ########## log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java: ########## @@ -0,0 +1,351 @@ +/* + * 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.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import javax.xml.XMLConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import org.apache.logging.log4j.docgen.SchemaGenerator; +import org.apache.logging.log4j.docgen.model.Description; +import org.apache.logging.log4j.docgen.model.EnumType; +import org.apache.logging.log4j.docgen.model.EnumValue; +import org.apache.logging.log4j.docgen.model.PluginAttribute; +import org.apache.logging.log4j.docgen.model.PluginBundle; +import org.apache.logging.log4j.docgen.model.PluginElement; +import org.apache.logging.log4j.docgen.model.PluginEntry; +import org.apache.logging.log4j.docgen.model.io.stax.PluginBundleStaxReader; + +public class DefaultSchemaGenerator implements SchemaGenerator { + + private static final String PLUGIN_NAMESPACE = "Core"; + 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(Collection<PluginBundle> bundles, XMLStreamWriter writer) throws XMLStreamException { + try { + final PluginBundle configurationBundle = + new PluginBundleStaxReader().read(getClass().getResourceAsStream("configuration.xml")); + final Set<PluginBundle> extendedBundles = new HashSet<>(bundles); + extendedBundles.add(configurationBundle); + final TypeLookup lookup = new TypeLookup(extendedBundles); + writeSchema(lookup, writer); + } catch (IOException e) { + throw new XMLStreamException(e); + } + } + + private 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"); + + // Simple types + writeEnumTypes(lookup.getRequiredEnums(), writer); + + // Plugins + writePluginEntries(lookup, writer); + + // Interfaces + writeGroups(lookup, lookup.getRequiredGroups(), writer); + + writer.writeEndElement(); + writer.writeEndDocument(); + } + + private void writePluginEntries(final TypeLookup lookup, final XMLStreamWriter writer) throws XMLStreamException { + for (final PluginEntry entry : lookup.getPlugins()) { + writePluginEntry(lookup, entry, writer); + } + } + + private void writePluginEntry(final TypeLookup lookup, final PluginEntry entry, final XMLStreamWriter writer) + throws XMLStreamException { + writer.writeStartElement(XSD_NAMESPACE, "complexType"); + writer.writeAttribute("name", entry.getClassName()); + + writeDocumentation(entry.getDescription(), writer); + + final boolean hasSimpleContent = entry.getElements().isEmpty(); + + if (!hasSimpleContent) { + writer.writeStartElement(XSD_NAMESPACE, "sequence"); + for (final PluginElement element : entry.getElements()) { + writePluginElement(lookup, element, writer); + } + writer.writeEndElement(); + } + + for (final PluginAttribute attribute : entry.getAttributes()) { + writePluginAttribute(lookup, attribute, writer); + } + + writer.writeEndElement(); + } + + private void writePluginElement(final TypeLookup lookup, final PluginElement element, final XMLStreamWriter writer) + throws XMLStreamException { + final String type = element.getType(); + final String xmlType = lookup.getXmlType(type, false); + final PluginEntry entry = lookup.getPluginByType(type); + /* + * If some plugins have `type` as super type or if the type is unknown, + * we use a <group> element. + */ + if (lookup.getPluginsByGroup(type) != null || entry == null) { + writer.writeStartElement(XSD_NAMESPACE, "group"); + writer.writeAttribute("ref", xmlType); + writeMultiplicity(element.isRequired(), element.getMultiplicity(), writer); + writeDocumentation(element.getDescription(), writer); + writer.writeEndElement(); + } else { + final Set<String> actualKeys = new TreeSet<>(entry.getKeys()); + actualKeys.add(entry.getName()); + for (final String key : actualKeys) { + 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 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"); + } + } + + private 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", lookup.getXmlType(attribute.getType(), true)); + final Description description = attribute.getDescription(); + if (description != null) { + writeDocumentation(description, writer); + } + writer.writeEndElement(); + } + + private void writeDocumentation(final Description description, final XMLStreamWriter writer) + throws XMLStreamException { + writer.writeStartElement(XSD_NAMESPACE, "annotation"); + writer.writeStartElement(XSD_NAMESPACE, "documentation"); + writer.writeCharacters(normalizeAsciidoc(description.getText())); + writer.writeEndElement(); + writer.writeEndElement(); + } + + private String normalizeAsciidoc(final String text) { + final StringBuilder sb = new StringBuilder(); + for (final String line : text.split("\r?\n", -1)) { + sb.append(line.trim()).append('\n'); + } + final int length = sb.length(); + if (length > 0) { + sb.setLength(length - 1); + } + return sb.toString(); + } + + private void writeEnumTypes(final Collection<EnumType> types, final XMLStreamWriter writer) + throws XMLStreamException { + for (final EnumType type : types) { + writeEnumType(type, writer); + } + } + + private void writeGroups(final TypeLookup lookup, final Collection<String> groups, final XMLStreamWriter writer) + throws XMLStreamException { + for (final String group : groups) { + writeGroup( + lookup, + group, + Optional.ofNullable(lookup.getPluginsByGroup(group)).orElse(Collections.emptySet()), + writer); + } + } + + private void writeGroup( + final TypeLookup lookup, Review Comment: `lookup` is not used. ########## log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java: ########## @@ -0,0 +1,351 @@ +/* + * 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.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import javax.xml.XMLConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import org.apache.logging.log4j.docgen.SchemaGenerator; +import org.apache.logging.log4j.docgen.model.Description; +import org.apache.logging.log4j.docgen.model.EnumType; +import org.apache.logging.log4j.docgen.model.EnumValue; +import org.apache.logging.log4j.docgen.model.PluginAttribute; +import org.apache.logging.log4j.docgen.model.PluginBundle; +import org.apache.logging.log4j.docgen.model.PluginElement; +import org.apache.logging.log4j.docgen.model.PluginEntry; +import org.apache.logging.log4j.docgen.model.io.stax.PluginBundleStaxReader; + +public class DefaultSchemaGenerator implements SchemaGenerator { + + private static final String PLUGIN_NAMESPACE = "Core"; + 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(Collection<PluginBundle> bundles, XMLStreamWriter writer) throws XMLStreamException { + try { + final PluginBundle configurationBundle = + new PluginBundleStaxReader().read(getClass().getResourceAsStream("configuration.xml")); + final Set<PluginBundle> extendedBundles = new HashSet<>(bundles); Review Comment: There are several places where we use `HashSet`, `HashMap`, etc. in this class. Shouldn't we be using deterministic alternatives (`TreeSet`, `TreeMap`, etc.) instead for reproducibility? ########## log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java: ########## @@ -0,0 +1,351 @@ +/* + * 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.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import javax.xml.XMLConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import org.apache.logging.log4j.docgen.SchemaGenerator; +import org.apache.logging.log4j.docgen.model.Description; +import org.apache.logging.log4j.docgen.model.EnumType; +import org.apache.logging.log4j.docgen.model.EnumValue; +import org.apache.logging.log4j.docgen.model.PluginAttribute; +import org.apache.logging.log4j.docgen.model.PluginBundle; +import org.apache.logging.log4j.docgen.model.PluginElement; +import org.apache.logging.log4j.docgen.model.PluginEntry; +import org.apache.logging.log4j.docgen.model.io.stax.PluginBundleStaxReader; + +public class DefaultSchemaGenerator implements SchemaGenerator { + + private static final String PLUGIN_NAMESPACE = "Core"; + 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(Collection<PluginBundle> bundles, XMLStreamWriter writer) throws XMLStreamException { + try { + final PluginBundle configurationBundle = + new PluginBundleStaxReader().read(getClass().getResourceAsStream("configuration.xml")); + final Set<PluginBundle> extendedBundles = new HashSet<>(bundles); + extendedBundles.add(configurationBundle); + final TypeLookup lookup = new TypeLookup(extendedBundles); + writeSchema(lookup, writer); + } catch (IOException e) { + throw new XMLStreamException(e); + } + } + + private 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"); + + // Simple types + writeEnumTypes(lookup.getRequiredEnums(), writer); + + // Plugins + writePluginEntries(lookup, writer); + + // Interfaces + writeGroups(lookup, lookup.getRequiredGroups(), writer); + + writer.writeEndElement(); + writer.writeEndDocument(); + } + + private void writePluginEntries(final TypeLookup lookup, final XMLStreamWriter writer) throws XMLStreamException { + for (final PluginEntry entry : lookup.getPlugins()) { + writePluginEntry(lookup, entry, writer); + } + } + + private void writePluginEntry(final TypeLookup lookup, final PluginEntry entry, final XMLStreamWriter writer) + throws XMLStreamException { + writer.writeStartElement(XSD_NAMESPACE, "complexType"); + writer.writeAttribute("name", entry.getClassName()); + + writeDocumentation(entry.getDescription(), writer); + + final boolean hasSimpleContent = entry.getElements().isEmpty(); + + if (!hasSimpleContent) { + writer.writeStartElement(XSD_NAMESPACE, "sequence"); + for (final PluginElement element : entry.getElements()) { + writePluginElement(lookup, element, writer); + } + writer.writeEndElement(); + } + + for (final PluginAttribute attribute : entry.getAttributes()) { + writePluginAttribute(lookup, attribute, writer); + } + + writer.writeEndElement(); + } + + private void writePluginElement(final TypeLookup lookup, final PluginElement element, final XMLStreamWriter writer) + throws XMLStreamException { + final String type = element.getType(); + final String xmlType = lookup.getXmlType(type, false); + final PluginEntry entry = lookup.getPluginByType(type); + /* + * If some plugins have `type` as super type or if the type is unknown, + * we use a <group> element. + */ + if (lookup.getPluginsByGroup(type) != null || entry == null) { + writer.writeStartElement(XSD_NAMESPACE, "group"); + writer.writeAttribute("ref", xmlType); + writeMultiplicity(element.isRequired(), element.getMultiplicity(), writer); + writeDocumentation(element.getDescription(), writer); + writer.writeEndElement(); + } else { + final Set<String> actualKeys = new TreeSet<>(entry.getKeys()); + actualKeys.add(entry.getName()); + for (final String key : actualKeys) { + 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 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"); + } + } + + private 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", lookup.getXmlType(attribute.getType(), true)); + final Description description = attribute.getDescription(); + if (description != null) { + writeDocumentation(description, writer); + } + writer.writeEndElement(); + } + + private void writeDocumentation(final Description description, final XMLStreamWriter writer) + throws XMLStreamException { + writer.writeStartElement(XSD_NAMESPACE, "annotation"); + writer.writeStartElement(XSD_NAMESPACE, "documentation"); + writer.writeCharacters(normalizeAsciidoc(description.getText())); + writer.writeEndElement(); + writer.writeEndElement(); + } + + private String normalizeAsciidoc(final String text) { + final StringBuilder sb = new StringBuilder(); + for (final String line : text.split("\r?\n", -1)) { + sb.append(line.trim()).append('\n'); + } + final int length = sb.length(); + if (length > 0) { + sb.setLength(length - 1); + } + return sb.toString(); + } + + private void writeEnumTypes(final Collection<EnumType> types, final XMLStreamWriter writer) + throws XMLStreamException { + for (final EnumType type : types) { + writeEnumType(type, writer); + } + } + + private void writeGroups(final TypeLookup lookup, final Collection<String> groups, final XMLStreamWriter writer) + throws XMLStreamException { + for (final String group : groups) { + writeGroup( + lookup, + group, + Optional.ofNullable(lookup.getPluginsByGroup(group)).orElse(Collections.emptySet()), + writer); + } + } + + private void writeGroup( + final TypeLookup lookup, + final String groups, + final Collection<PluginEntry> entries, + final XMLStreamWriter writer) + throws XMLStreamException { + writer.writeStartElement(XSD_NAMESPACE, "group"); + writer.writeAttribute("name", groups + ".group"); + writer.writeStartElement(XSD_NAMESPACE, "choice"); + + for (final PluginEntry entry : entries) { + final Set<String> actualKeys = new TreeSet<>(entry.getKeys()); + actualKeys.add(entry.getName()); + for (final String key : actualKeys) { + writer.writeEmptyElement(XSD_NAMESPACE, "element"); + writer.writeAttribute("name", key); + writer.writeAttribute("type", LOG4J_PREFIX + ":" + entry.getClassName()); + } + } + + writer.writeEndElement(); + writer.writeEndElement(); + } + + private void writeEnumType(final EnumType 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 EnumValue value : type.getValues()) { + writeEnumValue(value, writer); + } + + writer.writeEndElement(); + writer.writeEndElement(); + } + + private void writeEnumValue(final EnumValue value, final XMLStreamWriter writer) throws XMLStreamException { + writer.writeStartElement(XSD_NAMESPACE, "enumeration"); + writer.writeAttribute("value", value.getName()); + + writeDocumentation(value, writer); + + writer.writeEndElement(); + } + + private static class TypeLookup { Review Comment: ```suggestion private static final class TypeLookup { ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
