diff --git a/.github/workflows/dep-diff-pull_request.yml b/.github/workflows/dep-diff-pull_request.yml index 2f783f8f428..15d012f5154 100644 --- a/.github/workflows/dep-diff-pull_request.yml +++ b/.github/workflows/dep-diff-pull_request.yml @@ -38,7 +38,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 11 + java-version: 17 # Run the caching against the base version only - name: Cache local Maven repository diff --git a/README.md b/README.md index ed26e75007f..48b077f5f31 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Building Prerequisites: -* JDK 11 or newer - check `java -version` +* JDK 17 or newer - check `java -version` * Maven 3.6.0 or newer - check `mvn -v` To build with your own Maven installation: @@ -63,7 +63,7 @@ Contributing Using Eclipse ------------- 1. Install the latest version of Eclipse. -2. Make sure Xmx in Eclipse.ini is at least 1280M, and it's using java 11 +2. Make sure Xmx in Eclipse.ini is at least 1280M, and it's using java 17 3. Launch Eclipse and install the m2e plugin, make sure it uses your repo configs (get it from: https://www.eclipse.org/m2e/ or install "Maven Integration for Eclipse" from the Eclipse Marketplace). diff --git a/controller/src/main/java/org/jboss/as/controller/AttributeParser.java b/controller/src/main/java/org/jboss/as/controller/AttributeParser.java index 3a4583df2c9..70e7de28a08 100644 --- a/controller/src/main/java/org/jboss/as/controller/AttributeParser.java +++ b/controller/src/main/java/org/jboss/as/controller/AttributeParser.java @@ -7,11 +7,13 @@ import java.util.Collections; + import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import org.jboss.as.controller.operations.validation.ParameterValidator; import org.jboss.as.controller.parsing.ParseUtils; +import org.jboss.as.controller.xml.XMLCardinality; import org.jboss.dmr.ModelNode; import org.jboss.staxmapper.XMLExtendedStreamReader; @@ -96,6 +98,15 @@ public String getXmlName(final AttributeDefinition attribute){ return attribute.getXmlName(); } + /** + * Returns the cardinality of the XML particle for the specified attribute. + * @param attribute an attribute definition + * @return the cardinality of the XML particle for the specified attribute. + */ + public XMLCardinality getCardinality(AttributeDefinition attribute) { + return this.isParseAsElement() ? (attribute.isNillable() ? XMLCardinality.Single.OPTIONAL : XMLCardinality.Single.REQUIRED) : XMLCardinality.NONE; + } + public static final AttributeParser SIMPLE = new AttributeParser() { }; diff --git a/controller/src/main/java/org/jboss/as/controller/AttributeParsers.java b/controller/src/main/java/org/jboss/as/controller/AttributeParsers.java index 5a24c2a2913..ee770c50a25 100644 --- a/controller/src/main/java/org/jboss/as/controller/AttributeParsers.java +++ b/controller/src/main/java/org/jboss/as/controller/AttributeParsers.java @@ -19,6 +19,7 @@ import javax.xml.stream.XMLStreamException; import org.jboss.as.controller.parsing.ParseUtils; +import org.jboss.as.controller.xml.XMLCardinality; import org.jboss.dmr.ModelNode; import org.jboss.staxmapper.XMLExtendedStreamReader; @@ -95,39 +96,35 @@ public void parseElement(AttributeDefinition attribute, XMLExtendedStreamReader assert attribute instanceof MapAttributeDefinition; MapAttributeDefinition mapAttribute = (MapAttributeDefinition) attribute; - operation.get(attribute.getName()).setEmptyObject();//create empty attribute to address WFCORE-1448 + if (!operation.hasDefined(attribute.getName())) { + // Create empty attribute to address WFCORE-1448 + operation.get(attribute.getName()).setEmptyObject(); + } if (wrapElement) { if (!reader.getLocalName().equals(wrapper)) { throw ParseUtils.unexpectedElement(reader, Collections.singleton(wrapper)); - } else { - // allow empty properties list - if (reader.nextTag() == END_ELEMENT) { - return; - } } - } - - do { - if (elementName.equals(reader.getLocalName())) { - //real parsing happens - parseSingleElement(mapAttribute, reader, operation); - } else { - throw ParseUtils.unexpectedElement(reader, Collections.singleton(elementName)); + while (reader.hasNext() && (reader.nextTag() != END_ELEMENT)) { + this.readElement(mapAttribute, reader, operation); } + } else { + this.readElement(mapAttribute, reader, operation); + } + } - } while (reader.hasNext() && reader.nextTag() != END_ELEMENT && reader.getLocalName().equals(elementName)); - - if (wrapElement) { - // To exit the do loop either we hit an END_ELEMENT or a START_ELEMENT not for 'elementName' - // The latter means a bad document - if (reader.getEventType() != END_ELEMENT) { - throw ParseUtils.unexpectedElement(reader, Collections.singleton(elementName)); - } + private void readElement(MapAttributeDefinition attribute, XMLExtendedStreamReader reader, ModelNode operation) throws XMLStreamException { + if (!reader.getLocalName().equals(this.elementName)) { + throw ParseUtils.unexpectedElement(reader, Collections.singleton(this.elementName)); } + this.parseSingleElement(attribute, reader, operation); } public abstract void parseSingleElement(MapAttributeDefinition attribute, XMLExtendedStreamReader reader, ModelNode operation) throws XMLStreamException; + @Override + public XMLCardinality getCardinality(AttributeDefinition attribute) { + return (this.wrapperElement != null) ? (attribute.isNillable() ? XMLCardinality.Single.OPTIONAL : XMLCardinality.Single.REQUIRED) : (attribute.isNillable() ? XMLCardinality.Unbounded.OPTIONAL : XMLCardinality.Unbounded.REQUIRED); + } } @@ -192,7 +189,6 @@ public void parseSingleElement(MapAttributeDefinition attribute, XMLExtendedStre String key = reader.getAttributeValue(null, keyAttributeName); ModelNode op = operation.get(attribute.getName(), key); parseEmbeddedElement(objectType, reader, op, keyAttributeName); - ParseUtils.requireNoContent(reader); } } @@ -218,9 +214,6 @@ public void parseElement(AttributeDefinition attribute, XMLExtendedStreamReader } else { throw ParseUtils.unexpectedElement(reader, Collections.singleton(attribute.getXmlName())); } - if (!reader.isEndElement()) { - ParseUtils.requireNoContent(reader); - } } static void parseEmbeddedElement(ObjectTypeAttributeDefinition attribute, XMLExtendedStreamReader reader, ModelNode op, String... additionalExpectedAttributes) throws XMLStreamException { @@ -261,8 +254,9 @@ static void parseEmbeddedElement(ObjectTypeAttributeDefinition attribute, XMLExt throw ParseUtils.unexpectedElement(reader, attributeElements.keySet()); } } + } else { + ParseUtils.requireNoContent(reader); } - } } @@ -297,9 +291,6 @@ public void parseElement(AttributeDefinition attribute, XMLExtendedStreamReader } else { throw ParseUtils.unexpectedElement(reader, Collections.singleton(objectType.getXmlName())); } - if (!reader.isEndElement()) { - ParseUtils.requireNoContent(reader); - } } operation.get(attribute.getName()).set(listValue); } @@ -327,9 +318,11 @@ public void parseElement(AttributeDefinition attribute, XMLExtendedStreamReader } else { throw ParseUtils.unexpectedElement(reader, Collections.singleton(xmlName)); } - if (!reader.isEndElement()) { - ParseUtils.requireNoContent(reader); - } + } + + @Override + public XMLCardinality getCardinality(AttributeDefinition attribute) { + return attribute.isNillable() ? XMLCardinality.Unbounded.OPTIONAL : XMLCardinality.Unbounded.REQUIRED; } } @@ -348,6 +341,11 @@ public void parseElement(AttributeDefinition ad, XMLExtendedStreamReader reader, addPermissionMapper.get(ad.getName()).add(name); ParseUtils.requireNoContent(reader); } + + @Override + public XMLCardinality getCardinality(AttributeDefinition attribute) { + return attribute.isNillable() ? XMLCardinality.Unbounded.OPTIONAL : XMLCardinality.Unbounded.REQUIRED; + } } static AttributeParser getObjectMapAttributeParser(String keyElementName) { diff --git a/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLDescription.java b/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLDescription.java index 17f62cd3755..f8e66bf6401 100644 --- a/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLDescription.java +++ b/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLDescription.java @@ -5,132 +5,59 @@ package org.jboss.as.controller; -import static javax.xml.stream.XMLStreamConstants.END_ELEMENT; -import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; -import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME; -import static org.jboss.as.controller.parsing.ParseUtils.unexpectedElement; - +import java.util.AbstractList; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.xml.stream.XMLStreamConstants; +import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; -import org.jboss.as.controller.logging.ControllerLogger; import org.jboss.as.controller.operations.common.Util; -import org.jboss.as.controller.parsing.Element; -import org.jboss.as.controller.parsing.ParseUtils; +import org.jboss.as.controller.persistence.xml.ResourceXMLElement; +import org.jboss.as.controller.persistence.xml.ResourceXMLElementLocalName; +import org.jboss.as.controller.xml.QNameResolver; +import org.jboss.as.controller.xml.XMLCardinality; +import org.jboss.as.controller.xml.XMLContent; +import org.jboss.as.controller.xml.XMLElement; import org.jboss.dmr.ModelNode; import org.jboss.staxmapper.Namespace; import org.jboss.staxmapper.XMLExtendedStreamReader; import org.jboss.staxmapper.XMLExtendedStreamWriter; +import org.wildfly.common.function.Functions; /** * A representation of a resource as needed by the XML parser. * * @author Tomaz Cerar * @author Stuart Douglas + * @deprecated Superseded by {@link ResourceXMLElement}. */ +@Deprecated(forRemoval = true) public final class PersistentResourceXMLDescription implements ResourceParser, ResourceMarshaller { - private final PathElement pathElement; - private final String xmlElementName; - private final String xmlWrapperElement; - private final LinkedHashMap> attributesByGroup; - private final List children; - private final List marshallers; - private final Map attributeElements = new HashMap<>(); - private final boolean useValueAsElementName; - private final boolean noAddOperation; - private final AdditionalOperationsGenerator additionalOperationsGenerator; - private final LinkedHashMap customChildParsers; - private final String decoratorElement; - private boolean flushRequired = true; - private boolean childAlreadyRead = false; - private final Map attributeParsers; - private final Map attributeMarshallers; - private final boolean useElementsForGroups; - private final String namespaceURI; - private final Set attributeGroups; - private final String forcedName; - private final boolean marshallDefaultValues; - //name of the attribute that is used for wildcard elements - private final String nameAttributeName; - - - private PersistentResourceXMLDescription(PersistentResourceXMLBuilder builder) { - this.pathElement = builder.pathElement; - this.xmlElementName = builder.xmlElementName; - this.xmlWrapperElement = builder.xmlWrapperElement; - this.useElementsForGroups = builder.useElementsForGroups; - this.attributesByGroup = new LinkedHashMap<>(); - this.namespaceURI = builder.namespaceURI; - this.attributeGroups = new HashSet<>(); - if (useElementsForGroups) { - // Ensure we have a map for the default group even if there are no attributes so we don't NPE later - this.attributesByGroup.put(null, new LinkedHashMap<>()); - // Segregate attributes by group - for (AttributeDefinition ad : builder.attributeList) { - String adGroup = ad.getAttributeGroup(); - LinkedHashMap forGroup = this.attributesByGroup.get(adGroup); - if (forGroup == null) { - forGroup = new LinkedHashMap<>(); - this.attributesByGroup.put(adGroup, forGroup); - this.attributeGroups.add(adGroup); - } - String adXmlName = ad.getXmlName(); - forGroup.put(adXmlName, ad); - AttributeParser ap = builder.attributeParsers.getOrDefault(adXmlName, ad.getParser()); - if (ap != null && ap.isParseAsElement()) { - attributeElements.put(ap.getXmlName(ad), ad); - } + private final ResourceXMLElement element; - } - } else { - LinkedHashMap attrs = new LinkedHashMap<>(); - for (AttributeDefinition ad : builder.attributeList) { - attrs.put(ad.getXmlName(), ad); - AttributeParser ap = builder.attributeParsers.getOrDefault(ad.getXmlName(), ad.getParser()); - if (ap != null && ap.isParseAsElement()) { - attributeElements.put(ap.getXmlName(ad), ad); - } - } - // Ignore attribute-group, treat all as if they are in the default group - this.attributesByGroup.put(null, attrs); - } - this.children = new ArrayList<>(); - this.marshallers = builder.marshallers; - for (PersistentResourceXMLBuilder b : builder.childrenBuilders) { - PersistentResourceXMLDescription child = b.build(); - this.children.add(child); - this.marshallers.add(child); - } - this.children.addAll(builder.children); - this.useValueAsElementName = builder.useValueAsElementName; - this.noAddOperation = builder.noAddOperation; - this.additionalOperationsGenerator = builder.additionalOperationsGenerator; - this.attributeParsers = builder.attributeParsers; - this.attributeMarshallers = builder.attributeMarshallers; - this.forcedName = builder.forcedName; - this.marshallDefaultValues = builder.marshallDefaultValues; - this.nameAttributeName = builder.nameAttributeName; - this.customChildParsers = builder.customChildParsers; - this.decoratorElement = builder.decoratorElement; + private PersistentResourceXMLDescription(ResourceXMLElement element) { + this.element = element; } public PathElement getPathElement() { - return this.pathElement; + return this.element.getPathElement(); + } + + public ResourceXMLElement getXMLElement() { + return this.element; } /** @@ -140,388 +67,38 @@ public PathElement getPathElement() { * @param list list of operations where result will be put to. * @throws XMLStreamException if any error occurs while parsing */ + @Override public void parse(final XMLExtendedStreamReader reader, PathAddress parentAddress, List list) throws XMLStreamException { - if (decoratorElement != null) { - parseDecorator(reader, parentAddress, list); - return; - } - if (xmlWrapperElement != null) { - if (reader.getLocalName().equals(xmlWrapperElement)) { - if (reader.hasNext() && reader.nextTag() == END_ELEMENT) { return; } - } else { - throw ParseUtils.unexpectedElement(reader); - } - parseInternal(reader, parentAddress, list); - while (reader.nextTag() != END_ELEMENT && !reader.getLocalName().equals(xmlWrapperElement)) { - parseInternal(reader, parentAddress, list); - } - } else { - parseInternal(reader, parentAddress, list); - } - } - - private void parseDecorator(final XMLExtendedStreamReader reader, PathAddress parentAddress, List list) throws XMLStreamException { - if (!reader.getLocalName().equals(decoratorElement)) { - throw unexpectedElement(reader, Collections.singleton(decoratorElement)); - } - if (!reader.isEndElement()) { //only parse children if we are not on end of tag already - parseChildren(reader, parentAddress, list, new ModelNode()); - } - } - - private void parseInternal(final XMLExtendedStreamReader reader, PathAddress parentAddress, List list) throws XMLStreamException { - ModelNode op = Util.createAddOperation(); - boolean wildcard = pathElement.isWildcard(); - String name = parseAttributeGroups(reader, op, wildcard); - if (wildcard && name == null) { - if (forcedName != null) { - name = forcedName; - } else { - throw ControllerLogger.ROOT_LOGGER.missingRequiredAttributes(new StringBuilder(NAME), reader.getLocation()); - } - } - PathElement path = wildcard ? PathElement.pathElement(pathElement.getKey(), name) : pathElement; - PathAddress address = parentAddress.append(path); - if (!noAddOperation) { - op.get(ADDRESS).set(address.toModelNode()); - list.add(op); - } - if (additionalOperationsGenerator != null) { - additionalOperationsGenerator.additionalOperations(address, op, list); - } - if (!reader.isEndElement()) { //only parse children if we are not on end of tag already - parseChildren(reader, address, list, op); - } - } - - - private String parseAttributeGroups(final XMLExtendedStreamReader reader, ModelNode op, boolean wildcard) throws XMLStreamException { - String name = parseAttributes(reader, op, attributesByGroup.get(null), wildcard); //parse attributes not belonging to a group - if (!attributeGroups.isEmpty()) { - while (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT) { - final String localName = reader.getLocalName(); - boolean element = attributeElements.containsKey(localName); - //it can be a group or element attribute - if (element || attributeGroups.contains(localName)) { - if (element) { - AttributeDefinition ad = attributeElements.get(localName); - getAttributeParser(ad).parseElement(ad, reader, op); - final String newLocalName = reader.getLocalName(); - if (attributeGroups.contains(newLocalName)) { - parseGroup(reader, op, wildcard); - } else if (reader.isEndElement() && !attributeGroups.contains(newLocalName) && !attributeElements.containsKey(newLocalName)) { - childAlreadyRead = true; - break; + Map operations = new LinkedHashMap<>(); + this.element.readElement(reader, Map.entry(parentAddress, operations)); + if (!operations.isEmpty()) { + for (ModelNode operation : operations.values()) { + String op = operation.get(ModelDescriptionConstants.OP).asStringOrNull(); + if (op != null) { + // Unpack any composite operations + if (op.equals(ModelDescriptionConstants.COMPOSITE)) { + for (ModelNode step : operation.get(ModelDescriptionConstants.STEPS).asListOrEmpty()) { + list.add(step); } } else { - parseGroup(reader, op, wildcard); + list.add(operation); } - - } else { - //don't break, as we read all attributes, we set that child was already read so readChildren wont do .nextTag() - childAlreadyRead = true; - return name; } } - flushRequired = false; } - return name; } - private void parseGroup(XMLExtendedStreamReader reader, ModelNode op, boolean wildcard) throws XMLStreamException { - Map groupAttrs = attributesByGroup.get(reader.getLocalName()); - for (AttributeDefinition attrGroup : groupAttrs.values()) { - if (op.hasDefined(attrGroup.getName())) { - throw ParseUtils.unexpectedElement(reader); - } - } - parseAttributes(reader, op, groupAttrs, wildcard); - // Check if there are also element attributes inside a group - while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { - AttributeDefinition ad = groupAttrs.get(reader.getLocalName()); - if (ad != null) { - getAttributeParser(ad).parseElement(ad, reader, op); - } else { - throw ParseUtils.unexpectedElement(reader); - } - } - } - - private String parseAttributes(final XMLExtendedStreamReader reader, ModelNode op, Map attributes, boolean wildcard) throws XMLStreamException { - String name = null; - int attrCount = reader.getAttributeCount(); - for (int i = 0; i < attrCount; i++) { - String attributeName = reader.getAttributeLocalName(i); - String value = reader.getAttributeValue(i); - if (wildcard && nameAttributeName.equals(attributeName)) { - name = value; - } else if (attributes.containsKey(attributeName)) { - AttributeDefinition def = attributes.get(attributeName); - AttributeParser parser = getAttributeParser(def); - assert parser != null; - parser.parseAndSetParameter(def, value, op, reader); - } else { - Set possible = new LinkedHashSet<>(attributes.keySet()); - possible.add(nameAttributeName); - throw ParseUtils.unexpectedAttribute(reader, i, possible); - } - } - //only parse attribute elements here if there are no attribute groups defined - if (attributeGroups.isEmpty() && !attributeElements.isEmpty() && reader.isStartElement()) { - String originalStartElement = reader.getLocalName(); - if (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT) { - do { - AttributeDefinition ad = attributeElements.get(reader.getLocalName()); - if (ad != null) { - getAttributeParser(ad).parseElement(ad, reader, op); - } else { - childAlreadyRead = true; - return name; //this possibly means we only have children left, return so child handling logic can take over - } - childAlreadyRead = true; - } while (!reader.getLocalName().equals(originalStartElement) && reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT); - } - } - - - return name; - } - - private Map getChildrenMap() { - Map res = new HashMap<>(); - for (PersistentResourceXMLDescription child : children) { - if (child.xmlWrapperElement != null) { - res.put(child.xmlWrapperElement, child); - } else { - res.put(child.xmlElementName, child); - } - } - return res; - } - - private void parseChildren(final XMLExtendedStreamReader reader, PathAddress parentAddress, List list, ModelNode op) throws XMLStreamException { - if (children.isEmpty()) { - if (flushRequired && attributeGroups.isEmpty() && attributeElements.isEmpty()) { - ParseUtils.requireNoContent(reader); - } - if (childAlreadyRead) { - throw ParseUtils.unexpectedElement(reader); - } - } else { - Map children = getChildrenMap(); - if (childAlreadyRead) { - PersistentResourceXMLDescription decoratorChild = children.get(reader.getLocalName()); - if (decoratorChild != null && decoratorChild.decoratorElement != null) { - decoratorChild.parseDecorator(reader, parentAddress, list); - } - } - if (childAlreadyRead || (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT)) { - do { - final String localName = reader.getLocalName(); - AttributeDefinition elementAd; - ResourceParser child = children.get(localName); - if (child != null) { - child.parse(reader, parentAddress, list); - } else if ((elementAd = attributeElements.get(localName)) != null) { - getAttributeParser(elementAd).parseElement(elementAd, reader, op); - } else if ((child = customChildParsers.get(localName)) != null) { - child.parse(reader, parentAddress, list); - } else { - throw ParseUtils.unexpectedElement(reader, children.keySet()); - } - } while (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT); - } - } - } - - + @Override public void persist(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException { - persist(writer, model, namespaceURI); - } - - private void writeStartElement(XMLExtendedStreamWriter writer, String namespaceURI, String localName) throws XMLStreamException { - if (namespaceURI != null) { - writer.writeStartElement(namespaceURI, localName); - } else { - writer.writeStartElement(localName); - } - } - - private void startSubsystemElement(XMLExtendedStreamWriter writer, String namespaceURI, boolean empty) throws XMLStreamException { - if (writer.getNamespaceContext().getPrefix(namespaceURI) == null) { - // Unknown namespace; it becomes default - writer.setDefaultNamespace(namespaceURI); - if (empty) { - writer.writeEmptyElement(Element.SUBSYSTEM.getLocalName()); - } else { - writer.writeStartElement(Element.SUBSYSTEM.getLocalName()); - } - writer.writeNamespace(null, namespaceURI); - } else { - if (empty) { - writer.writeEmptyElement(namespaceURI, Element.SUBSYSTEM.getLocalName()); - } else { - writer.writeStartElement(namespaceURI, Element.SUBSYSTEM.getLocalName()); - } - } - - } - - /** - * persist decorator and than continue to children without touching the model - */ - private void persistDecorator(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException { - if (shouldWriteDecoratorAndElements(model)) { - writer.writeStartElement(decoratorElement); - persistChildren(writer, model); - writer.writeEndElement(); - } - } - - /** - * @return true if any of children are defined in the model - */ - private boolean shouldWriteDecoratorAndElements(ModelNode model) { - for (PersistentResourceXMLDescription child : children) { - //if we have child decorator, than we check its children, we only handle one level of nesting - if (child.decoratorElement != null) { - for (PersistentResourceXMLDescription decoratedChild : child.children) { - if (definedInModel(model, decoratedChild)) { - return true; - } - } - } else if (definedInModel(model, child)) { - return true; - } - - } - //we always write if there is custom writer defined - return !customChildParsers.isEmpty(); - } - - private boolean definedInModel(ModelNode model, PersistentResourceXMLDescription child) { - PathElement pe = child.getPathElement(); - boolean wildcard = getPathElement().isWildcard(); - if (wildcard ? model.hasDefined(pe.getKey()) : model.hasDefined(pe.getKeyValuePair())){ - return true; - } - return false; + this.element.writeContent(writer, model); } public void persist(XMLExtendedStreamWriter writer, ModelNode model, String namespaceURI) throws XMLStreamException { - if (decoratorElement!=null){ - persistDecorator(writer, model); - return; - } - boolean wildcard = pathElement.isWildcard(); - model = wildcard ? model.get(pathElement.getKey()) : model.get(pathElement.getKeyValuePair()); - boolean isSubsystem = pathElement.getKey().equals(ModelDescriptionConstants.SUBSYSTEM); - if (!isSubsystem && !model.isDefined() && !useValueAsElementName) { - return; - } - - boolean writeWrapper = xmlWrapperElement != null; - if (writeWrapper) { - writeStartElement(writer, namespaceURI, xmlWrapperElement); - } - - if (wildcard) { - for (String name : model.keys()) { - ModelNode subModel = model.get(name); - if (useValueAsElementName) { - writeStartElement(writer, namespaceURI, name); - } else { - writeStartElement(writer, namespaceURI, xmlElementName); - writer.writeAttribute(nameAttributeName, name); - } - persistAttributes(writer, subModel); - persistChildren(writer, subModel); - writer.writeEndElement(); - } - } else { - final boolean empty = attributeGroups.isEmpty() && children.isEmpty() && attributeElements.isEmpty(); - if (useValueAsElementName) { - writeStartElement(writer, namespaceURI, getPathElement().getValue()); - } else if (isSubsystem) { - startSubsystemElement(writer, namespaceURI, empty); - } else { - writeStartElement(writer, namespaceURI, xmlElementName); - } - - persistAttributes(writer, model); - persistChildren(writer, model); - - // Do not attempt to write end element if the has no elements! - if (!isSubsystem || !empty) { - writer.writeEndElement(); - } - } - - if (writeWrapper) { - writer.writeEndElement(); - } - } - - private void persistAttributes(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException { - marshallAttributes(writer, model, attributesByGroup.get(null).values(), null); - if (useElementsForGroups) { - for (Map.Entry> entry : attributesByGroup.entrySet()) { - if (entry.getKey() == null) { - continue; - } - marshallAttributes(writer, model, entry.getValue().values(), entry.getKey()); - } - } - } - - private AttributeParser getAttributeParser(AttributeDefinition ad) { - return attributeParsers.getOrDefault(ad.getXmlName(), ad.getParser()); - } - - private void marshallAttributes(XMLExtendedStreamWriter writer, ModelNode model, Collection attributes, String group) throws XMLStreamException { - boolean started = false; - - //we sort attributes to make sure that attributes that marshall to elements are last - List sortedAds = new ArrayList<>(attributes.size()); - List elementAds = null; - for (AttributeDefinition ad : attributes) { - if (getAttributeParser(ad).isParseAsElement()) { - if (elementAds == null) { - elementAds = new ArrayList<>(); - } - elementAds.add(ad); - } else { - sortedAds.add(ad); - } - } - if (elementAds != null) { - sortedAds.addAll(elementAds); - } - - for (AttributeDefinition ad : sortedAds) { - AttributeMarshaller marshaller = attributeMarshallers.getOrDefault(ad.getXmlName(), ad.getMarshaller()); - if (marshaller.isMarshallable(ad, model, marshallDefaultValues)) { - if (!started && group != null) { - if (elementAds != null) { - writer.writeStartElement(group); - } else { - writer.writeEmptyElement(group); - } - started = true; - } - marshaller.marshall(ad, model, marshallDefaultValues, writer); - } - - } - if (elementAds != null && started) { - writer.writeEndElement(); - } + this.persist(writer, model); } public void persistChildren(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException { - for (ResourceMarshaller child : marshallers) { - child.persist(writer, model); - } + throw new UnsupportedOperationException(); } /** @@ -532,7 +109,7 @@ public void persistChildren(XMLExtendedStreamWriter writer, ModelNode model) thr */ @Deprecated public static PersistentResourceXMLBuilder builder(final PathElement pathElement) { - return new PersistentResourceXMLBuilder(pathElement); + return new PersistentResourceXMLBuilder(new LegacyBuilder(pathElement).withElementLocalName(pathElement.isWildcard() ? ResourceXMLElementLocalName.KEY : ResourceXMLElementLocalName.VALUE)); } /** @@ -545,7 +122,7 @@ public static PersistentResourceXMLBuilder builder(final PathElement pathElement */ @Deprecated public static PersistentResourceXMLBuilder builder(final PathElement pathElement, final String namespaceURI) { - return new PersistentResourceXMLBuilder(pathElement, namespaceURI); + return new PersistentResourceXMLBuilder(new LegacyBuilder(pathElement, namespaceURI).withElementLocalName(ResourceXMLElementLocalName.KEY)); } /** @@ -558,7 +135,7 @@ public static PersistentResourceXMLBuilder builder(final PathElement pathElement */ @Deprecated public static PersistentResourceXMLBuilder builder(PathElement path, Namespace namespace) { - return new PersistentResourceXMLBuilder(path, namespace.getUri()); + return builder(path, namespace.getUri()); } /** @@ -569,187 +146,112 @@ public static PersistentResourceXMLBuilder builder(PathElement path, Namespace n * @since 4.0 */ public static PersistentResourceXMLBuilder decorator(final String elementName) { - return new PersistentResourceXMLBuilder(PathElement.pathElement(elementName), null).setDecoratorGroup(elementName); - } - - /** - * Creates a factory for creating a {@link PersistentResourceXMLDescription} builders for the specified subsystem schema. - * @param the schema type - * @param schema a subsystem schema - * @return a factory for creating a {@link PersistentResourceXMLDescription} builders - */ - public static > Factory factory(PersistentSubsystemSchema schema) { - return new Factory() { + // Create build only to collect children + ResourceXMLElement.Builder builder = new LegacyBuilder(PathElement.pathElement(elementName)); + return new PersistentResourceXMLBuilder(builder) { @Override - public Builder builder(ResourceRegistration registration) { - if (!schema.enables(registration)) { - // If resource is not enabled for this schema, return a builder stub that returns a null description - return new Builder() { - @Override - public Builder addChild(PersistentResourceXMLDescription description) { - return this; - } - - @Override - public Builder addAttribute(AttributeDefinition attribute) { - return this; - } - - @Override - public Builder addAttribute(AttributeDefinition attribute, AttributeParser attributeParser, AttributeMarshaller attributeMarshaller) { - return this; - } - - @Override - public Builder addAttributes(AttributeDefinition... attributes) { - return this; - } - - @Override - public Builder addAttributes(Stream attributes) { - return this; - } - - @Override - public Builder addAttributes(Stream attributes, AttributeParser attributeParser, AttributeMarshaller attributeMarshaller) { - return this; - } - - @Override - public Builder setXmlWrapperElement(String xmlWrapperElement) { - return this; - } - - @Override - public Builder setXmlElementName(String xmlElementName) { - return this; - } - - @Override - public Builder setNoAddOperation(boolean noAddOperation) { - return this; - } - - @Override - public Builder setAdditionalOperationsGenerator(AdditionalOperationsGenerator additionalOperationsGenerator) { - return this; - } - - @Override - public Builder setUseElementsForGroups(boolean useElementsForGroups) { - return this; - } - - @Override - public Builder setNameAttributeName(String nameAttributeName) { - return this; - } - - @Override - public PersistentResourceXMLDescription build() { - return null; - } - }; + public PersistentResourceXMLDescription build() { + List> childBuilders = this.children; + List children = new ArrayList<>(childBuilders.size()); + for (Supplier childBuilder : childBuilders) { + children.add(childBuilder.get().element); } - PathElement path = registration.getPathElement(); - Builder builder = path.getKey().equals(ModelDescriptionConstants.SUBSYSTEM) ? PersistentResourceXMLDescription.builder(path, schema.getNamespace()) : PersistentResourceXMLDescription.builder(path); - // Return decorated builder that filters its attributes - return new Builder() { - @Override - public Builder addChild(PersistentResourceXMLDescription description) { - // Description might be null if this resource is not enabled by this schema - if (description != null) { - builder.addChild(description); - } - return this; - } - - @Override - public Builder addAttribute(AttributeDefinition attribute) { - if (schema.enables(attribute)) { - builder.addAttribute(attribute); - } - return this; - } - - @Override - public Builder addAttribute(AttributeDefinition attribute, AttributeParser attributeParser, AttributeMarshaller attributeMarshaller) { - if (schema.enables(attribute)) { - builder.addAttribute(attribute, attributeParser, attributeMarshaller); - } - return this; - } - + XMLElement>, ModelNode> element = XMLElement.wrap(new QName(elementName), XMLContent.all(children), XMLCardinality.Single.OPTIONAL); + return new PersistentResourceXMLDescription(new ResourceXMLElement() { @Override - public Builder addAttributes(AttributeDefinition... attributes) { - for (AttributeDefinition attribute : attributes) { - if (schema.enables(attribute)) { - builder.addAttribute(attribute); - } - } - return this; + public PathElement getPathElement() { + return null; } @Override - public Builder addAttributes(Stream attributes) { - builder.addAttributes(attributes.filter(schema::enables)); - return this; + public QName getName() { + return element.getName(); } @Override - public Builder addAttributes(Stream attributes, AttributeParser attributeParser, AttributeMarshaller attributeMarshaller) { - builder.addAttributes(attributes.filter(schema::enables), attributeParser, attributeMarshaller); - return this; + public void readElement(XMLExtendedStreamReader reader, Map.Entry> context) throws XMLStreamException { + element.readElement(reader, context); } @Override - public Builder setXmlWrapperElement(String xmlWrapperElement) { - builder.setXmlElementName(xmlWrapperElement); - return this; + public XMLCardinality getCardinality() { + return element.getCardinality(); } @Override - public Builder setXmlElementName(String xmlElementName) { - builder.setXmlElementName(xmlElementName); - return this; + public void writeContent(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException { + element.writeContent(writer, model); } @Override - public Builder setNoAddOperation(boolean noAddOperation) { - builder.setNoAddOperation(noAddOperation); - return this; + public boolean isEmpty(ModelNode model) { + return element.isEmpty(model); } + }); + } + }; + } - @Override - public Builder setAdditionalOperationsGenerator(AdditionalOperationsGenerator additionalOperationsGenerator) { - builder.setAdditionalOperationsGenerator(additionalOperationsGenerator); - return this; - } + static class LegacyBuilder extends ResourceXMLElement.DefaultBuilder { + private static final FeatureFilter ALWAYS = new FeatureFilter() { + @Override + public boolean enables(F feature) { + return true; + } + }; - @Override - public Builder setUseElementsForGroups(boolean useElementsForGroups) { - builder.setUseElementsForGroups(useElementsForGroups); - return this; - } + LegacyBuilder(PathElement path) { + super(ALWAYS, new ResourceDescription() { + @Override + public PathElement getPathElement() { + return path; + } + }, QName::new); + } - @Override - public Builder setNameAttributeName(String nameAttributeName) { - builder.setNameAttributeName(nameAttributeName); - return this; - } + LegacyBuilder(PathElement path, String namespaceURI) { + super(ALWAYS, new ResourceDescription() { + @Override + public PathElement getPathElement() { + return path; + } + }, new QNameResolver() { + @Override + public QName resolveQName(String localName) { + return new QName(namespaceURI, localName); + } + }); + } + } - @Override - public PersistentResourceXMLDescription build() { - return builder.build(); - } - }; + /** + * Creates a factory for creating a {@link PersistentResourceXMLDescription} builders for the specified subsystem schema. + * @param the schema type + * @param schema a subsystem schema + * @return a factory for creating a {@link PersistentResourceXMLDescription} builders + */ + public static > Factory factory(S schema) { + ResourceXMLElement.Builder.Factory factory = ResourceXMLElement.Builder.Factory.newInstance(schema); + return new Factory() { + @Override + public Builder builder(ResourceRegistration registration) { + PathElement path = registration.getPathElement(); + boolean subsystem = path.getKey().equals(ModelDescriptionConstants.SUBSYSTEM); + ResourceXMLElement.Builder builder = subsystem ? factory.createBuilder(SubsystemResourceDescription.of(registration, List.of())) : factory.createBuilder(ResourceDescription.of(registration, List.of())); + if (!subsystem && !path.isWildcard()) { + // Override default naming strategy according to previous PersistentResourceXMLDescription logic + builder.withElementLocalName(ResourceXMLElementLocalName.VALUE); + } + return new PersistentResourceXMLBuilder(builder); } }; } /** * Factory for creating a {@link PersistentResourceXMLDescription} builder. + * @deprecated Superseded by {@link ResourceXMLElement.Builder.Factory}. */ + @Deprecated(forRemoval = true) public static interface Factory { /** * Creates a builder for the resource registered at the specified path. @@ -799,50 +301,25 @@ public static interface Builder { PersistentResourceXMLDescription build(); } - public static final class PersistentResourceXMLBuilder implements Builder { - private final PathElement pathElement; - private final String namespaceURI; - private String xmlElementName; - private String xmlWrapperElement; - private boolean useValueAsElementName; - private boolean noAddOperation; - private AdditionalOperationsGenerator additionalOperationsGenerator; - private final LinkedList attributeList = new LinkedList<>(); - private final List childrenBuilders = new ArrayList<>(); - private final List children = new ArrayList<>(); - private final LinkedHashMap attributeParsers = new LinkedHashMap<>(); - private final LinkedHashMap attributeMarshallers = new LinkedHashMap<>(); - private final LinkedHashMap customChildParsers = new LinkedHashMap<>(); - private final LinkedList marshallers = new LinkedList<>(); - private boolean useElementsForGroups = true; - private String forcedName; - private boolean marshallDefaultValues = true; - private String nameAttributeName = NAME; - private String decoratorElement = null; - - private PersistentResourceXMLBuilder(final PathElement pathElement) { - this.pathElement = pathElement; - this.namespaceURI = null; - this.xmlElementName = pathElement.isWildcard() ? pathElement.getKey() : pathElement.getValue(); - } + public static class PersistentResourceXMLBuilder implements Builder { + private final ResourceXMLElement.Builder builder; + private volatile String wrapperElementLocalName = null; + final List> children = new LinkedList<>(); + private volatile Map parsers = Map.of(); + private volatile Map marshallers = Map.of(); - private PersistentResourceXMLBuilder(final PathElement pathElement, String namespaceURI) { - this.pathElement = pathElement; - this.namespaceURI = namespaceURI; - this.xmlElementName = pathElement.isWildcard() ? pathElement.getKey() : pathElement.getValue(); + private PersistentResourceXMLBuilder(ResourceXMLElement.Builder builder) { + this.builder = builder; } public PersistentResourceXMLBuilder addChild(PersistentResourceXMLBuilder builder) { - this.childrenBuilders.add(builder); + this.children.add(builder::build); return this; } @Override public PersistentResourceXMLBuilder addChild(PersistentResourceXMLDescription description) { - if (description != null) { - this.children.add(description); - this.marshallers.add(description); - } + this.children.add(Functions.constantSupplier(description)); return this; } @@ -861,51 +338,69 @@ public PersistentResourceXMLBuilder addChild(final String xmlElementName, Resour @Override public PersistentResourceXMLBuilder addAttribute(AttributeDefinition attribute) { - this.attributeList.add(attribute); + this.builder.includeAttribute(attribute); return this; } public PersistentResourceXMLBuilder addAttribute(AttributeDefinition attribute, AttributeParser attributeParser) { - this.attributeList.add(attribute); - this.attributeParsers.put(attribute.getXmlName(), attributeParser); - return this; + if (attribute.getParser() != attributeParser) { + if (this.parsers.isEmpty()) { + this.parsers = new HashMap<>(); + } + this.parsers.put(attribute, attributeParser); + } + return this.addAttribute(attribute); } @Override public PersistentResourceXMLBuilder addAttribute(AttributeDefinition attribute, AttributeParser attributeParser, AttributeMarshaller attributeMarshaller) { - this.attributeList.add(attribute); - this.attributeParsers.put(attribute.getXmlName(), attributeParser); - this.attributeMarshallers.put(attribute.getXmlName(), attributeMarshaller); - return this; + if (attribute.getMarshaller() != attributeMarshaller) { + if (this.marshallers.isEmpty()) { + this.marshallers = new HashMap<>(); + } + this.marshallers.put(attribute, attributeMarshaller); + } + return this.addAttribute(attribute, attributeParser); } @Override public PersistentResourceXMLBuilder addAttributes(AttributeDefinition... attributes) { - Collections.addAll(this.attributeList, attributes); + this.builder.includeAttributes(List.of(attributes)); return this; } @Override - public PersistentResourceXMLBuilder addAttributes(Stream attributes) { - attributes.forEach(this::addAttribute); + public PersistentResourceXMLBuilder addAttributes(Stream stream) { + this.builder.includeAttributes(stream.collect(Collectors.toList())); return this; } @Override - public PersistentResourceXMLBuilder addAttributes(Stream attributes, AttributeParser parser, AttributeMarshaller attributeMarshaller) { - attributes.forEach(attribute -> this.addAttribute(attribute, parser, attributeMarshaller)); + public PersistentResourceXMLBuilder addAttributes(Stream stream, AttributeParser parser, AttributeMarshaller attributeMarshaller) { + Set attributes = stream.collect(Collectors.toSet()); + if (this.parsers.isEmpty()) { + this.parsers = new HashMap<>(); + } + if (this.marshallers.isEmpty()) { + this.marshallers = new HashMap<>(); + } + for (AttributeDefinition attribute : attributes) { + this.parsers.put(attribute, parser); + this.marshallers.put(attribute, attributeMarshaller); + } + this.builder.includeAttributes(attributes); return this; } @Override public PersistentResourceXMLBuilder setXmlWrapperElement(final String xmlWrapperElement) { - this.xmlWrapperElement = xmlWrapperElement; + this.wrapperElementLocalName = xmlWrapperElement; return this; } @Override public PersistentResourceXMLBuilder setXmlElementName(final String xmlElementName) { - this.xmlElementName = xmlElementName; + this.builder.withElementLocalName(xmlElementName); return this; } @@ -914,21 +409,50 @@ public PersistentResourceXMLBuilder setXmlElementName(final String xmlElementNam */ @Deprecated(forRemoval = true) public PersistentResourceXMLBuilder setUseValueAsElementName(final boolean useValueAsElementName) { - this.useValueAsElementName = useValueAsElementName; + this.builder.withElementLocalName(ResourceXMLElementLocalName.VALUE); return this; } @Override - @SuppressWarnings("unused") public PersistentResourceXMLBuilder setNoAddOperation(final boolean noAddOperation) { - this.noAddOperation = noAddOperation; + if (noAddOperation) { + this.builder.thenDiscardOperation(); + } return this; } @Override - @SuppressWarnings("unused") public PersistentResourceXMLBuilder setAdditionalOperationsGenerator(final AdditionalOperationsGenerator additionalOperationsGenerator) { - this.additionalOperationsGenerator = additionalOperationsGenerator; + this.builder.withOperationTransformation(new BiConsumer<>() { + @Override + public void accept(Map operations, PathAddress operationKey) { + ModelNode operation = operations.get(operationKey); + + // Replace original operation with a composite operation so that operation order is consistent with previous implementation + ModelNode compositeOperation = Util.createEmptyOperation(ModelDescriptionConstants.COMPOSITE, PathAddress.EMPTY_ADDRESS); + ModelNode steps = compositeOperation.get(ModelDescriptionConstants.STEPS); + steps.add(operation); + operations.put(operationKey, compositeOperation); + additionalOperationsGenerator.additionalOperations(operationKey, operation, new AbstractList() { + @Override + public int size() { + return steps.asList().size(); + } + + @Override + public ModelNode get(int index) { + return steps.asList().get(index); + } + + @Override + public boolean add(ModelNode operation) { + // Add operation as composite operation step + steps.add(operation); + return true; + } + }); + } + }); return this; } @@ -945,8 +469,7 @@ public PersistentResourceXMLBuilder setAdditionalOperationsGenerator(final Addit */ @Deprecated(forRemoval = true) public PersistentResourceXMLBuilder setForcedName(String forcedName) { - this.forcedName = forcedName; - return this; + throw new UnsupportedOperationException(); } /** @@ -960,7 +483,12 @@ public PersistentResourceXMLBuilder setForcedName(String forcedName) { */ @Override public PersistentResourceXMLBuilder setUseElementsForGroups(boolean useElementsForGroups) { - this.useElementsForGroups = useElementsForGroups; + this.builder.withAttributeGroupElementLocalNames(new UnaryOperator<>() { + @Override + public String apply(String groupName) { + return useElementsForGroups ? groupName : null; + } + }); return this; } @@ -971,7 +499,9 @@ public PersistentResourceXMLBuilder setUseElementsForGroups(boolean useElementsF * @return builder */ public PersistentResourceXMLBuilder setMarshallDefaultValues(boolean marshallDefault) { - this.marshallDefaultValues = marshallDefault; + if (!marshallDefault) { + throw new UnsupportedOperationException(); + } return this; } @@ -985,19 +515,56 @@ public PersistentResourceXMLBuilder setMarshallDefaultValues(boolean marshallDef */ @Override public PersistentResourceXMLBuilder setNameAttributeName(String nameAttributeName) { - this.nameAttributeName = nameAttributeName; - return this; - } - - private PersistentResourceXMLBuilder setDecoratorGroup(String elementName){ - this.decoratorElement = elementName; + this.builder.withPathValueAttributeLocalName(nameAttributeName); return this; } @Override public PersistentResourceXMLDescription build() { + if (!this.parsers.isEmpty()) { + this.builder.withParsers(this.parsers); + } + if (!this.marshallers.isEmpty()) { + this.builder.withMarshallers(this.marshallers); + } + for (Supplier child : PersistentResourceXMLBuilder.this.children) { + this.builder.addChild(child.get().element); + } + ResourceXMLElement element = this.builder.build(); + String wrapperLocalName = this.wrapperElementLocalName; + if (wrapperLocalName == null) return new PersistentResourceXMLDescription(element); + XMLElement>, ModelNode> wrappedElement = XMLElement.wrap(PersistentResourceXMLBuilder.this.builder.resolveQName(wrapperLocalName), element); + return new PersistentResourceXMLDescription(new ResourceXMLElement() { + @Override + public PathElement getPathElement() { + return element.getPathElement(); + } + + @Override + public XMLCardinality getCardinality() { + return wrappedElement.getCardinality(); + } + + @Override + public QName getName() { + return wrappedElement.getName(); + } + + @Override + public void readElement(XMLExtendedStreamReader reader, Map.Entry> value) throws XMLStreamException { + wrappedElement.readElement(reader, value); + } + + @Override + public void writeContent(XMLExtendedStreamWriter streamWriter, ModelNode value) throws XMLStreamException { + wrappedElement.writeContent(streamWriter, value); + } - return new PersistentResourceXMLDescription(this); + @Override + public boolean isEmpty(ModelNode content) { + return wrappedElement.isEmpty(content); + } + }); } } diff --git a/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLDescriptionReader.java b/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLDescriptionReader.java index a280f014056..002208d7cab 100644 --- a/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLDescriptionReader.java +++ b/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLDescriptionReader.java @@ -17,7 +17,9 @@ /** * An {@link XMLElementReader} based on a {@link PersistentResourceXMLDescription}. * @author Paul Ferraro + * @deprecated Superseded by {@link org.jboss.as.controller.persistence.xml.SubsystemResourceXMLElementReader}. */ +@Deprecated(forRemoval = true) public class PersistentResourceXMLDescriptionReader implements XMLElementReader> { private final Supplier description; diff --git a/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLDescriptionWriter.java b/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLDescriptionWriter.java index 803ddbb5c8d..45d2c12175a 100644 --- a/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLDescriptionWriter.java +++ b/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLDescriptionWriter.java @@ -17,7 +17,9 @@ /** * An {@link XMLElementWriter} based on a {@link PersistentResourceXMLDescription}. * @author Paul Ferraro + * @deprecated Superseded by {@link org.jboss.as.controller.persistence.xml.SubsystemResourceXMLElementWriter}. */ +@Deprecated(forRemoval = true) public class PersistentResourceXMLDescriptionWriter implements XMLElementWriter { private final Supplier description; @@ -47,9 +49,10 @@ public > PersistentResourceXMLDescription @Override public void writeContent(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException { + ModelNode subsystemModel = context.getModelNode(); PersistentResourceXMLDescription description = this.description.get(); ModelNode model = new ModelNode(); - model.get(description.getPathElement().getKeyValuePair()).set(context.getModelNode()); + model.get(description.getPathElement().getKeyValuePair()).set(subsystemModel.isDefined() ? subsystemModel : new ModelNode().setEmptyObject()); description.persist(writer, model); } } diff --git a/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLParser.java b/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLParser.java index e7ffabac1d9..7f016131c87 100644 --- a/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLParser.java +++ b/controller/src/main/java/org/jboss/as/controller/PersistentResourceXMLParser.java @@ -21,7 +21,9 @@ /** * @author Tomaz Cerar (c) 2015 Red Hat Inc. + * @deprecated Superseded by org.wildfly.subsystem.SubsystemPersistence */ +@Deprecated(forRemoval = true) public abstract class PersistentResourceXMLParser implements XMLStreamConstants, XMLElementReader>, XMLElementWriter, UnaryOperator { private final AtomicReference cachedDescription = new AtomicReference<>(); diff --git a/controller/src/main/java/org/jboss/as/controller/PersistentSubsystemSchema.java b/controller/src/main/java/org/jboss/as/controller/PersistentSubsystemSchema.java index 33f1a0f0235..b0d5d727dda 100644 --- a/controller/src/main/java/org/jboss/as/controller/PersistentSubsystemSchema.java +++ b/controller/src/main/java/org/jboss/as/controller/PersistentSubsystemSchema.java @@ -8,6 +8,8 @@ import javax.xml.stream.XMLStreamException; +import org.jboss.as.controller.persistence.xml.ResourceXMLElement; +import org.jboss.as.controller.persistence.xml.SubsystemResourceXMLSchema; import org.jboss.dmr.ModelNode; import org.jboss.staxmapper.XMLExtendedStreamReader; @@ -15,11 +17,18 @@ * Defines a versioned schema for a subsystem defined via a {@link PersistentResourceXMLDescription}. * @author Paul Ferraro * @param the schema type + * @deprecated Superseded by {@link SubsystemResourceXMLSchema}. */ -public interface PersistentSubsystemSchema> extends SubsystemSchema { +@Deprecated(forRemoval = true) +public interface PersistentSubsystemSchema> extends SubsystemResourceXMLSchema { PersistentResourceXMLDescription getXMLDescription(); + @Override + default ResourceXMLElement getSubsystemResourceXMLElement() { + return this.getXMLDescription().getXMLElement(); + } + @Override default void readElement(XMLExtendedStreamReader reader, List value) throws XMLStreamException { new PersistentResourceXMLDescriptionReader(this.getXMLDescription()).readElement(reader, value); diff --git a/controller/src/main/java/org/jboss/as/controller/ResourceDescription.java b/controller/src/main/java/org/jboss/as/controller/ResourceDescription.java new file mode 100644 index 00000000000..d8f456f48cf --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/ResourceDescription.java @@ -0,0 +1,80 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.as.controller; + +import java.util.Collection; +import java.util.stream.Stream; + +import org.jboss.as.version.Stability; + +/** + * Describes the persistent aspects of a resource. + * @author Paul Ferraro + */ +public interface ResourceDescription extends ResourceRegistration { + + /** + * Returns a "path key" used to identify the resource during parsing. + * Normally, this is the same as {@link #getPathElement()}. + * Mutually exclusive child resources of a given parent resource should share the same path key. + * @return a path element used to identify the resource during parsing. + */ + default PathElement getPathKey() { + return this.getPathElement(); + } + + /** + * Returns the attributes of this resource. + * @return a stream of attributes + */ + default Stream getAttributes() { + return Stream.empty(); + } + + /** + * Creates a basic description of a resource. + * @param registration a resource registration + * @param attributes the attributes of this resource + * @return a resource description + */ + static ResourceDescription of(ResourceRegistration registration, Collection attributes) { + return new ResourceDescription() { + @Override + public PathElement getPathElement() { + return registration.getPathElement(); + } + + @Override + public Stability getStability() { + return registration.getStability(); + } + + @Override + public Stream getAttributes() { + return attributes.stream(); + } + }; + } + + /** + * Creates a basic description of a resource. + * @param path a resource path element + * @param attributes the attributes of this resource + * @return a resource description + */ + static ResourceDescription of(PathElement path, Collection attributes) { + return new ResourceDescription() { + @Override + public PathElement getPathElement() { + return path; + } + + @Override + public Stream getAttributes() { + return attributes.stream(); + } + }; + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/ResourceMarshaller.java b/controller/src/main/java/org/jboss/as/controller/ResourceMarshaller.java index e634b8c5998..ebc1e7e82b7 100644 --- a/controller/src/main/java/org/jboss/as/controller/ResourceMarshaller.java +++ b/controller/src/main/java/org/jboss/as/controller/ResourceMarshaller.java @@ -12,7 +12,9 @@ /** * @author Tomaz Cerar (c) 2017 Red Hat Inc. + * @deprecated To be removed without replacement. */ +@Deprecated(forRemoval = true) @FunctionalInterface public interface ResourceMarshaller { void persist(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException; diff --git a/controller/src/main/java/org/jboss/as/controller/ResourceParser.java b/controller/src/main/java/org/jboss/as/controller/ResourceParser.java index 55dda9e8512..f8f1b037095 100644 --- a/controller/src/main/java/org/jboss/as/controller/ResourceParser.java +++ b/controller/src/main/java/org/jboss/as/controller/ResourceParser.java @@ -13,7 +13,9 @@ /** * @author Tomaz Cerar (c) 2017 Red Hat Inc. + * @deprecated To be removed without replacement. */ +@Deprecated(forRemoval = true) @FunctionalInterface public interface ResourceParser { void parse(final XMLExtendedStreamReader reader, PathAddress parentAddress, List list) throws XMLStreamException; diff --git a/controller/src/main/java/org/jboss/as/controller/SubsystemResourceDescription.java b/controller/src/main/java/org/jboss/as/controller/SubsystemResourceDescription.java new file mode 100644 index 00000000000..a0943029e8e --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/SubsystemResourceDescription.java @@ -0,0 +1,75 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.as.controller; + +import java.util.Collection; +import java.util.stream.Stream; + +import org.jboss.as.controller.descriptions.ModelDescriptionConstants; +import org.jboss.as.version.Stability; + +/** + * Describe the persistent aspects of a subsystem resource. + * @author Paul Ferraro + */ +public interface SubsystemResourceDescription extends ResourceDescription { + + String getName(); + + @Override + default PathElement getPathElement() { + return PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, this.getName()); + } + + /** + * Creates a basic description of a subsystem resource. + * @param registration a resource registration + * @param attributes the attributes of this resource + * @return a subsystem resource description + */ + static SubsystemResourceDescription of(ResourceRegistration registration, Collection attributes) { + return new SubsystemResourceDescription() { + @Override + public String getName() { + return this.getPathElement().getValue(); + } + + @Override + public PathElement getPathElement() { + return registration.getPathElement(); + } + + @Override + public Stability getStability() { + return registration.getStability(); + } + + @Override + public Stream getAttributes() { + return attributes.stream(); + } + }; + } + + /** + * Creates a basic description of a subsystem resource. + * @param name the subsystem name + * @param attributes the attributes of this resource + * @return a subsystem resource description + */ + static SubsystemResourceDescription of(String name, Collection attributes) { + return new SubsystemResourceDescription() { + @Override + public String getName() { + return name; + } + + @Override + public Stream getAttributes() { + return attributes.stream(); + } + }; + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/logging/ControllerLogger.java b/controller/src/main/java/org/jboss/as/controller/logging/ControllerLogger.java index 3738d8b45dc..d6204cfe002 100644 --- a/controller/src/main/java/org/jboss/as/controller/logging/ControllerLogger.java +++ b/controller/src/main/java/org/jboss/as/controller/logging/ControllerLogger.java @@ -3809,4 +3809,24 @@ OperationFailedRuntimeException capabilityAlreadyRegisteredInContext(String capa @LogMessage(level = WARN) @Message(id = 517, value = "There are multiple Parallel Boot Operations.") void multipleParallelBootOperation(); + + @LogMessage(level = WARN) + @Message(id = 518, value = "Attribute '%2$s' of element '%1$s' is no longer supported and will be ignored") + void attributeIgnored(QName elementName, QName attributeName); + + @LogMessage(level = WARN) + @Message(id = 519, value = "Element '%s' is no longer supported and will be ignored") + void elementIgnored(QName elementName); + + @Message(id = 520, value = "Element '%s' already defines attribute: %s") + IllegalArgumentException duplicateAttributes(QName elementName, QName attributeName); + + @Message(id = 521, value = "XML model group already defines element: %s") + IllegalArgumentException duplicateElements(QName elementName); + + @Message(id = 522, value = "Element(s) '%s' occur(s) an insufficient number of times") + String minOccursNotReached(Set elements); + + @Message(id = 523, value = "Element(s) '%s' occur(s) an excessive number of times") + String maxOccursExceeded(Set elements); } diff --git a/controller/src/main/java/org/jboss/as/controller/parsing/ParseUtils.java b/controller/src/main/java/org/jboss/as/controller/parsing/ParseUtils.java index 11ee253549a..94a968e31ad 100644 --- a/controller/src/main/java/org/jboss/as/controller/parsing/ParseUtils.java +++ b/controller/src/main/java/org/jboss/as/controller/parsing/ParseUtils.java @@ -17,6 +17,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.TreeSet; import javax.xml.XMLConstants; import javax.xml.namespace.QName; @@ -630,4 +631,42 @@ public static XMLStreamException unsupportedElement(final XMLExtendedStreamReade .alternatives(new HashSet() {{add(supportedElement);}}), ex); } + + /** + * Creates an exception reporting that a given element did not appear a sufficient number of times. + * @param reader the stream reader + * @param elementName the element name + * @return a validation exception + */ + public static XMLStreamException minOccursNotReached(XMLExtendedStreamReader reader, Set choices) { + XMLStreamException e = new XMLStreamException(ControllerLogger.ROOT_LOGGER.minOccursNotReached(choices), reader.getLocation()); + return createValidationException(e, ErrorType.REQUIRED_ELEMENT_MISSING, choices); + } + + /** + * Creates an exception reporting that a given element appeared too many times. + * @param reader the stream reader + * @param elementName the element name + * @return a validation exception + */ + public static XMLStreamException maxOccursExceeded(XMLExtendedStreamReader reader, Set choices) { + XMLStreamException e = new XMLStreamException(ControllerLogger.ROOT_LOGGER.maxOccursExceeded(choices), reader.getLocation()); + return createValidationException(e, ErrorType.DUPLICATE_ELEMENT, choices); + } + + private static XMLStreamValidationException createValidationException(XMLStreamException e, ErrorType type, Set choices) { + ValidationError error = ValidationError.from(e, type); + Iterator names = choices.iterator(); + if (names.hasNext()) { + error.element(names.next()); + } + if (names.hasNext()) { + Set alternatives = new TreeSet<>(); + do { + alternatives.add(names.next().getLocalPart()); + } while (names.hasNext()); + error.alternatives(alternatives); + } + return new XMLStreamValidationException(e.getMessage(), error, e); + } } diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceAttributeXMLElement.java b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceAttributeXMLElement.java new file mode 100644 index 00000000000..c8ea3ad6d1e --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceAttributeXMLElement.java @@ -0,0 +1,69 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.jboss.as.controller.persistence.xml; + +import java.util.Map; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; + +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.AttributeMarshaller; +import org.jboss.as.controller.AttributeParser; +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.parsing.ParseUtils; +import org.jboss.as.controller.xml.XMLCardinality; +import org.jboss.as.controller.xml.XMLContentReader; +import org.jboss.as.controller.xml.XMLContentWriter; +import org.jboss.as.controller.xml.XMLElement; +import org.jboss.dmr.ModelNode; +import org.jboss.staxmapper.XMLExtendedStreamReader; +import org.jboss.staxmapper.XMLExtendedStreamWriter; +import org.wildfly.common.Assert; + +/** + * An XML element for a resource attribute. + */ +public class ResourceAttributeXMLElement extends XMLElement.DefaultXMLElement>, ModelNode> { + + /** + * Returns an XML element for a resource attribute that should parse and/or marshal as an element, using the specified parser and marshaller. + * @param name the name of the element + * @param attribute the attribute to read/write + * @param parser an attribute parser + * @param marshaller an attribute marshaller + */ + ResourceAttributeXMLElement(QName name, AttributeDefinition attribute, AttributeParser parser, AttributeMarshaller marshaller) { + super(name, new ResourceOperationXMLContentReader(new XMLContentReader<>() { + @Override + public void readElement(XMLExtendedStreamReader reader, ModelNode operation) throws XMLStreamException { + if (parser.isParseAsElement()) { + parser.parseElement(attribute, reader, operation); + } else { + throw ParseUtils.unexpectedElement(reader); + } + } + + @Override + public XMLCardinality getCardinality() { + return parser.getCardinality(attribute); + } + }), new XMLContentWriter<>() { + @Override + public void writeContent(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException { + if (marshaller.isMarshallableAsElement()) { + marshaller.marshallAsElement(attribute, model, true, writer); + } + } + + @Override + public boolean isEmpty(ModelNode model) { + return !marshaller.isMarshallableAsElement() || !model.hasDefined(attribute.getName()); + } + }); + Assert.assertTrue(parser.isParseAsElement() || marshaller.isMarshallableAsElement()); + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceAttributesXMLContentReader.java b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceAttributesXMLContentReader.java new file mode 100644 index 00000000000..1f933017f8c --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceAttributesXMLContentReader.java @@ -0,0 +1,78 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.jboss.as.controller.persistence.xml; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; + +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.AttributeParser; +import org.jboss.as.controller.parsing.ParseUtils; +import org.jboss.as.controller.xml.XMLCardinality; +import org.jboss.as.controller.xml.XMLContentReader; +import org.jboss.dmr.ModelNode; +import org.jboss.staxmapper.XMLExtendedStreamReader; + +/** + * Reads XML content into a resource operation. + */ +public class ResourceAttributesXMLContentReader implements XMLContentReader { + + private final Map> attributes; + + ResourceAttributesXMLContentReader(Map attributes, Function parsers) { + this.attributes = attributes.isEmpty() ? Map.of() : new HashMap<>(); + // Collect only those attributes that will parse as an XML attribute + for (Map.Entry entry : attributes.entrySet()) { + AttributeDefinition attribute = entry.getValue(); + AttributeParser parser = parsers.apply(attribute); + if (!parser.isParseAsElement()) { + this.attributes.put(entry.getKey(), Map.entry(attribute, parser)); + } + } + } + + @Override + public void readElement(XMLExtendedStreamReader reader, ModelNode operation) throws XMLStreamException { + Set distinctAttributes = new TreeSet<>(Comparator.comparing(QName::toString)); + for (int i = 0; i < reader.getAttributeCount(); i++) { + QName name = reader.getAttributeName(i); + String localName = name.getLocalPart(); + if (!distinctAttributes.add(name)) { + throw ParseUtils.duplicateAttribute(reader, localName); + } + if (name.getNamespaceURI().equals(XMLConstants.NULL_NS_URI)) { + // Inherit namespace of element, if unspecified + name = new QName(reader.getNamespaceURI(), localName); + } + Map.Entry entry = this.attributes.get(name); + if (entry == null) { + // Try matching w/out namespace (for PersistentResourceXMLDescription compatibility) + entry = this.attributes.get(new QName(localName)); + if (entry == null) { + throw ParseUtils.unexpectedAttribute(reader, i, this.attributes.keySet().stream().map(QName::getLocalPart).collect(Collectors.toSet())); + } + } + AttributeDefinition attribute = entry.getKey(); + AttributeParser parser = entry.getValue(); + parser.parseAndSetParameter(attribute, reader.getAttributeValue(i), operation, reader); + } + } + + @Override + public XMLCardinality getCardinality() { + return this.attributes.values().stream().map(Map.Entry::getKey).noneMatch(AttributeDefinition::isRequired) ? XMLCardinality.Single.OPTIONAL : XMLCardinality.Single.REQUIRED; + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceAttributesXMLContentWriter.java b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceAttributesXMLContentWriter.java new file mode 100644 index 00000000000..186b7fffede --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceAttributesXMLContentWriter.java @@ -0,0 +1,50 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.jboss.as.controller.persistence.xml; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import javax.xml.stream.XMLStreamException; + +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.AttributeMarshaller; +import org.jboss.as.controller.xml.XMLContentWriter; +import org.jboss.dmr.ModelNode; +import org.jboss.staxmapper.XMLExtendedStreamWriter; + +/** + * Writes XML content from a resource model. + */ +public class ResourceAttributesXMLContentWriter implements XMLContentWriter { + + private final List> attributes; + + ResourceAttributesXMLContentWriter(Collection attributes, Function marshallers) { + this.attributes = attributes.isEmpty() ? List.of() : new ArrayList<>(attributes.size()); + for (AttributeDefinition attribute : attributes) { + AttributeMarshaller marshaller = marshallers.apply(attribute); + if (!marshaller.isMarshallableAsElement()) { + this.attributes.add(Map.entry(attribute, marshaller)); + } + } + } + + @Override + public void writeContent(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException { + for (Map.Entry entry : this.attributes) { + entry.getValue().marshallAsAttribute(entry.getKey(), model, true, writer); + } + } + + @Override + public boolean isEmpty(ModelNode model) { + return this.attributes.stream().map(Map.Entry::getKey).map(AttributeDefinition::getName).noneMatch(model::hasDefined); + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceEntryAttributesXMLContentWriter.java b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceEntryAttributesXMLContentWriter.java new file mode 100644 index 00000000000..5270cca8adc --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceEntryAttributesXMLContentWriter.java @@ -0,0 +1,55 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.jboss.as.controller.persistence.xml; + +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; + +import org.jboss.as.controller.xml.XMLContentWriter; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.Property; +import org.jboss.staxmapper.XMLExtendedStreamWriter; + +/** + * Writer of XML content for a resource entry. + */ +public class ResourceEntryAttributesXMLContentWriter implements XMLContentWriter { + + private final QName pathValueAttributeName; + private final XMLContentWriter attributesWriter; + + /** + * Constructs a content writer that writes the path value attribute before writing attributes via the specified writer. + * @param pathValueAttributeName the qualified name of the path value, of null if no path value attribute should be written. + * @param attributesWriter a writer of an element's attributes + */ + ResourceEntryAttributesXMLContentWriter(QName pathValueAttributeName, XMLContentWriter attributesWriter) { + this.pathValueAttributeName = pathValueAttributeName; + this.attributesWriter = attributesWriter; + } + + @Override + public void writeContent(XMLExtendedStreamWriter writer, Property property) throws XMLStreamException { + if (this.pathValueAttributeName != null) { + String value = property.getName(); + String localName = this.pathValueAttributeName.getLocalPart(); + String namespaceURI = this.pathValueAttributeName.getNamespaceURI(); + if (namespaceURI != XMLConstants.NULL_NS_URI) { + writer.writeAttribute(namespaceURI, localName, value); + } else { + // For PersistentResourceXMLDescription compatibility + writer.writeAttribute(localName, value); + } + } + this.attributesWriter.writeContent(writer, property.getValue()); + } + + @Override + public boolean isEmpty(Property property) { + return (this.pathValueAttributeName == null) && this.attributesWriter.isEmpty(property.getValue()); + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceOperationXMLContentReader.java b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceOperationXMLContentReader.java new file mode 100644 index 00000000000..9f54a8c3317 --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceOperationXMLContentReader.java @@ -0,0 +1,41 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.jboss.as.controller.persistence.xml; + +import java.util.Map; + +import javax.xml.stream.XMLStreamException; + +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.xml.XMLCardinality; +import org.jboss.as.controller.xml.XMLContentReader; +import org.jboss.dmr.ModelNode; +import org.jboss.staxmapper.XMLExtendedStreamReader; + +/** + * Reads XML content into a resource operation. + */ +public class ResourceOperationXMLContentReader implements XMLContentReader>> { + + private final XMLContentReader reader; + + ResourceOperationXMLContentReader(XMLContentReader reader) { + this.reader = reader; + } + + @Override + public void readElement(XMLExtendedStreamReader reader, Map.Entry> context) throws XMLStreamException { + PathAddress operationKey = context.getKey(); + Map operations = context.getValue(); + ModelNode operation = operations.get(operationKey); + this.reader.readElement(reader, operation); + } + + @Override + public XMLCardinality getCardinality() { + return this.reader.getCardinality(); + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceOperationXMLElement.java b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceOperationXMLElement.java new file mode 100644 index 00000000000..887345944b4 --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceOperationXMLElement.java @@ -0,0 +1,97 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.as.controller.persistence.xml; + +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; + +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.parsing.ParseUtils; +import org.jboss.as.controller.xml.XMLCardinality; +import org.jboss.as.controller.xml.XMLContent; +import org.jboss.as.controller.xml.XMLContentReader; +import org.jboss.as.controller.xml.XMLContentWriter; +import org.jboss.as.controller.xml.XMLElement; +import org.jboss.dmr.ModelNode; +import org.jboss.staxmapper.XMLExtendedStreamReader; +import org.jboss.staxmapper.XMLExtendedStreamWriter; + +/** + * A readable/writable XML element of a resource. + * @author Paul Ferraro + */ +public class ResourceOperationXMLElement extends XMLElement.AbstractXMLElement>, C> { + + private final QName name; + private final XMLContentReader>> attributesReader; + private final XMLContentWriter attributesWriter; + private final Function model; + private final XMLContent>, ModelNode> childContent; + + ResourceOperationXMLElement(QName name, XMLContentReader attributesReader, XMLContentWriter attributesWriter, Function model, XMLContent>, ModelNode> childContent) { + this.name = name; + this.attributesReader = new ResourceOperationXMLContentReader(attributesReader); + this.attributesWriter = attributesWriter; + this.model = model; + this.childContent = childContent; + } + + @Override + public QName getName() { + return this.name; + } + + @Override + public void readElement(XMLExtendedStreamReader reader, Map.Entry> context) throws XMLStreamException { + // Validate entry criteria + // Try matching w/out namespace (for PersistentResourceXMLDescription compatibility) + if (!reader.isStartElement() || (!reader.getName().equals(this.name) && !reader.getLocalName().equals(this.name.getLocalPart()))) { + throw ParseUtils.unexpectedElement(reader, Set.of(this.name.toString())); + } + this.attributesReader.readElement(reader, context); + this.childContent.readElement(reader, context); + // Validate exit criteria + // Try matching w/out namespace (for PersistentResourceXMLDescription compatibility) + if (!reader.isEndElement() || (!reader.getName().equals(this.name) && !reader.getLocalName().equals(this.name.getLocalPart()))) { + throw ParseUtils.unexpectedElement(reader); + } + } + + @Override + public XMLCardinality getCardinality() { + return this.attributesReader.getCardinality(); + } + + @Override + public void writeContent(XMLExtendedStreamWriter writer, C content) throws XMLStreamException { + String namespaceURI = this.name.getNamespaceURI(); + // If namespace is not yet bound to any prefix, bind it + if (writer.getNamespaceContext().getPrefix(namespaceURI) == null) { + writer.writeStartElement(this.name.getLocalPart()); + // Bind and write namespace + if (namespaceURI != XMLConstants.NULL_NS_URI) { // For PersistentResourceXMLDescription compatibility + writer.setPrefix(this.name.getPrefix(), namespaceURI); + writer.writeNamespace(this.name.getPrefix(), namespaceURI); + } + } else { + writer.writeStartElement(namespaceURI, this.name.getLocalPart()); + } + this.attributesWriter.writeContent(writer, content); + + this.childContent.writeContent(writer, this.model.apply(content)); + + writer.writeEndElement(); + } + + @Override + public boolean isEmpty(C content) { + return this.attributesWriter.isEmpty(content) && this.childContent.isEmpty(this.model.apply(content)); + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceXMLChoice.java b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceXMLChoice.java new file mode 100644 index 00000000000..2c80da87068 --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceXMLChoice.java @@ -0,0 +1,29 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.as.controller.persistence.xml; + +import java.util.Map; +import java.util.Set; + +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.xml.XMLChoice; +import org.jboss.dmr.ModelNode; + +/** + * A readable/writable XML choice between one or more resources. + * @author Paul Ferraro + */ +public interface ResourceXMLChoice extends XMLChoice>, ModelNode> { + + /** + * Returns the set of resource paths for this choice. + * @return the set of resource paths for this choice. + */ + Set getPathElements(); + + abstract class AbstractResourceXMLChoice extends AbstractXMLChoice>, ModelNode> implements ResourceXMLChoice { + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceXMLElement.java b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceXMLElement.java new file mode 100644 index 00000000000..8f15637ecfc --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceXMLElement.java @@ -0,0 +1,1064 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.as.controller.persistence.xml; + +import java.util.AbstractMap; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.AttributeMarshaller; +import org.jboss.as.controller.AttributeParser; +import org.jboss.as.controller.FeatureFilter; +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.ResourceDescription; +import org.jboss.as.controller.ResourceRegistration; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.SubsystemResourceDescription; +import org.jboss.as.controller.SubsystemSchema; +import org.jboss.as.controller.descriptions.ModelDescriptionConstants; +import org.jboss.as.controller.logging.ControllerLogger; +import org.jboss.as.controller.operations.common.Util; +import org.jboss.as.controller.parsing.ParseUtils; +import org.jboss.as.controller.xml.QNameResolver; +import org.jboss.as.controller.xml.XMLCardinality; +import org.jboss.as.controller.xml.XMLChoice; +import org.jboss.as.controller.xml.XMLContent; +import org.jboss.as.controller.xml.XMLContentReader; +import org.jboss.as.controller.xml.XMLContentWriter; +import org.jboss.as.controller.xml.XMLElement; +import org.jboss.as.version.Stability; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; +import org.jboss.dmr.Property; +import org.jboss.staxmapper.XMLExtendedStreamReader; +import org.jboss.staxmapper.XMLExtendedStreamWriter; +import org.wildfly.common.Assert; +import org.wildfly.common.function.Functions; + +/** + * An XML element for a resource. + * @author Paul Ferraro + */ +public interface ResourceXMLElement extends ResourceRegistration, ResourceXMLChoice, XMLElement>, ModelNode> { + + @Override + default Set getPathElements() { + return Set.of(this.getPathElement()); + } + + /** + * A builder of a {@link ResourceXMLElement}. + */ + interface Builder extends QNameResolver { + + /** + * A factory that creates a builder of a {@link ResourceXMLElement}. + */ + interface Factory extends QNameResolver { + /** + * Creates a new {@link ResourceXMLElement} builder for the specified subsystem resource description. + * @param description a subsystem resource description + * @return a new {@link ResourceXMLElement} builder + */ + Builder createBuilder(SubsystemResourceDescription description); + + /** + * Creates a new {@link ResourceXMLElement} builder for the specified resource description. + * @param description a resource description + * @return a new {@link ResourceXMLElement} builder + */ + Builder createBuilder(ResourceDescription description); + + /** + * Returns a factory for creating {@link ResourceXMLElement} builder instances for the specified subsystem schema. + * @param the schema type + * @param schema a subsystem schema + * @return a factory for creating {@link ResourceXMLElement} builder instances + */ + static > Factory newInstance(S schema) { + return new Factory() { + @Override + public Builder createBuilder(SubsystemResourceDescription description) { + return new DefaultBuilder(schema, description, this).withElementLocalName(ResourceXMLElementLocalName.KEY); + } + + @Override + public Builder createBuilder(ResourceDescription description) { + return new DefaultBuilder(schema, description, this); + } + + @Override + public QName resolveQName(String localName) { + return new QName(schema.getNamespace().getUri(), localName); + } + }; + } + } + + /** + * Indicates that this resource is required to be present. + * This is a convenience method that delegates to {@link #withCardinality(XMLCardinality)}. + * @return a reference to this builder. + */ + Builder require(); + + /** + * Overrides the cardinality of this resource. + * @return a reference to this builder. + */ + Builder withCardinality(XMLCardinality cardinality); + + /** + * Overrides the local name of the attribute used to create the path for this resource. + * Defaults to {@value ModelDescriptionConstants#NAME} if unspecified. + * @param localName a attribute local name. + * @return a reference to this builder. + */ + default Builder withPathValueAttributeLocalName(String localName) { + return this.withPathValueAttributeName(this.resolveQName(localName)); + } + + /** + * Overrides the local name of the attribute used to create the path for this resource. + * Defaults to {@value ModelDescriptionConstants#NAME} if unspecified. + * @param name a attribute local name. + * @return a reference to this builder. + */ + Builder withPathValueAttributeName(QName name); + + /** + * Overrides the logic used to determine the local name of a given attribute. + * Defaults to {@link AttributeParser#getXmlName(AttributeDefinition)} if unspecified. + * @param localNames a function used to determine the local name of a given attribute. + * @return a reference to this builder. + */ + default Builder withLocalNames(Function localNames) { + return this.withNames(localNames.andThen(this::resolveQName)); + } + + /** + * Provides a local name overrides for a specific set of attributes. + * @param localNames a mapping of attribute to local name + * @return a reference to this builder. + */ + Builder withLocalNames(Map localNames); + + /** + * Overrides the logic used to determine the local name of a given attribute. + * Defaults to {@link AttributeParser#getXmlName(AttributeDefinition)} if unspecified. + * @param localNames a function used to determine the local name of a given attribute. + * @return a reference to this builder. + */ + Builder withNames(Function names); + + /** + * Provides a local name overrides for a specific set of attributes. + * @param localNames a mapping of attribute to local name + * @return a reference to this builder. + */ + Builder withNames(Map names); + + /** + * Overrides the logic used to determine the parser of a given attribute. + * Defaults to {@link AttributeDefinition#getParser()} if unspecified. + * @param parsers a function used to determine the parser of a given attribute. + * @return a reference to this builder. + */ + default Builder withParsers(Map parsers) { + return this.withParsers(new Function<>() { + @Override + public AttributeParser apply(AttributeDefinition attribute) { + return parsers.getOrDefault(attribute, attribute.getParser()); + } + }); + } + + /** + * Overrides the logic used to determine the parser of a given attribute. + * Defaults to {@link AttributeDefinition#getAttributeParser()} if unspecified. + * @param parsers a function used to determine the parser of a given attribute. + * @return a reference to this builder. + */ + Builder withParsers(Function parsers); + + /** + * Overrides the logic used to determine the parser of a given attribute. + * Defaults to {@link AttributeDefinition#getParser()} if unspecified. + * @param parsers a function used to determine the parser of a given attribute. + * @return a reference to this builder. + */ + default Builder withMarshallers(Map marshallers) { + return this.withMarshallers(new Function<>() { + @Override + public AttributeMarshaller apply(AttributeDefinition attribute) { + return marshallers.getOrDefault(attribute, attribute.getMarshaller()); + } + }); + } + + /** + * Overrides the logic used to determine the parser of a given attribute. + * Defaults to {@link AttributeDefinition#getAttributeParser()} if unspecified. + * @param parsers a function used to determine the parser of a given attribute. + * @return a reference to this builder. + */ + Builder withMarshallers(Function marshallers); + + /** + * Overrides the element local name of this resource. + * @param localName the local element name override. + * @return a reference to this builder. + */ + default Builder withElementLocalName(String localName) { + return this.withElementName(this.resolveQName(localName)); + } + + /** + * Overrides the logic used to determine the element local name of this resource. + * @see {@link ResourceXMLElementLocalName} + * @param localName a function returning the element local name for a given path. + * @return a reference to this builder. + */ + default Builder withElementLocalName(Function localName) { + return this.withElementName(localName.andThen(this::resolveQName)); + } + + /** + * Overrides the logic used to determine the local element name of this resource. + * @see {@link ResourceXMLElementLocalName} + * @param function a function returning the qualified element name for a given path. + * @return a reference to this builder. + */ + default Builder withElementName(QName name) { + return this.withElementName(new Function<>() { + @Override + public QName apply(PathElement path) { + return name; + } + }); + } + + /** + * Overrides the logic used to determine the element local name of this resource. + * @see {@link ResourceXMLElementLocalName} + * @param name a function returning the qualified element name for a given path. + * @return a reference to this builder. + */ + Builder withElementName(Function name); + + /** + * Overrides the default element local names using the specific mapping. + * If the attribute group does not exist in the specified map, those attributes will not be grouped within an element. + * @param elementLocalNames a map of attribute group name to element local name + * @return a reference to this builder. + */ + default Builder withAttributeGroupElementLocalNames(Map localNames) { + return this.withAttributeGroupElementLocalNames(localNames::get); + } + + /** + * Overrides the logic used to determine the element local name of an attribute group. + * Defaults to {@link UnaryOperator#identity()} if unspecified. + * If the specified function returns null, attributes of a given a group will not be parsed/marshalled within an element. + * @param elementLocalName a function returning the element local name of a given attribute group + * @return a reference to this builder. + */ + default Builder withAttributeGroupElementLocalNames(UnaryOperator localNames) { + return this.withAttributeGroupElementNames(new Function<>() { + @Override + public QName apply(String groupName) { + String localName = localNames.apply(groupName); + return (localName != null) ? Builder.this.resolveQName(localName) : null; + } + }); + } + + /** + * Overrides the default element local names using the specific mapping. + * If the attribute group does not exist in the specified map, those attributes will not be grouped within an element. + * @param elementLocalNames a map of attribute group name to element local name + * @return a reference to this builder. + */ + default Builder withAttributeGroupElementNames(Map names) { + return this.withAttributeGroupElementNames(names::get); + } + + /** + * Overrides the logic used to determine the element local name of an attribute group. + * Defaults to {@link UnaryOperator#identity()} if unspecified. + * If the specified function returns null, attributes of a given a group will not be parsed/marshalled within an element. + * @param elementLocalName a function returning the element local name of a given attribute group + * @return a reference to this builder. + */ + Builder withAttributeGroupElementNames(Function names); + + /** + * Applies the specified operation to the attributes of this resource. + * Defaults to {@link UnaryOperator#identity()} if unspecified. + * Used to filter and/or prepend/append attributes. + * @param filter an attribute filter + * @return a reference to this builder. + */ + Builder filterAttributes(UnaryOperator> filter); + + /** + * Includes the specified attributes. + * @param includedAttributes a collection of included attributes + * @return a reference to this builder. + */ + default Builder includeAttribute(AttributeDefinition includedAttribute) { + return this.filterAttributes(new UnaryOperator<>() { + @Override + public Stream apply(Stream attributes) { + return Stream.concat(attributes, Stream.of(includedAttribute)); + } + }); + } + + /** + * Includes the specified attributes. + * @param includedAttributes a collection of included attributes + * @return a reference to this builder. + */ + default Builder includeAttributes(Collection includedAttributes) { + return this.filterAttributes(new UnaryOperator<>() { + @Override + public Stream apply(Stream attributes) { + return Stream.concat(attributes, includedAttributes.stream()); + } + }); + } + + /** + * Includes an ignored attribute with the specified name. + * @param attributeLocalName the local name of an attribute that should be ignored by the parser. + * @return a reference to this builder. + */ + default Builder ignoreAttribute(String attributeLocalName) { + return this.includeAttribute(DefaultBuilder.createIgnoredAttributeDefinition(attributeLocalName)); + } + + /** + * Includes a set of ignored attributes with the specified names in this description. + * @param attributeLocalNames a set of attribute local names that should be ignored by the parser. + * @return a reference to this builder. + */ + default Builder ignoreAttributes(Set attributeLocalNames) { + return this.includeAttributes(attributeLocalNames.stream().map(DefaultBuilder::createIgnoredAttributeDefinition).toList()); + } + + /** + * Excludes the specified attributes. + * @param excludedAttributes a collection of excluded attributes + * @return a reference to this builder. + */ + default Builder excludeAttribute(AttributeDefinition excludedAttribute) { + return this.filterAttributes(new UnaryOperator<>() { + @Override + public Stream apply(Stream attributes) { + return attributes.filter(Predicate.not(excludedAttribute::equals)); + } + }); + } + + /** + * Excludes the specified attributes. + * @param excludedAttributes a collection of excluded attributes + * @return a reference to this builder. + */ + default Builder excludeAttributes(Collection excludedAttributes) { + return this.filterAttributes(new UnaryOperator<>() { + @Override + public Stream apply(Stream attributes) { + return attributes.filter(Predicate.not(excludedAttributes::contains)); + } + }); + } + + /** + * Indicates that this element can be omitted if all of its attributes are undefined and any child resources are also empty. + * @return a reference to this builder. + */ + Builder omitIfEmpty(); + + /** + * Adds a child resource to this description to be written before any element-based attributes of this resource. + * @param description an XML description of a child resource. + * @return a reference to this builder. + */ + Builder insertChild(XMLChoice>, ModelNode> choice); + + /** + * Adds a child resource to this description to be written after any element-based attributes of this resource. + * @param description an XML description of a child resource. + * @return a reference to this builder. + */ + Builder addChild(XMLChoice>, ModelNode> choice); + + /** + * Adds a child resource to this description to be written before any element-based attributes of the specified attribute group. + * @param attribute a grouped attribute. + * @param choice a choice. + * @return a reference to this builder. + */ + Builder insertChild(AttributeDefinition attribute, XMLChoice>, ModelNode> choice); + + /** + * Adds a child resource to this description to be written after any element-based attributes of the specified attribute group. + * @param attribute a grouped attribute. + * @param choice a choice. + * @return a reference to this builder. + */ + Builder addChild(AttributeDefinition attribute, XMLChoice>, ModelNode> choice); + + /** + * Indicates that the operation associated with this resource should be discarded. + * @return a reference to this builder. + */ + default Builder thenDiscardOperation() { + BiConsumer, PathAddress> remove = Map::remove; + return this.withOperationTransformation(remove); + } + + /** + * Specifies an operation transformation function, applied after this resource and any children are parsed into an {@value ModelDescriptionConstants#ADD} operation. + * Defaults to {@link UnaryOperator#identity()} if unspecified. + * If this operator returns null, the {@value ModelDescriptionConstants#ADD} operation will be discarded. + * @param transformer an operation transformer + * @return a reference to this builder. + */ + default Builder withOperationTransformation(UnaryOperator transformer) { + return this.withOperationTransformation(new BiFunction<>() { + @Override + public ModelNode apply(PathAddress key, ModelNode operation) { + return transformer.apply(operation); + } + }); + } + + /** + * Specifies an operation remapping function, applied after this resource and any children are parsed into an {@value ModelDescriptionConstants#ADD} operation. + * If this function returns null, the {@value ModelDescriptionConstants#ADD} operation will be discarded. + * @param remappingFunction a remapping function for the current operation + * @return a reference to this builder. + */ + default Builder withOperationTransformation(BiFunction remappingFunction) { + return this.withOperationTransformation(new BiConsumer<>() { + @Override + public void accept(Map operations, PathAddress operationKey) { + operations.compute(operationKey, remappingFunction); + } + }); + } + + /** + * Specifies an operation transformation function, applied after this resource and any children are parsed into an {@value ModelDescriptionConstants#ADD} operation. + * Defaults to {@link Functions#discardingBiConsumer()} if unspecified. + * @param transformation a consumer accepting all operations and the key of the current operation + * @return a reference to this builder. + */ + Builder withOperationTransformation(BiConsumer, PathAddress> transformation); + + /** + * Indicates that the child elements of this resource should be parsed using xs:sequence semantics. + * @return a reference to this builder. + */ + Builder withSequentialChildren(); + + /** + * Indicates that the child elements of the element for the specified attribute group should be parsed using xs:sequence semantics. + * @return a reference to this builder. + */ + Builder withSequentialChildren(AttributeDefinition attribute); + + /** + * Builds an XML choice for this resource, using the specified override elements + * @return an XML choice for this resource. + */ + ResourceXMLChoice build(Collection overrideElements); + + /** + * Builds an XML element for this resource + * @return an XML element for this resource. + */ + ResourceXMLElement build(); + } + + static class DefaultBuilder implements Builder { + private static final AttributeParser IGNORED_PARSER = new AttributeParser() { + @Override + public void parseAndSetParameter(AttributeDefinition attribute, String value, ModelNode operation, XMLStreamReader reader) throws XMLStreamException { + ControllerLogger.ROOT_LOGGER.attributeIgnored(reader.getName(), new QName(reader.getNamespaceURI(), attribute.getXmlName())); + } + }; + private static final AttributeParser NO_OP_PARSER = new AttributeParser() { + @Override + public void parseAndSetParameter(AttributeDefinition attribute, String value, ModelNode operation, XMLStreamReader reader) throws XMLStreamException { + } + }; + private static final AttributeMarshaller NO_OP_MARSHALLER = new AttributeMarshaller() { + @Override + public void marshallAsAttribute(AttributeDefinition attribute, ModelNode resourceModel, boolean marshallDefault, XMLStreamWriter writer) throws XMLStreamException { + // Do nothing + } + }; + static AttributeDefinition createIgnoredAttributeDefinition(String localName) { + return createIgnoredAttributeDefinition(localName, IGNORED_PARSER); + } + private static AttributeDefinition createIgnoredAttributeDefinition(String localName, AttributeParser parser) { + return new SimpleAttributeDefinitionBuilder(localName, ModelType.STRING) + .setRequired(false) + .setAttributeParser(parser) + .setAttributeMarshaller(NO_OP_MARSHALLER) + .build(); + } + + final FeatureFilter filter; + final ResourceDescription description; + final QNameResolver resolver; + volatile Function elementName; + volatile QName pathValueAttributeName; + volatile Function, Stream> attributesFilter = UnaryOperator.identity(); + volatile Function names; + volatile Function parsers = AttributeDefinition::getParser; + volatile Function marshallers = AttributeDefinition::getMarshaller; + volatile Function groupElementNames; + volatile Predicate write = ModelNode::isDefined; + volatile boolean omitIfEmpty = false; + volatile BiConsumer, PathAddress> operationTransformation = Functions.discardingBiConsumer(); + volatile List>, ModelNode>> preAttributeElements = List.of(); + volatile List>, ModelNode>> postAttributeElements = List.of(); + volatile Map>, ModelNode>>> preAttributeGroupElements = Map.of(); + volatile Map>, ModelNode>>> postAttributeGroupElements = Map.of(); + volatile XMLCardinality cardinality; + volatile Set sequentialChildGroups = Set.of(); // The set of attribute groups containing sequential children + volatile boolean sequentialChildren = false; + + protected DefaultBuilder(FeatureFilter filter, ResourceDescription description, QNameResolver qualifiedNameFactory) { + this.filter = filter; + this.description = description; + this.resolver = qualifiedNameFactory; + boolean wildcard = description.getPathElement().isWildcard(); + this.cardinality = wildcard ? XMLCardinality.Unbounded.OPTIONAL : XMLCardinality.Single.OPTIONAL; + this.pathValueAttributeName = wildcard ? this.resolver.resolveQName(ModelDescriptionConstants.NAME) : null; + Function elementName = this.resolver::resolveQName; + this.elementName = elementName.compose(wildcard ? ResourceXMLElementLocalName.KEY : ResourceXMLElementLocalName.VALUE); + this.groupElementNames = elementName; + this.names = elementName.compose(new Function<>() { + @Override + public String apply(AttributeDefinition attribute) { + return DefaultBuilder.this.getParser(attribute).getXmlName(attribute); + } + }); + } + + private AttributeParser getParser(AttributeDefinition attribute) { + return this.parsers.apply(attribute); + } + + @Override + public QName resolveQName(String localName) { + return this.resolver.resolveQName(localName); + } + + @Override + public Builder require() { + return this.withCardinality(this.description.getPathElement().isWildcard() ? XMLCardinality.Unbounded.REQUIRED : XMLCardinality.Single.REQUIRED); + } + + @Override + public Builder withCardinality(XMLCardinality cardinality) { + this.cardinality = cardinality; + return this; + } + + @Override + public Builder withPathValueAttributeName(QName name) { + this.pathValueAttributeName = name; + return this; + } + + @Override + public Builder withLocalNames(Map localNames) { + Function defaultNames = this.names; + return this.withLocalNames(new Function<>() { + @Override + public String apply(AttributeDefinition attribute) { + return localNames.getOrDefault(attribute, defaultNames.apply(attribute).getLocalPart()); + } + }); + } + + @Override + public Builder withNames(Function names) { + this.names = names; + return this; + } + + @Override + public Builder withNames(Map names) { + Function defaultNames = this.names; + return this.withNames(new Function<>() { + @Override + public QName apply(AttributeDefinition attribute) { + return names.getOrDefault(attribute, defaultNames.apply(attribute)); + } + }); + } + + @Override + public Builder withParsers(Function parsers) { + this.parsers = parsers; + return this; + } + + @Override + public Builder withMarshallers(Function marshallers) { + this.marshallers = marshallers; + return this; + } + + @Override + public Builder withElementName(Function elementName) { + this.elementName = elementName; + return this; + } + + @Override + public Builder withAttributeGroupElementNames(Function groupElementNames) { + this.groupElementNames = groupElementNames; + return this; + } + + @Override + public Builder filterAttributes(UnaryOperator> filter) { + this.attributesFilter = this.attributesFilter.andThen(filter); + return this; + } + + @Override + public Builder insertChild(AttributeDefinition attribute, XMLChoice>, ModelNode> choice) { + String group = attribute.getAttributeGroup(); + Assert.assertNotNull(group); + if (this.preAttributeGroupElements.isEmpty()) { + this.preAttributeGroupElements = Map.of(group, new LinkedList<>()); + } + List>, ModelNode>> choices = this.preAttributeGroupElements.get(group); + if (choices == null) { + choices = new LinkedList<>(); + if (this.preAttributeGroupElements.size() == 1) { + this.preAttributeGroupElements = new HashMap<>(this.preAttributeGroupElements); + } + this.preAttributeGroupElements.put(group, choices); + } + choices.add(choice); + return this; + } + + @Override + public Builder addChild(AttributeDefinition attribute, XMLChoice>, ModelNode> choice) { + String group = attribute.getAttributeGroup(); + Assert.assertNotNull(group); + if (this.postAttributeGroupElements.isEmpty()) { + this.postAttributeGroupElements = Map.of(group, new LinkedList<>()); + } + List>, ModelNode>> choices = this.postAttributeGroupElements.get(group); + if (choices == null) { + choices = new LinkedList<>(); + if (this.postAttributeGroupElements.size() == 1) { + this.postAttributeGroupElements = new HashMap<>(this.postAttributeGroupElements); + } + this.postAttributeGroupElements.put(group, choices); + } + choices.add(choice); + return this; + } + + @Override + public Builder insertChild(XMLChoice>, ModelNode> choice) { + if (this.preAttributeElements.isEmpty()) { + this.preAttributeElements = new LinkedList<>(); + } + this.preAttributeElements.add(choice); + return this; + } + + @Override + public Builder addChild(XMLChoice>, ModelNode> choice) { + if (this.postAttributeElements.isEmpty()) { + this.postAttributeElements = new LinkedList<>(); + } + this.postAttributeElements.add(choice); + return this; + } + + @Override + public Builder omitIfEmpty() { + this.omitIfEmpty = true; + return this; + } + + @Override + public Builder withOperationTransformation(BiConsumer, PathAddress> transformation) { + this.operationTransformation = this.operationTransformation.andThen(transformation); + return this; + } + + @Override + public Builder withSequentialChildren() { + this.sequentialChildren = true; + return this; + } + + @Override + public Builder withSequentialChildren(AttributeDefinition attribute) { + String group = attribute.getAttributeGroup(); + if (group != null) { + if (this.sequentialChildGroups.isEmpty()) { + this.sequentialChildGroups = new TreeSet<>(); + } + this.sequentialChildGroups.add(group); + } + return this; + } + + @Override + public ResourceXMLElement build() { + ResourceDescription description = this.description; + PathElement path = description.getPathElement(); + QName name = this.elementName.apply(path); + + Function, Stream> attributesFilter = this.attributesFilter; + FeatureFilter filter = this.filter; + Supplier> attributesProvider = new Supplier<>() { + @Override + public Stream get() { + return attributesFilter.apply(description.getAttributes()).filter(filter::enables); + } + }; + QName pathValueAttributeName = this.pathValueAttributeName; + // Pseudo attribute for the path value + AttributeDefinition pathValueAttribute = (pathValueAttributeName != null) ? createIgnoredAttributeDefinition(UUID.randomUUID().toString(), NO_OP_PARSER) : null; + + Function configuredNames = this.names; + Function names = (pathValueAttribute != null) ? new Function<>() { + @Override + public QName apply(AttributeDefinition attribute) { + // Associate path value attribute with its QName + return (attribute == pathValueAttribute) ? pathValueAttributeName : configuredNames.apply(attribute); + } + } : configuredNames; + + Function parsers = this.parsers; + Function marshallers = this.marshallers; + Function groupElementNames = this.groupElementNames; + List>, ModelNode>> preAttributeElements = List.copyOf(this.preAttributeElements); + List>, ModelNode>> postAttributeElements = List.copyOf(this.postAttributeElements); + boolean omitIfEmpty = this.omitIfEmpty; + + BiConsumer, PathAddress> operationTransformation = this.operationTransformation; + Map, List>, ModelNode>>>> groups = this.preAttributeGroupElements.isEmpty() && this.postAttributeGroupElements.isEmpty() ? Map.of() : new LinkedHashMap<>(); + + // Pre-attribute group elements + for (Map.Entry>, ModelNode>>> groupElements : this.preAttributeGroupElements.entrySet()) { + QName groupName = this.groupElementNames.apply(groupElements.getKey()); + Map.Entry, List>, ModelNode>>> group = groups.get(groupName); + if (group == null) { + group = new AbstractMap.SimpleEntry<>(new HashMap<>(), new LinkedList<>()); + groups.put(groupName, group); + } + for (XMLChoice>, ModelNode> choice : groupElements.getValue()) { + if (this.filter.enables(choice)) { + group.getValue().add(choice); + } + } + } + + Iterator resourceAttributes = attributesProvider.get().iterator(); + Map.Entry, List>, ModelNode>>> defaultGroup = new AbstractMap.SimpleEntry<>(resourceAttributes.hasNext() || (pathValueAttribute != null) ? new HashMap<>() : Map.of(), preAttributeElements.isEmpty() && postAttributeElements.isEmpty() ? List.of() : new LinkedList<>()); + + if (pathValueAttribute != null) { + defaultGroup.getKey().put(pathValueAttributeName, pathValueAttribute); + } + + // Early children + for (XMLChoice>, ModelNode> choice : preAttributeElements) { + if (this.filter.enables(choice)) { + defaultGroup.getValue().add(choice); + } + } + + Set sequentialGroups = !this.sequentialChildGroups.isEmpty() ? this.sequentialChildGroups.stream().map(groupElementNames).collect(Collectors.toSet()) : Set.of(); + + // Process attributes and groups + while (resourceAttributes.hasNext()) { + AttributeDefinition attribute = resourceAttributes.next(); + QName attributeGroupName = Optional.ofNullable(attribute.getAttributeGroup()).map(groupElementNames).orElse(null); + Map.Entry, List>, ModelNode>>> group = (attributeGroupName == null) ? defaultGroup : groups.get(attributeGroupName); + if (group == null) { + if (groups.isEmpty()) { + groups = new LinkedHashMap<>(); + } + group = new AbstractMap.SimpleEntry<>(new HashMap<>(), List.of()); + groups.put(attributeGroupName, group); + } + AttributeParser parser = parsers.apply(attribute); + AttributeMarshaller marshaller = marshallers.apply(attribute); + QName attributeName = names.apply(attribute); + if (parser.isParseAsElement() || marshaller.isMarshallableAsElement()) { + if (group.getValue().isEmpty()) { + group.setValue(new LinkedList<>()); + } + group.getValue().add(new ResourceAttributeXMLElement(attributeName, attribute, parser, marshaller)); + } else if (!parser.isParseAsElement() || !marshaller.isMarshallableAsElement()) { + AttributeDefinition existing = group.getKey().put(attributeName, attribute); + if (existing != null) { + throw ControllerLogger.ROOT_LOGGER.duplicateAttributes(Optional.ofNullable(attributeGroupName).orElse(name), attributeName); + } + } + } + + // Post-attribute group elements + for (Map.Entry>, ModelNode>>> groupElements : this.postAttributeGroupElements.entrySet()) { + QName groupName = this.groupElementNames.apply(groupElements.getKey()); + Map.Entry, List>, ModelNode>>> group = groups.get(groupName); + if (group.getValue().isEmpty()) { + group.setValue(new LinkedList<>()); + } + for (XMLChoice>, ModelNode> choice : groupElements.getValue()) { + if (this.filter.enables(choice)) { + group.getValue().add(choice); + } + } + } + + // Attribute group elements + if (!groups.isEmpty()) { + if (defaultGroup.getValue().isEmpty()) { + defaultGroup.setValue(new LinkedList<>()); + } + for (Map.Entry, List>, ModelNode>>>> groupEntry : groups.entrySet()) { + QName groupName = groupEntry.getKey(); + Map groupAttributes = groupEntry.getValue().getKey(); + List>, ModelNode>> groupChildren = groupEntry.getValue().getValue(); + XMLContentReader attributesReader = new ResourceAttributesXMLContentReader(groupAttributes, parsers); + XMLContentWriter attributesWriter = new ResourceAttributesXMLContentWriter(groupAttributes.values(), marshallers); + XMLContent>, ModelNode> childContent = sequentialGroups.contains(groupName) ? XMLContent.sequence(groupChildren) : XMLContent.all(groupChildren); + defaultGroup.getValue().add(new ResourceOperationXMLElement<>(groupName, attributesReader, attributesWriter, Function.identity(), childContent)); + } + } + + // Late children + for (XMLChoice>, ModelNode> choice : postAttributeElements) { + if (this.filter.enables(choice)) { + defaultGroup.getValue().add(choice); + } + } + + XMLCardinality cardinality = this.cardinality; + + Map defaultGroupAttributes = defaultGroup.getKey(); + List>, ModelNode>> defaultGroupChildren = defaultGroup.getValue(); + XMLContentReader attributesReader = new ResourceAttributesXMLContentReader(defaultGroupAttributes, parsers); + XMLContentWriter attributesWriter = new ResourceAttributesXMLContentWriter(defaultGroupAttributes.values(), marshallers); + XMLContent>, ModelNode> childContent = this.sequentialChildren ? XMLContent.sequence(defaultGroupChildren) : XMLContent.all(defaultGroupChildren); + ResourceOperationXMLElement element = new ResourceOperationXMLElement<>(name, attributesReader, new ResourceEntryAttributesXMLContentWriter(pathValueAttributeName, attributesWriter), Property::getValue, childContent); + + return new AbstractResourceXMLElement() { + @Override + public QName getName() { + return name; + } + + @Override + public PathElement getPathElement() { + return path; + } + + @Override + public Stability getStability() { + return description.getStability(); + } + + @Override + public void readElement(XMLExtendedStreamReader reader, Map.Entry> context) throws XMLStreamException { + PathAddress parentOperationKey = context.getKey(); + Map operations = context.getValue(); + + String value = (pathValueAttributeName != null) ? reader.getAttributeValue(null, pathValueAttributeName.getLocalPart()) : null; + if (path.isWildcard() && (value == null)) { + throw ParseUtils.missingRequired(reader, pathValueAttributeName.getLocalPart()); + } + + ModelNode parentOperation = (parentOperationKey.size() > 0) ? operations.get(parentOperationKey) : null; + PathAddress parentAddress = (parentOperation != null) ? PathAddress.pathAddress(parentOperation.get(ModelDescriptionConstants.OP_ADDR)) : PathAddress.EMPTY_ADDRESS; + PathAddress operationAddress = parentAddress.append(path.isWildcard() ? PathElement.pathElement(path.getKey(), value) : path); + PathAddress operationKey = path.isWildcard() ? operationAddress : parentOperationKey.append(description.getPathKey()); + ModelNode operation = Util.createAddOperation(operationAddress); + operations.put(operationKey, operation); + + element.readElement(reader, Map.entry(operationKey, operations)); + operationTransformation.accept(operations, operationKey); + } + + @Override + public void writeContent(XMLExtendedStreamWriter writer, ModelNode parentModel) throws XMLStreamException { + String key = path.getKey(); + if (parentModel.hasDefined(key)) { + ModelNode keyModel = parentModel.get(key); + + List properties = path.isWildcard() ? keyModel.asPropertyList() : (keyModel.hasDefined(path.getValue()) ? List.of(new Property(path.getValue(), keyModel.get(path.getValue()))) : List.of()); + for (Property property : properties) { + if (!omitIfEmpty || !element.isEmpty(property)) { + element.writeContent(writer, property); + } + } + } + } + + @Override + public boolean isEmpty(ModelNode parentModel) { + return !parentModel.hasDefined(path.getKey()) || (!path.isWildcard() && !parentModel.hasDefined(path.getKeyValuePair())); + } + + @Override + public XMLCardinality getCardinality() { + return cardinality; + } + }; + } + + @Override + public ResourceXMLChoice build(Collection overrideElements) { + // Build element for wildcard registration + ResourceXMLElement element = this.build(); + if (overrideElements.isEmpty()) return element; + + ResourceDescription description = this.description; + PathElement elementPath = description.getPathElement(); + Assert.assertTrue(elementPath.isWildcard()); + + Map>>>> readers = new HashMap<>(); + Map writers = new HashMap<>(); + readers.put(element.getName(), Map.of(element.getPathElement(), element)); + writers.put(element.getPathElement(), element); + for (ResourceXMLElement overrideElement : overrideElements) { + QName overrideName = overrideElement.getName(); + Map>>> paths = readers.get(overrideName); + if (paths == null) { + paths = new HashMap<>(); + readers.put(overrideName, paths); + } + PathElement overridePath = overrideElement.getPathElement(); + Assert.assertFalse(overridePath.isWildcard()); + Assert.assertTrue(elementPath.getKey().equals(overridePath.getKey())); + paths.put(overridePath, overrideElement); + writers.put(overridePath, overrideElement); + } + QName pathValueAttributeName = this.pathValueAttributeName; + + return new AbstractResourceXMLChoice() { + @Override + public Set getChoices() { + return readers.keySet(); + } + + @Override + public Set getPathElements() { + return writers.keySet(); + } + + @Override + public XMLContentReader>> getReader(QName name) { + Map>>> elements = readers.get(name); + return new XMLContentReader<>() { + @Override + public void readElement(XMLExtendedStreamReader reader, Map.Entry> context) throws XMLStreamException { + String value = reader.getAttributeValue(null, pathValueAttributeName.getLocalPart()); + if (value == null) { + throw ParseUtils.missingRequired(reader, pathValueAttributeName.getLocalPart()); + } + PathElement path = PathElement.pathElement(elementPath.getKey(), value); + elements.getOrDefault(path, element).readElement(reader, context); + } + + @Override + public XMLCardinality getCardinality() { + return XMLCardinality.Unbounded.OPTIONAL; + } + }; + } + + @Override + public void writeContent(XMLExtendedStreamWriter writer, ModelNode parent) throws XMLStreamException { + PathElement path = element.getPathElement(); + String key = path.getKey(); + if (parent.hasDefined(key)) { + ModelNode keyModel = parent.get(key); + + List properties = path.isWildcard() ? keyModel.asPropertyList() : (keyModel.hasDefined(path.getValue()) ? List.of(new Property(path.getValue(), keyModel.get(path.getValue()))) : List.of()); + for (Property property : properties) { + String value = property.getName(); + ModelNode model = property.getValue(); + + PathElement overridePath = PathElement.pathElement(key, value); + ModelNode parentWrapper = new ModelNode(); + parentWrapper.get(overridePath.getKeyValuePair()).set(model); + writers.getOrDefault(overridePath, element).writeContent(writer, parentWrapper); + } + } + } + + @Override + public Stability getStability() { + Stability stability = element.getStability(); + for (ResourceXMLElement overrideElement : overrideElements) { + if (stability.enables(overrideElement.getStability())) { + stability = overrideElement.getStability(); + } + } + return stability; + } + + @Override + public boolean isEmpty(ModelNode parent) { + return !parent.hasDefined(element.getPathElement().getKey()); + } + + @Override + public XMLCardinality getCardinality() { + return element.getCardinality(); + } + }; + } + } + + abstract class AbstractResourceXMLElement extends AbstractXMLElement>, ModelNode> implements ResourceXMLElement { + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceXMLElementLocalName.java b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceXMLElementLocalName.java new file mode 100644 index 00000000000..1da02c69e7d --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/persistence/xml/ResourceXMLElementLocalName.java @@ -0,0 +1,41 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.as.controller.persistence.xml; + +import java.util.function.Function; + +import org.jboss.as.controller.PathElement; + +/** + * Functions resolving the local element name for the {@link PathElement} of a resource. + * @author Paul Ferraro + */ +public enum ResourceXMLElementLocalName implements Function { + KEY_VALUE() { + @Override + public String apply(PathElement path) { + return String.join("-", path.getKey(), path.getValue()); + } + }, + VALUE_KEY() { + @Override + public String apply(PathElement path) { + return String.join("-", path.getValue(), path.getKey()); + } + }, + KEY() { + @Override + public String apply(PathElement path) { + return path.getKey(); + } + }, + VALUE() { + @Override + public String apply(PathElement path) { + return path.getValue(); + } + }, + ; +} diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/xml/SubsystemResourceXMLElementReader.java b/controller/src/main/java/org/jboss/as/controller/persistence/xml/SubsystemResourceXMLElementReader.java new file mode 100644 index 00000000000..0280b86e1a2 --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/persistence/xml/SubsystemResourceXMLElementReader.java @@ -0,0 +1,41 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.as.controller.persistence.xml; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.xml.stream.XMLStreamException; + +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.parsing.ParseUtils; +import org.jboss.dmr.ModelNode; +import org.jboss.staxmapper.XMLElementReader; +import org.jboss.staxmapper.XMLExtendedStreamReader; + +/** + * StAX reader for a subsystem. + * @author Paul Ferraro + */ +public class SubsystemResourceXMLElementReader implements XMLElementReader> { + private final ResourceXMLElement element; + + public SubsystemResourceXMLElementReader(ResourceXMLElement element) { + this.element = element; + } + + @Override + public void readElement(XMLExtendedStreamReader reader, List operations) throws XMLStreamException { + if (!reader.getName().equals(this.element.getName())) { + throw ParseUtils.unexpectedElement(reader, Set.of(this.element.getName().getLocalPart())); + } + // An index of operations preserving insertion order + Map index = new LinkedHashMap<>(); + this.element.readElement(reader, Map.entry(PathAddress.EMPTY_ADDRESS, index)); + operations.addAll(index.values()); + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/xml/SubsystemResourceXMLElementWriter.java b/controller/src/main/java/org/jboss/as/controller/persistence/xml/SubsystemResourceXMLElementWriter.java new file mode 100644 index 00000000000..c4f740fdbc2 --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/persistence/xml/SubsystemResourceXMLElementWriter.java @@ -0,0 +1,32 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.as.controller.persistence.xml; + +import javax.xml.stream.XMLStreamException; + +import org.jboss.as.controller.persistence.SubsystemMarshallingContext; +import org.jboss.dmr.ModelNode; +import org.jboss.staxmapper.XMLElementWriter; +import org.jboss.staxmapper.XMLExtendedStreamWriter; + +/** + * StAX writer for a subsystem. + * @author Paul Ferraro + */ +public class SubsystemResourceXMLElementWriter implements XMLElementWriter{ + private final ResourceXMLElement element; + + public SubsystemResourceXMLElementWriter(ResourceXMLElement element) { + this.element = element; + } + + @Override + public void writeContent(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException { + ModelNode subsystemModel = context.getModelNode(); + ModelNode model = new ModelNode(); + model.get(this.element.getPathElement().getKeyValuePair()).set(subsystemModel); + this.element.writeContent(writer, model); + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/xml/SubsystemResourceXMLSchema.java b/controller/src/main/java/org/jboss/as/controller/persistence/xml/SubsystemResourceXMLSchema.java new file mode 100644 index 00000000000..a9f3bbef077 --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/persistence/xml/SubsystemResourceXMLSchema.java @@ -0,0 +1,27 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.as.controller.persistence.xml; + +import java.util.List; + +import javax.xml.stream.XMLStreamException; + +import org.jboss.as.controller.SubsystemSchema; +import org.jboss.dmr.ModelNode; +import org.jboss.staxmapper.XMLExtendedStreamReader; + +/** + * Analogous to {@link org.jboss.as.controller.PersistentSubsystemSchema}, but using {@link ResourceXMLElement} instead of {@link org.jboss.as.controller.PersistentResourceXMLDescription}. + * @author Paul Ferraro + */ +public interface SubsystemResourceXMLSchema> extends SubsystemSchema { + + ResourceXMLElement getSubsystemResourceXMLElement(); + + @Override + default void readElement(XMLExtendedStreamReader reader, List operations) throws XMLStreamException { + new SubsystemResourceXMLElementReader(this.getSubsystemResourceXMLElement()).readElement(reader, operations); + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/transform/SubsystemExtensionTransformerRegistration.java b/controller/src/main/java/org/jboss/as/controller/transform/SubsystemExtensionTransformerRegistration.java index cf288f4d7d6..8434f8175ce 100644 --- a/controller/src/main/java/org/jboss/as/controller/transform/SubsystemExtensionTransformerRegistration.java +++ b/controller/src/main/java/org/jboss/as/controller/transform/SubsystemExtensionTransformerRegistration.java @@ -10,6 +10,7 @@ import org.jboss.as.controller.ModelVersion; import org.jboss.as.controller.SubsystemModel; +import org.jboss.as.controller.SubsystemResourceDescription; import org.jboss.as.controller.transform.description.TransformationDescription; /** @@ -21,6 +22,10 @@ public class SubsystemExtensionTransformerRegistration implements ExtensionTrans private final Iterable legacyModels; private final Function factory; + protected & SubsystemModel> SubsystemExtensionTransformerRegistration(SubsystemResourceDescription description, E currentModel, Function factory) { + this(description.getName(), currentModel, factory); + } + protected & SubsystemModel> SubsystemExtensionTransformerRegistration(String name, E currentModel, Function factory) { this.name = name; this.legacyModels = EnumSet.complementOf(EnumSet.of(currentModel)); diff --git a/controller/src/main/java/org/jboss/as/controller/xml/QNameResolver.java b/controller/src/main/java/org/jboss/as/controller/xml/QNameResolver.java new file mode 100644 index 00000000000..a84d1ccfea9 --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/xml/QNameResolver.java @@ -0,0 +1,21 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.as.controller.xml; + +import javax.xml.namespace.QName; + +/** + * Resolves a local name to a qualified name. + * @author Paul Ferraro + */ +public interface QNameResolver { + + /** + * Resolves the specified local name to a qualified name. + * @param localName an attribute/element local name + * @return a qualified name + */ + QName resolveQName(String localName); +} diff --git a/controller/src/main/java/org/jboss/as/controller/xml/XMLCardinality.java b/controller/src/main/java/org/jboss/as/controller/xml/XMLCardinality.java new file mode 100644 index 00000000000..92774a1cd71 --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/xml/XMLCardinality.java @@ -0,0 +1,92 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.jboss.as.controller.xml; + +import java.util.OptionalInt; + +/** + * Defines the cardinality of an XML particle. + */ +public interface XMLCardinality { + + /** + * Returns the minimum number of occurrences of this particle. + * @return the minimum number of occurrences of this particle. + */ + int getMinOccurs(); + + /** + * Returns the maximum number of occurrences of this particle. + * @return the maximum number of occurrences of this particle. + */ + OptionalInt getMaxOccurs(); + + /** + * Cardinality of empty content. + */ + XMLCardinality NONE = new XMLCardinality() { + private final OptionalInt max = OptionalInt.of(0); + + @Override + public int getMinOccurs() { + return 0; + } + + @Override + public OptionalInt getMaxOccurs() { + return this.max; + } + }; + + /** + * Cardinalities for single particles. + */ + enum Single implements XMLCardinality { + OPTIONAL(0), + REQUIRED(1), + ; + private static final OptionalInt MAX = OptionalInt.of(1); + private final int min; + + Single(int min) { + this.min = min; + } + + @Override + public int getMinOccurs() { + return this.min; + } + + @Override + public OptionalInt getMaxOccurs() { + return MAX; + } + } + + /** + * Common cardinalities for unbounded particles. + */ + enum Unbounded implements XMLCardinality { + OPTIONAL(0), + REQUIRED(1), + ; + private final int min; + + Unbounded(int min) { + this.min = min; + } + + @Override + public int getMinOccurs() { + return this.min; + } + + @Override + public OptionalInt getMaxOccurs() { + return OptionalInt.empty(); + } + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/xml/XMLChoice.java b/controller/src/main/java/org/jboss/as/controller/xml/XMLChoice.java new file mode 100644 index 00000000000..bbac4bd5a14 --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/xml/XMLChoice.java @@ -0,0 +1,131 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.as.controller.xml; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; + +import org.jboss.as.controller.logging.ControllerLogger; +import org.jboss.staxmapper.XMLExtendedStreamWriter; + +/** + * A readable/writable XML choice. + * @param the reader context + * @param the writer content + * @author Paul Ferraro + */ +public interface XMLChoice extends XMLContentWriter { + + /** + * Returns the set of qualified choice names. + * @return the set of qualified choice names. + */ + Set getChoices(); + + /** + * Returns a reader for the specified qualified element name. + * @return a reader for the specified qualified element name. + */ + XMLContentReader getReader(QName name); + + /** + * Returns the cardinality of this choice. + * @return a cardinality + */ + XMLCardinality getCardinality(); + + /** + * Returns an empty choice, i.e. with zero choices/elements. + * @param the reader context + * @param the writer content + */ + static XMLChoice empty() { + return new DefaultXMLChoice<>(Map.of(), XMLContent.empty(), XMLCardinality.NONE); + } + + /** + * Composes a xs:choice of the specified choices. + * @param the reader context + * @param the writer content + * @param choices a collection of zero or more choices + * @return a choice that reads/writes one of the specified choices. + */ + static XMLChoice of(Collection> choices, XMLCardinality cardinality) { + if (choices.isEmpty()) return empty(); + + Map> readers = new HashMap<>(); + for (XMLChoice choice : choices) { + for (QName name : choice.getChoices()) { + XMLContentReader existing = readers.put(name, choice.getReader(name)); + if (existing != null) { + throw ControllerLogger.ROOT_LOGGER.duplicateElements(name); + } + } + } + return new DefaultXMLChoice<>(readers, XMLContentWriter.of(choices), cardinality); + } + + class DefaultXMLChoice extends AbstractXMLChoice { + private final Map> readers; + private final XMLContentWriter writer; + private final XMLCardinality cardinality; + + protected DefaultXMLChoice(Map> readers, XMLContentWriter writer, XMLCardinality cardinality) { + this.readers = readers; + this.writer = writer; + this.cardinality = cardinality; + } + + @Override + public Set getChoices() { + return this.readers.keySet(); + } + + @Override + public XMLContentReader getReader(QName name) { + return this.readers.get(name); + } + + @Override + public void writeContent(XMLExtendedStreamWriter writer, WC content) throws XMLStreamException { + this.writer.writeContent(writer, content); + } + + @Override + public boolean isEmpty(WC content) { + return this.writer.isEmpty(content); + } + + @Override + public XMLCardinality getCardinality() { + return this.cardinality; + } + } + + abstract class AbstractXMLChoice implements XMLChoice { + + @Override + public int hashCode() { + return this.getChoices().hashCode(); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof XMLChoice)) return false; + XMLChoice choice = (XMLChoice) object; + return this.getChoices().equals(choice.getChoices()); + } + + @Override + public String toString() { + return this.getChoices().toString(); + } + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/xml/XMLContent.java b/controller/src/main/java/org/jboss/as/controller/xml/XMLContent.java new file mode 100644 index 00000000000..528e5951f65 --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/xml/XMLContent.java @@ -0,0 +1,185 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.as.controller.xml; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.OptionalInt; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; + +import org.jboss.as.controller.logging.ControllerLogger; +import org.jboss.as.controller.parsing.ParseUtils; +import org.jboss.staxmapper.XMLElementReader; +import org.jboss.staxmapper.XMLExtendedStreamReader; +import org.jboss.staxmapper.XMLExtendedStreamWriter; + +/** + * A readable/writable model group of XML content. + * @param the reader context + * @param the writer content + * @author Paul Ferraro + */ +public interface XMLContent extends XMLContentReader, XMLContentWriter { + + /** + * Returns an empty content + * @param the reader context + * @param the writer context + * @return an empty content + */ + static XMLContent empty() { + return of(XMLContentReader.empty(), XMLContentWriter.empty()); + } + + /** + * Returns an empty content + * @param the reader context + * @param the writer context + * @return an empty content + */ + static XMLContent of(XMLContentReader contentReader, XMLContentWriter contentWriter) { + return new XMLContent<>() { + @Override + public void readElement(XMLExtendedStreamReader reader, RC context) throws XMLStreamException { + contentReader.readElement(reader, context); + } + + @Override + public XMLCardinality getCardinality() { + return contentReader.getCardinality(); + } + + @Override + public void writeContent(XMLExtendedStreamWriter writer, WC content) throws XMLStreamException { + contentWriter.writeContent(writer, content); + } + + @Override + public boolean isEmpty(WC content) { + return contentWriter.isEmpty(content); + } + }; + } + + /** + * Composes xs:all content that reads/writes the specified choices in any order. + * @param choices a list of zero or more choices + * @param the reader context + * @param the writer context + * @return content that reads/writes the specified choices in any order. + */ + static XMLContent all(Collection> all) { + if (all.isEmpty()) return empty(); + + Map> choices = new HashMap<>(); + for (XMLChoice choice : all) { + for (QName name : choice.getChoices()) { + XMLChoice existing = choices.put(name, choice); + if (existing != null) { + throw ControllerLogger.ROOT_LOGGER.duplicateElements(name); + } + } + } + XMLContentReader reader = new XMLContentReader<>() { + @Override + public void readElement(XMLExtendedStreamReader reader, RC context) throws XMLStreamException { + Map, AtomicInteger> occurrences = new HashMap<>(); + for (XMLChoice choice : all) { + occurrences.put(choice, new AtomicInteger(0)); + } + while (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT) { + QName name = reader.getName(); + XMLChoice choice = choices.get(name); + if (choice == null) { + // Try matching w/out namespace (for PersistentResourceXMLDescription compatibility) + name = new QName(reader.getLocalName()); + choice = choices.get(name); + if (choice == null) { + throw ParseUtils.unexpectedElement(reader, choices.keySet().stream().map(QName::getLocalPart).collect(Collectors.toSet())); + } + } + AtomicInteger occurrence = occurrences.get(choice); + // Validate maxOccurs + OptionalInt maxOccurs = choice.getCardinality().getMaxOccurs(); + if (maxOccurs.isPresent() && (occurrence.getPlain() >= maxOccurs.getAsInt())) { + throw ParseUtils.maxOccursExceeded(reader, choice.getChoices()); + } + occurrence.setPlain(occurrence.getPlain() + 1); + choice.getReader(name).readElement(reader, context); + } + // Validate minOccurs + for (Map.Entry, AtomicInteger> entry : occurrences.entrySet()) { + XMLChoice choice = entry.getKey(); + AtomicInteger occurrence = entry.getValue(); + if (occurrence.getPlain() < choice.getCardinality().getMinOccurs()) { + throw ParseUtils.minOccursNotReached(reader, choice.getChoices()); + } + } + } + + @Override + public XMLCardinality getCardinality() { + return XMLCardinality.Single.OPTIONAL; + } + }; + return of(reader, XMLContentWriter.of(all)); + } + + /** + * Composes xs:sequence content that reads/writes the specified sequence of choices. + * @param sequence a list of zero or more choices + * @param the reader context + * @param the writer context + * @return content that reads/writes the specified choices in sequential order. + */ + static XMLContent sequence(List> sequence) { + if (sequence.isEmpty()) return empty(); + + XMLContentReader reader = new XMLContentReader<>() { + @Override + public void readElement(XMLExtendedStreamReader reader, RC context) throws XMLStreamException { + Iterator> choices = sequence.iterator(); + XMLChoice currentChoice = XMLChoice.empty(); + int occurrences = 0; + while (reader.hasNext() && reader.nextTag() != XMLStreamConstants.END_ELEMENT) { + QName name = reader.getName(); + while (!currentChoice.getChoices().contains(reader.getName()) && choices.hasNext()) { + // Validate minOccurs + if (occurrences < currentChoice.getCardinality().getMinOccurs()) { + throw ParseUtils.minOccursNotReached(reader, currentChoice.getChoices()); + } + currentChoice = choices.next(); + occurrences = 0; + } + XMLElementReader choiceReader = currentChoice.getReader(name); + if (choiceReader == null) { + throw ParseUtils.unexpectedElement(reader, currentChoice.getChoices().stream().map(QName::getLocalPart).collect(Collectors.toSet())); + } + occurrences += 1; + OptionalInt maxOccurs = currentChoice.getCardinality().getMaxOccurs(); + // Validate maxOccurs + if (maxOccurs.isPresent() && (occurrences > maxOccurs.getAsInt())) { + throw ParseUtils.maxOccursExceeded(reader, currentChoice.getChoices()); + } + choiceReader.readElement(reader, context); + } + } + + @Override + public XMLCardinality getCardinality() { + return XMLCardinality.Single.OPTIONAL; + } + }; + return of(reader, XMLContentWriter.of(sequence)); + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/xml/XMLContentReader.java b/controller/src/main/java/org/jboss/as/controller/xml/XMLContentReader.java new file mode 100644 index 00000000000..a494dd1b0f5 --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/xml/XMLContentReader.java @@ -0,0 +1,38 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.jboss.as.controller.xml; + +import javax.xml.stream.XMLStreamException; + +import org.jboss.as.controller.parsing.ParseUtils; +import org.jboss.staxmapper.XMLElementReader; +import org.jboss.staxmapper.XMLExtendedStreamReader; + +/** + * A readable model group of XML content. + */ +public interface XMLContentReader extends XMLElementReader { + + /** + * Returns the cardinality of this model group. + * @return the cardinality of this model group. + */ + XMLCardinality getCardinality(); + + static XMLContentReader empty() { + return new XMLContentReader<>() { + @Override + public void readElement(XMLExtendedStreamReader reader, C value) throws XMLStreamException { + ParseUtils.requireNoContent(reader); + } + + @Override + public XMLCardinality getCardinality() { + return XMLCardinality.NONE; + } + }; + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/xml/XMLContentWriter.java b/controller/src/main/java/org/jboss/as/controller/xml/XMLContentWriter.java new file mode 100644 index 00000000000..f1a98697372 --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/xml/XMLContentWriter.java @@ -0,0 +1,71 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.as.controller.xml; + +import java.util.Collection; +import java.util.List; + +import javax.xml.stream.XMLStreamException; + +import org.jboss.as.controller.Feature; +import org.jboss.as.version.Stability; +import org.jboss.staxmapper.XMLElementWriter; +import org.jboss.staxmapper.XMLExtendedStreamWriter; + +/** + * A writable model group of XML content. + * @author Paul Ferraro + */ +public interface XMLContentWriter extends XMLElementWriter, Feature { + + /** + * Indicates whether the specified content is empty. + */ + boolean isEmpty(C content); + + static XMLContentWriter empty() { + return of(List.of()); + } + + static XMLContentWriter of(Collection> writers) { + return new DefaultXMLContentWriter<>(writers); + } + + class DefaultXMLContentWriter implements XMLContentWriter { + private final Collection> contentWriters; + + DefaultXMLContentWriter(Collection> contentWriters) { + this.contentWriters = contentWriters; + } + + @Override + public void writeContent(XMLExtendedStreamWriter writer, C content) throws XMLStreamException { + for (XMLContentWriter contentWriter : this.contentWriters) { + if (!contentWriter.isEmpty(content)) { + contentWriter.writeContent(writer, content); + } + } + } + + @Override + public boolean isEmpty(C content) { + for (XMLContentWriter contentWriter : this.contentWriters) { + if (!contentWriter.isEmpty(content)) return false; + } + return true; + } + + @Override + public Stability getStability() { + Stability stability = null; + for (XMLContentWriter contentWriter : this.contentWriters) { + if (stability == null || stability.enables(contentWriter.getStability())) { + stability = contentWriter.getStability(); + } + } + return (stability != null) ? stability : Stability.DEFAULT; + } + } +} diff --git a/controller/src/main/java/org/jboss/as/controller/xml/XMLElement.java b/controller/src/main/java/org/jboss/as/controller/xml/XMLElement.java new file mode 100644 index 00000000000..29251504434 --- /dev/null +++ b/controller/src/main/java/org/jboss/as/controller/xml/XMLElement.java @@ -0,0 +1,198 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.as.controller.xml; + +import java.util.List; +import java.util.Set; + +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; + +import org.jboss.as.controller.logging.ControllerLogger; +import org.jboss.as.controller.parsing.ParseUtils; +import org.jboss.staxmapper.XMLExtendedStreamReader; +import org.jboss.staxmapper.XMLExtendedStreamWriter; + +/** + * A readable/writable XML element, implemented as a singleton choice. + * @param the reader context + * @param the writer content + * @author Paul Ferraro + */ +public interface XMLElement extends XMLChoice, XMLContent { + /** + * The qualified name of this element. + * @return a qualified name + */ + QName getName(); + + @Override + default Set getChoices() { + return Set.of(this.getName()); + } + + @Override + default XMLContentReader getReader(QName name) { + return this.getName().equals(name) ? this : null; + } + + /** + * Creates an element whose content should be ignored, if present. + * @param the reader context + * @param the writer content + * @param name the qualified name of ignored element + * @return an element whose content should be ignored. + */ + static XMLElement ignore(QName name) { + XMLContentReader reader = new XMLContentReader<>() { + @Override + public void readElement(XMLExtendedStreamReader reader, RC context) throws XMLStreamException { + if (!reader.isStartElement() || !reader.getName().equals(name)) { + throw ParseUtils.unexpectedElement(reader, Set.of(name.getLocalPart())); + } + ControllerLogger.ROOT_LOGGER.elementIgnored(name); + this.skipElement(reader); + if (!reader.isEndElement() || !reader.getName().equals(name)) { + throw ParseUtils.unexpectedElement(reader); + } + } + + private void skipElement(XMLExtendedStreamReader reader) throws XMLStreamException { + while (reader.hasNext() && (reader.nextTag() != XMLStreamConstants.END_ELEMENT)) { + this.skipElement(reader); + } + } + + @Override + public XMLCardinality getCardinality() { + return XMLCardinality.Unbounded.OPTIONAL; + } + }; + return new DefaultXMLElement<>(name, reader, XMLContentWriter.empty()); + } + + /** + * Applies an element wrapper to the specified content. + * @param the reader context + * @param the writer content + * @param name the qualified name of the wrapper element + * @param choice the XML content to wrap + * @return an element that reads/writes the wrapped content. + */ + static XMLElement wrap(QName name, XMLChoice choice) { + return wrap(name, XMLContent.all(List.of(choice)), choice.getCardinality()); + } + + /** + * Applies an element wrapper to the specified content. + * @param the reader context + * @param the writer content + * @param name the qualified name of the wrapper element + * @param choice the XML content to wrap + * @return an element that reads/writes the wrapped content. + */ + static XMLElement wrap(QName name, XMLContent content, XMLCardinality cardinality) { + XMLContentReader reader = new XMLContentReader<>() { + @Override + public void readElement(XMLExtendedStreamReader reader, RC context) throws XMLStreamException { + // Validate entry criteria + // Try matching w/out namespace (for PersistentResourceXMLDescription compatibility) + if (!reader.isStartElement() || (!reader.getName().equals(name) && !reader.getLocalName().equals(name.getLocalPart()))) { + throw ParseUtils.unexpectedElement(reader, Set.of(name.getLocalPart())); + } + ParseUtils.requireNoAttributes(reader); + content.readElement(reader, context); + // Validate exit criteria + // Try matching w/out namespace (for PersistentResourceXMLDescription compatibility) + if (!reader.isEndElement() || (!reader.getName().equals(name) && !reader.getLocalName().equals(name.getLocalPart()))) { + throw ParseUtils.unexpectedElement(reader); + } + } + + @Override + public XMLCardinality getCardinality() { + return cardinality; + } + }; + XMLContentWriter writer = new XMLContentWriter<>() { + @Override + public void writeContent(XMLExtendedStreamWriter writer, WC value) throws XMLStreamException { + if (name.getNamespaceURI() != XMLConstants.NULL_NS_URI) { + writer.writeStartElement(name.getNamespaceURI(), name.getLocalPart()); + } else { + // PersistentResourceXMLDescription compatibility + writer.writeStartElement(name.getLocalPart()); + } + content.writeContent(writer, value); + writer.writeEndElement(); + } + + @Override + public boolean isEmpty(WC value) { + return content.isEmpty(value); + } + }; + return new DefaultXMLElement<>(name, reader, writer); + } + + class DefaultXMLElement extends AbstractXMLElement { + private final QName name; + private final XMLContentReader reader; + private final XMLContentWriter writer; + + protected DefaultXMLElement(QName name, XMLContentReader reader, XMLContentWriter writer) { + this.name = name; + this.reader = reader; + this.writer = writer; + } + + @Override + public QName getName() { + return this.name; + } + + @Override + public void readElement(XMLExtendedStreamReader reader, RC value) throws XMLStreamException { + this.reader.readElement(reader, value); + } + + @Override + public XMLCardinality getCardinality() { + return this.reader.getCardinality(); + } + + @Override + public void writeContent(XMLExtendedStreamWriter writer, WC content) throws XMLStreamException { + this.writer.writeContent(writer, content); + } + + @Override + public boolean isEmpty(WC content) { + return this.writer.isEmpty(content); + } + } + + abstract class AbstractXMLElement implements XMLElement { + + @Override + public int hashCode() { + return this.getName().hashCode(); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof XMLElement)) return false; + XMLElement element = (XMLElement) object; + return this.getName().equals(element.getName()); + } + + @Override + public String toString() { + return this.getName().toString(); + } + } +} diff --git a/controller/src/test/java/org/jboss/as/controller/persistence/PersistentResourceXMLParserTestCase.java b/controller/src/test/java/org/jboss/as/controller/persistence/PersistentResourceXMLParserTestCase.java index d80b9dc964b..0449938f7d2 100644 --- a/controller/src/test/java/org/jboss/as/controller/persistence/PersistentResourceXMLParserTestCase.java +++ b/controller/src/test/java/org/jboss/as/controller/persistence/PersistentResourceXMLParserTestCase.java @@ -499,19 +499,23 @@ private static class MyParser extends PersistentResourceXMLParser { static final AttributeDefinition clusterAttr1 = create("cluster-attr1", ModelType.STRING) + .setRequired(false) .setAttributeGroup("cluster") .setXmlName("attr1") .build(); static final AttributeDefinition clusterAttr2 = create("cluster-attr2", ModelType.STRING) + .setRequired(false) .setAttributeGroup("cluster") .setXmlName("attr2") .build(); static final AttributeDefinition securityAttr1 = create("security-my-attr1", ModelType.STRING) + .setRequired(false) .setAttributeGroup("security") .setXmlName("my-attr1") .build(); static final AttributeDefinition securityAttr2 = create("security-my-attr2", ModelType.STRING) + .setRequired(false) .setAttributeGroup("security") .setXmlName("my-attr2") .build(); @@ -1261,9 +1265,11 @@ static class Constants { .build(); public static final AttributeDefinition UNWRAPPED_LISTENER = ObjectListAttributeDefinition.Builder.of("unwrapped-listener", STATE_LISTENER) - .setRequired(true) - .setRuntimeServiceNotRequired() - .build(); + .setAttributeParser(AttributeParsers.UNWRAPPED_OBJECT_LIST_PARSER) + .setAttributeMarshaller(AttributeMarshallers.OBJECT_LIST_UNWRAPPED) + .setRequired(true) + .setRuntimeServiceNotRequired() + .build(); static class AttributeMappingObjectDefinition { static final SimpleAttributeDefinition FROM = new SimpleAttributeDefinitionBuilder("from", ModelType.STRING, false) diff --git a/discovery/src/main/java/org/wildfly/extension/discovery/AggregateDiscoveryProviderResourceDescription.java b/discovery/src/main/java/org/wildfly/extension/discovery/AggregateDiscoveryProviderResourceDescription.java new file mode 100644 index 00000000000..b0e87f18756 --- /dev/null +++ b/discovery/src/main/java/org/wildfly/extension/discovery/AggregateDiscoveryProviderResourceDescription.java @@ -0,0 +1,53 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.extension.discovery; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.PathElement; +import org.jboss.dmr.ModelNode; +import org.wildfly.discovery.impl.AggregateDiscoveryProvider; +import org.wildfly.discovery.spi.DiscoveryProvider; +import org.wildfly.subsystem.resource.capability.CapabilityReference; +import org.wildfly.subsystem.resource.capability.CapabilityReferenceListAttributeDefinition; +import org.wildfly.subsystem.service.ServiceDependency; + +/** + * Describes an aggregate discovery provider resource. + * @author Paul Ferraro + */ +public enum AggregateDiscoveryProviderResourceDescription implements DiscoveryProviderResourceDescription { + INSTANCE; + + static final CapabilityReferenceListAttributeDefinition PROVIDER_NAMES = new CapabilityReferenceListAttributeDefinition.Builder<>("providers", CapabilityReference.builder(DiscoveryProviderResourceRegistrar.DISCOVERY_PROVIDER_CAPABILITY, DiscoveryProviderResourceRegistrar.DISCOVERY_PROVIDER_DESCRIPTOR).build()).build(); + + private final PathElement path = PathElement.pathElement("aggregate-provider"); + + @Override + public PathElement getPathElement() { + return this.path; + } + + @Override + public Stream getAttributes() { + return Stream.of(PROVIDER_NAMES); + } + + @Override + public ServiceDependency resolve(OperationContext context, ModelNode model) throws OperationFailedException { + return AggregateDiscoveryProviderResourceDescription.PROVIDER_NAMES.resolve(context, model).map(new Function<>() { + @Override + public DiscoveryProvider apply(List providers) { + return new AggregateDiscoveryProvider(providers.toArray(DiscoveryProvider[]::new)); + } + }); + } +} diff --git a/discovery/src/main/java/org/wildfly/extension/discovery/AggregateDiscoveryProviderRegistrar.java b/discovery/src/main/java/org/wildfly/extension/discovery/AggregateDiscoveryProviderResourceRegistrar.java similarity index 50% rename from discovery/src/main/java/org/wildfly/extension/discovery/AggregateDiscoveryProviderRegistrar.java rename to discovery/src/main/java/org/wildfly/extension/discovery/AggregateDiscoveryProviderResourceRegistrar.java index cb4b953d96b..d4e223bb333 100644 --- a/discovery/src/main/java/org/wildfly/extension/discovery/AggregateDiscoveryProviderRegistrar.java +++ b/discovery/src/main/java/org/wildfly/extension/discovery/AggregateDiscoveryProviderResourceRegistrar.java @@ -4,20 +4,14 @@ */ package org.wildfly.extension.discovery; -import java.util.Collection; import java.util.List; import java.util.function.Function; -import org.jboss.as.controller.AttributeDefinition; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationFailedException; -import org.jboss.as.controller.PathElement; import org.jboss.dmr.ModelNode; import org.wildfly.discovery.impl.AggregateDiscoveryProvider; import org.wildfly.discovery.spi.DiscoveryProvider; -import org.wildfly.subsystem.resource.ResourceDescriptor; -import org.wildfly.subsystem.resource.capability.CapabilityReference; -import org.wildfly.subsystem.resource.capability.CapabilityReferenceListAttributeDefinition; import org.wildfly.subsystem.service.ResourceServiceInstaller; import org.wildfly.subsystem.service.ServiceDependency; import org.wildfly.subsystem.service.capability.CapabilityServiceInstaller; @@ -26,21 +20,15 @@ * Registers the aggregate discovery provider resource definition. * @author Paul Ferraro */ -public class AggregateDiscoveryProviderRegistrar extends DiscoveryProviderRegistrar { +public class AggregateDiscoveryProviderResourceRegistrar extends DiscoveryProviderResourceRegistrar { - static final PathElement PATH = PathElement.pathElement("aggregate-provider"); - - private static final CapabilityReferenceListAttributeDefinition PROVIDER_NAMES = new CapabilityReferenceListAttributeDefinition.Builder<>("providers", CapabilityReference.builder(DISCOVERY_PROVIDER_CAPABILITY, DISCOVERY_PROVIDER_DESCRIPTOR).build()).build(); - - static final Collection ATTRIBUTES = List.of(PROVIDER_NAMES); - - AggregateDiscoveryProviderRegistrar() { - super(PATH, ResourceDescriptor.builder(DiscoverySubsystemRegistrar.RESOLVER.createChildResolver(PATH)).addAttributes(ATTRIBUTES)); + AggregateDiscoveryProviderResourceRegistrar() { + super(AggregateDiscoveryProviderResourceDescription.INSTANCE); } @Override public ResourceServiceInstaller configure(OperationContext context, ModelNode model) throws OperationFailedException { - ServiceDependency provider = PROVIDER_NAMES.resolve(context, model).map(new Function<>() { + ServiceDependency provider = AggregateDiscoveryProviderResourceDescription.PROVIDER_NAMES.resolve(context, model).map(new Function<>() { @Override public DiscoveryProvider apply(List providers) { return new AggregateDiscoveryProvider(providers.toArray(DiscoveryProvider[]::new)); diff --git a/discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryExtension.java b/discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryExtension.java index 5763fc74bed..209634504f6 100644 --- a/discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryExtension.java +++ b/discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryExtension.java @@ -17,6 +17,6 @@ public class DiscoveryExtension extends SubsystemExtension { public DiscoveryExtension() { - super(SubsystemConfiguration.of(DiscoverySubsystemRegistrar.NAME, DiscoverySubsystemModel.CURRENT, DiscoverySubsystemRegistrar::new), SubsystemPersistence.of(DiscoverySubsystemSchema.CURRENT)); + super(SubsystemConfiguration.of(DiscoverySubsystemResourceDescription.INSTANCE, DiscoverySubsystemModel.CURRENT, DiscoverySubsystemResourceRegistrar::new), SubsystemPersistence.of(DiscoverySubsystemSchema.CURRENT)); } } diff --git a/discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryExtensionTransformerRegistration.java b/discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryExtensionTransformerRegistration.java index 35cd52d521c..0f086e34916 100644 --- a/discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryExtensionTransformerRegistration.java +++ b/discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryExtensionTransformerRegistration.java @@ -14,6 +14,6 @@ public class DiscoveryExtensionTransformerRegistration extends SubsystemExtensionTransformerRegistration { public DiscoveryExtensionTransformerRegistration() { - super(DiscoverySubsystemRegistrar.NAME, DiscoverySubsystemModel.CURRENT, DiscoverySubsystemTransformationDescriptionFactory.INSTANCE); + super(DiscoverySubsystemResourceDescription.INSTANCE, DiscoverySubsystemModel.CURRENT, DiscoverySubsystemTransformationDescriptionFactory.INSTANCE); } } diff --git a/discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryProviderResourceDescription.java b/discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryProviderResourceDescription.java new file mode 100644 index 00000000000..d4ebddd9fe1 --- /dev/null +++ b/discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryProviderResourceDescription.java @@ -0,0 +1,17 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.extension.discovery; + +import org.jboss.as.controller.ResourceDescription; +import org.wildfly.discovery.spi.DiscoveryProvider; +import org.wildfly.subsystem.resource.ResourceModelResolver; +import org.wildfly.subsystem.service.ServiceDependency; + +/** + * Describes a discovery provider resource + */ +public interface DiscoveryProviderResourceDescription extends ResourceDescription, ResourceModelResolver> { +} diff --git a/discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryProviderRegistrar.java b/discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryProviderResourceRegistrar.java similarity index 57% rename from discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryProviderRegistrar.java rename to discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryProviderResourceRegistrar.java index 5507d98384a..257ee31d209 100644 --- a/discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryProviderRegistrar.java +++ b/discovery/src/main/java/org/wildfly/extension/discovery/DiscoveryProviderResourceRegistrar.java @@ -4,11 +4,12 @@ */ package org.wildfly.extension.discovery; -import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.ResourceDefinition; -import org.jboss.as.controller.ResourceRegistration; import org.jboss.as.controller.capability.RuntimeCapability; import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.dmr.ModelNode; import org.wildfly.discovery.spi.DiscoveryProvider; import org.wildfly.service.descriptor.UnaryServiceDescriptor; import org.wildfly.subsystem.resource.ChildResourceDefinitionRegistrar; @@ -17,34 +18,42 @@ import org.wildfly.subsystem.resource.ResourceDescriptor; import org.wildfly.subsystem.resource.operation.ResourceOperationRuntimeHandler; import org.wildfly.subsystem.service.ResourceServiceConfigurator; +import org.wildfly.subsystem.service.ResourceServiceInstaller; +import org.wildfly.subsystem.service.capability.CapabilityServiceInstaller; /** * Abstract registrar for a discovery provider resource definition. * @author Paul Ferraro */ -public abstract class DiscoveryProviderRegistrar implements ChildResourceDefinitionRegistrar, ResourceServiceConfigurator { +public class DiscoveryProviderResourceRegistrar implements ChildResourceDefinitionRegistrar, ResourceServiceConfigurator { // TODO Move this to an SPI module, when this capability acquires any consumers static final UnaryServiceDescriptor DISCOVERY_PROVIDER_DESCRIPTOR = UnaryServiceDescriptor.of("org.wildfly.discovery.provider", DiscoveryProvider.class); static final RuntimeCapability DISCOVERY_PROVIDER_CAPABILITY = RuntimeCapability.Builder.of(DISCOVERY_PROVIDER_DESCRIPTOR).setAllowMultipleRegistrations(true).build(); - private final ResourceRegistration registration; - private final ResourceDescriptor descriptor; + private final DiscoveryProviderResourceDescription description; - DiscoveryProviderRegistrar(PathElement path, ResourceDescriptor.Builder builder) { - this.registration = ResourceRegistration.of(path); - this.descriptor = builder.addCapability(DISCOVERY_PROVIDER_CAPABILITY) - .withRuntimeHandler(ResourceOperationRuntimeHandler.configureService(this)) - .build(); + DiscoveryProviderResourceRegistrar(DiscoveryProviderResourceDescription description) { + this.description = description; } @Override public ManagementResourceRegistration register(ManagementResourceRegistration parent, ManagementResourceRegistrationContext context) { - ResourceDefinition definition = ResourceDefinition.builder(this.registration, this.descriptor.getResourceDescriptionResolver()).build(); + ResourceDescriptor descriptor = ResourceDescriptor.builder(DiscoverySubsystemResourceRegistrar.RESOLVER.createChildResolver(this.description.getPathElement())) + .addAttributes(this.description.getAttributes().toList()) + .addCapability(DISCOVERY_PROVIDER_CAPABILITY) + .withRuntimeHandler(ResourceOperationRuntimeHandler.configureService(this)) + .build(); + ResourceDefinition definition = ResourceDefinition.builder(this.description, descriptor.getResourceDescriptionResolver()).build(); ManagementResourceRegistration registration = parent.registerSubModel(definition); - ManagementResourceRegistrar.of(this.descriptor).register(registration); + ManagementResourceRegistrar.of(descriptor).register(registration); return registration; } + + @Override + public ResourceServiceInstaller configure(OperationContext context, ModelNode model) throws OperationFailedException { + return CapabilityServiceInstaller.builder(DiscoveryProviderResourceRegistrar.DISCOVERY_PROVIDER_CAPABILITY, this.description.resolve(context, model)).build(); + } } diff --git a/discovery/src/main/java/org/wildfly/extension/discovery/DiscoverySubsystemResourceDescription.java b/discovery/src/main/java/org/wildfly/extension/discovery/DiscoverySubsystemResourceDescription.java new file mode 100644 index 00000000000..9a2967b939a --- /dev/null +++ b/discovery/src/main/java/org/wildfly/extension/discovery/DiscoverySubsystemResourceDescription.java @@ -0,0 +1,20 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.extension.discovery; + +import org.jboss.as.controller.SubsystemResourceDescription; + +/** + * Describes the discovery subsystem resource. + */ +public enum DiscoverySubsystemResourceDescription implements SubsystemResourceDescription { + INSTANCE; + + @Override + public String getName() { + return "discovery"; + } +} diff --git a/discovery/src/main/java/org/wildfly/extension/discovery/DiscoverySubsystemRegistrar.java b/discovery/src/main/java/org/wildfly/extension/discovery/DiscoverySubsystemResourceRegistrar.java similarity index 66% rename from discovery/src/main/java/org/wildfly/extension/discovery/DiscoverySubsystemRegistrar.java rename to discovery/src/main/java/org/wildfly/extension/discovery/DiscoverySubsystemResourceRegistrar.java index bd36f3ebab1..bd106ec480c 100644 --- a/discovery/src/main/java/org/wildfly/extension/discovery/DiscoverySubsystemRegistrar.java +++ b/discovery/src/main/java/org/wildfly/extension/discovery/DiscoverySubsystemResourceRegistrar.java @@ -5,9 +5,7 @@ package org.wildfly.extension.discovery; -import org.jboss.as.controller.PathElement; import org.jboss.as.controller.ResourceDefinition; -import org.jboss.as.controller.ResourceRegistration; import org.jboss.as.controller.SubsystemRegistration; import org.jboss.as.controller.descriptions.ParentResourceDescriptionResolver; import org.jboss.as.controller.descriptions.SubsystemResourceDescriptionResolver; @@ -21,23 +19,21 @@ * Registrar for the discovery subsystem. * @author Paul Ferraro */ -class DiscoverySubsystemRegistrar implements SubsystemResourceDefinitionRegistrar { +class DiscoverySubsystemResourceRegistrar implements SubsystemResourceDefinitionRegistrar { - static final String NAME = "discovery"; - static final PathElement PATH = SubsystemResourceDefinitionRegistrar.pathElement(NAME); - static final ParentResourceDescriptionResolver RESOLVER = new SubsystemResourceDescriptionResolver(NAME, DiscoverySubsystemRegistrar.class); + static final ParentResourceDescriptionResolver RESOLVER = new SubsystemResourceDescriptionResolver(DiscoverySubsystemResourceDescription.INSTANCE.getName(), DiscoverySubsystemResourceRegistrar.class); @Override public ManagementResourceRegistration register(SubsystemRegistration parent, ManagementResourceRegistrationContext context) { parent.setHostCapable(); - ManagementResourceRegistration registration = parent.registerSubsystemModel(ResourceDefinition.builder(ResourceRegistration.of(PATH), RESOLVER).build()); + ManagementResourceRegistration registration = parent.registerSubsystemModel(ResourceDefinition.builder(DiscoverySubsystemResourceDescription.INSTANCE, RESOLVER).build()); ResourceDescriptor descriptor = ResourceDescriptor.builder(RESOLVER).build(); ManagementResourceRegistrar.of(descriptor).register(registration); - new AggregateDiscoveryProviderRegistrar().register(registration, context); - new StaticDiscoveryProviderRegistrar().register(registration, context); + new AggregateDiscoveryProviderResourceRegistrar().register(registration, context); + new StaticDiscoveryProviderResourceRegistrar().register(registration, context); return registration; } diff --git a/discovery/src/main/java/org/wildfly/extension/discovery/DiscoverySubsystemSchema.java b/discovery/src/main/java/org/wildfly/extension/discovery/DiscoverySubsystemSchema.java index e6f939ddeaf..ff8905db46c 100644 --- a/discovery/src/main/java/org/wildfly/extension/discovery/DiscoverySubsystemSchema.java +++ b/discovery/src/main/java/org/wildfly/extension/discovery/DiscoverySubsystemSchema.java @@ -5,17 +5,21 @@ package org.wildfly.extension.discovery; -import org.jboss.as.controller.PersistentResourceXMLDescription; -import org.jboss.as.controller.PersistentSubsystemSchema; +import java.util.List; + import org.jboss.as.controller.SubsystemSchema; +import org.jboss.as.controller.persistence.xml.ResourceXMLElement; +import org.jboss.as.controller.persistence.xml.SubsystemResourceXMLSchema; import org.jboss.as.controller.xml.VersionedNamespace; +import org.jboss.as.controller.xml.XMLCardinality; +import org.jboss.as.controller.xml.XMLChoice; import org.jboss.staxmapper.IntVersion; /** * Enumeration of discovery subsystem schema versions. * @author Paul Ferraro */ -enum DiscoverySubsystemSchema implements PersistentSubsystemSchema { +enum DiscoverySubsystemSchema implements SubsystemResourceXMLSchema { VERSION_1_0(1, 0), ; static final DiscoverySubsystemSchema CURRENT = VERSION_1_0; @@ -23,7 +27,7 @@ enum DiscoverySubsystemSchema implements PersistentSubsystemSchema namespace; DiscoverySubsystemSchema(int major, int minor) { - this.namespace = SubsystemSchema.createLegacySubsystemURN(DiscoverySubsystemRegistrar.NAME, new IntVersion(major, minor)); + this.namespace = SubsystemSchema.createLegacySubsystemURN(DiscoverySubsystemResourceDescription.INSTANCE.getName(), new IntVersion(major, minor)); } @Override @@ -32,11 +36,10 @@ public VersionedNamespace getNamespace() { } @Override - public PersistentResourceXMLDescription getXMLDescription() { - PersistentResourceXMLDescription.Factory factory = PersistentResourceXMLDescription.factory(this); - return factory.builder(DiscoverySubsystemRegistrar.PATH) - .addChild(factory.builder(StaticDiscoveryProviderRegistrar.PATH).addAttributes(StaticDiscoveryProviderRegistrar.ATTRIBUTES.stream()).build()) - .addChild(factory.builder(AggregateDiscoveryProviderRegistrar.PATH).addAttributes(AggregateDiscoveryProviderRegistrar.ATTRIBUTES.stream()).build()) + public ResourceXMLElement getSubsystemResourceXMLElement() { + ResourceXMLElement.Builder.Factory factory = ResourceXMLElement.Builder.Factory.newInstance(this); + return factory.createBuilder(DiscoverySubsystemResourceDescription.INSTANCE) + .addChild(XMLChoice.of(List.of(factory.createBuilder(StaticDiscoveryProviderResourceDescription.INSTANCE).build(), factory.createBuilder(AggregateDiscoveryProviderResourceDescription.INSTANCE).build()), XMLCardinality.Unbounded.OPTIONAL)) .build(); } } diff --git a/discovery/src/main/java/org/wildfly/extension/discovery/StaticDiscoveryProviderResourceDescription.java b/discovery/src/main/java/org/wildfly/extension/discovery/StaticDiscoveryProviderResourceDescription.java new file mode 100644 index 00000000000..975273984f5 --- /dev/null +++ b/discovery/src/main/java/org/wildfly/extension/discovery/StaticDiscoveryProviderResourceDescription.java @@ -0,0 +1,115 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.extension.discovery; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.AttributeMarshaller; +import org.jboss.as.controller.AttributeParser; +import org.jboss.as.controller.ObjectListAttributeDefinition; +import org.jboss.as.controller.ObjectTypeAttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.descriptions.ModelDescriptionConstants; +import org.jboss.as.controller.registry.AttributeAccess.Flag; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; +import org.wildfly.discovery.AttributeValue; +import org.wildfly.discovery.ServiceURL; +import org.wildfly.discovery.impl.StaticDiscoveryProvider; +import org.wildfly.discovery.spi.DiscoveryProvider; +import org.wildfly.subsystem.service.ServiceDependency; + +/** + * Describes an aggregate discovery provider resource. + * @author Paul Ferraro + */ +public enum StaticDiscoveryProviderResourceDescription implements DiscoveryProviderResourceDescription { + INSTANCE; + + private static final SimpleAttributeDefinition ABSTRACT_TYPE = new SimpleAttributeDefinitionBuilder("abstract-type", ModelType.STRING, true).setAllowExpression(true).build(); + private static final SimpleAttributeDefinition ABSTRACT_TYPE_AUTHORITY = new SimpleAttributeDefinitionBuilder("abstract-type-authority", ModelType.STRING, true).setAllowExpression(true).build(); + private static final SimpleAttributeDefinition URI = new SimpleAttributeDefinitionBuilder("uri", ModelType.STRING, false).setValidator(new ServiceURIValidator()).setAllowExpression(true).build(); + private static final SimpleAttributeDefinition URI_SCHEME_AUTHORITY = new SimpleAttributeDefinitionBuilder("uri-scheme-authority", ModelType.STRING, true).setAllowExpression(true).build(); + + private static final SimpleAttributeDefinition NAME = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.NAME, ModelType.STRING, false).setAllowExpression(true).build(); + private static final SimpleAttributeDefinition VALUE = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.VALUE, ModelType.STRING, true).setAllowExpression(true).build(); + + private static final ObjectTypeAttributeDefinition ATTRIBUTE = new ObjectTypeAttributeDefinition.Builder("attribute", NAME, VALUE).build(); + + private static final ObjectListAttributeDefinition SERVICE_ATTRIBUTES = new ObjectListAttributeDefinition.Builder("attributes", ATTRIBUTE) + .setAttributeMarshaller(AttributeMarshaller.UNWRAPPED_OBJECT_LIST_MARSHALLER) + .setAttributeParser(AttributeParser.UNWRAPPED_OBJECT_LIST_PARSER) + .setRequired(false) + .build(); + + private static final ObjectTypeAttributeDefinition SERVICE = new ObjectTypeAttributeDefinition.Builder("service", + ABSTRACT_TYPE, + ABSTRACT_TYPE_AUTHORITY, + URI, + URI_SCHEME_AUTHORITY, + SERVICE_ATTRIBUTES + ).build(); + + private static final ObjectListAttributeDefinition SERVICES = new ObjectListAttributeDefinition.Builder("services", SERVICE) + .setAttributeMarshaller(AttributeMarshaller.UNWRAPPED_OBJECT_LIST_MARSHALLER) + .setAttributeParser(AttributeParser.UNWRAPPED_OBJECT_LIST_PARSER) + .setFlags(Flag.RESTART_RESOURCE_SERVICES) + .build(); + + private final PathElement path = PathElement.pathElement("static-provider"); + + @Override + public PathElement getPathElement() { + return this.path; + } + + @Override + public Stream getAttributes() { + return Stream.of(SERVICES); + } + + @Override + public ServiceDependency resolve(OperationContext context, ModelNode model) throws OperationFailedException { + List services = SERVICES.resolveModelAttribute(context, model).asListOrEmpty(); + List serviceURLs = new ArrayList<>(services.size()); + for (ModelNode service : services) { + ServiceURL.Builder builder = new ServiceURL.Builder(); + builder.setUri(java.net.URI.create(URI.resolveModelAttribute(context, service).asString())); + String abstractType = ABSTRACT_TYPE.resolveModelAttribute(context, service).asStringOrNull(); + if (abstractType != null) { + builder.setAbstractType(abstractType); + } + String abstractTypeAuthority = ABSTRACT_TYPE_AUTHORITY.resolveModelAttribute(context, service).asStringOrNull(); + if (abstractTypeAuthority != null) { + builder.setAbstractTypeAuthority(abstractTypeAuthority); + } + String uriSchemeAuthority = URI_SCHEME_AUTHORITY.resolveModelAttribute(context, service).asStringOrNull(); + if (uriSchemeAuthority != null) { + builder.setUriSchemeAuthority(uriSchemeAuthority); + } + for (ModelNode attribute : SERVICE_ATTRIBUTES.resolveModelAttribute(context, service).asListOrEmpty()) { + String name = NAME.resolveModelAttribute(context, attribute).asString(); + String value = VALUE.resolveModelAttribute(context, attribute).asStringOrNull(); + if (value != null) { + builder.addAttribute(name, AttributeValue.fromString(value)); + } else { + builder.addAttribute(name); + } + } + ServiceURL serviceURL = builder.create(); + Messages.log.tracef("Adding service URL %s", serviceURL); + serviceURLs.add(serviceURL); + } + return ServiceDependency.of(new StaticDiscoveryProvider(serviceURLs)); + } +} diff --git a/discovery/src/main/java/org/wildfly/extension/discovery/StaticDiscoveryProviderRegistrar.java b/discovery/src/main/java/org/wildfly/extension/discovery/StaticDiscoveryProviderResourceRegistrar.java similarity index 91% rename from discovery/src/main/java/org/wildfly/extension/discovery/StaticDiscoveryProviderRegistrar.java rename to discovery/src/main/java/org/wildfly/extension/discovery/StaticDiscoveryProviderResourceRegistrar.java index f61b795d90a..b3343820e0f 100644 --- a/discovery/src/main/java/org/wildfly/extension/discovery/StaticDiscoveryProviderRegistrar.java +++ b/discovery/src/main/java/org/wildfly/extension/discovery/StaticDiscoveryProviderResourceRegistrar.java @@ -16,7 +16,6 @@ import org.jboss.as.controller.ObjectTypeAttributeDefinition; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationFailedException; -import org.jboss.as.controller.PathElement; import org.jboss.as.controller.SimpleAttributeDefinition; import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; @@ -26,7 +25,6 @@ import org.wildfly.discovery.AttributeValue; import org.wildfly.discovery.ServiceURL; import org.wildfly.discovery.impl.StaticDiscoveryProvider; -import org.wildfly.subsystem.resource.ResourceDescriptor; import org.wildfly.subsystem.service.ResourceServiceInstaller; import org.wildfly.subsystem.service.capability.CapabilityServiceInstaller; @@ -34,8 +32,7 @@ * Registers the static discovery provider resource definition. * @author Paul Ferraro */ -public class StaticDiscoveryProviderRegistrar extends DiscoveryProviderRegistrar { - static final PathElement PATH = PathElement.pathElement("static-provider"); +public class StaticDiscoveryProviderResourceRegistrar extends DiscoveryProviderResourceRegistrar { private static final SimpleAttributeDefinition ABSTRACT_TYPE = new SimpleAttributeDefinitionBuilder("abstract-type", ModelType.STRING, true).setAllowExpression(true).build(); private static final SimpleAttributeDefinition ABSTRACT_TYPE_AUTHORITY = new SimpleAttributeDefinitionBuilder("abstract-type-authority", ModelType.STRING, true).setAllowExpression(true).build(); @@ -69,8 +66,8 @@ public class StaticDiscoveryProviderRegistrar extends DiscoveryProviderRegistrar static final Collection ATTRIBUTES = List.of(SERVICES); - StaticDiscoveryProviderRegistrar() { - super(PATH, ResourceDescriptor.builder(DiscoverySubsystemRegistrar.RESOLVER.createChildResolver(PATH)).addAttributes(ATTRIBUTES)); + StaticDiscoveryProviderResourceRegistrar() { + super(StaticDiscoveryProviderResourceDescription.INSTANCE); } @Override @@ -105,6 +102,6 @@ public ResourceServiceInstaller configure(OperationContext context, ModelNode mo Messages.log.tracef("Adding service URL %s", serviceURL); serviceURLs.add(serviceURL); } - return CapabilityServiceInstaller.builder(DiscoveryProviderRegistrar.DISCOVERY_PROVIDER_CAPABILITY, new StaticDiscoveryProvider(serviceURLs)).build(); + return CapabilityServiceInstaller.builder(DiscoveryProviderResourceRegistrar.DISCOVERY_PROVIDER_CAPABILITY, new StaticDiscoveryProvider(serviceURLs)).build(); } } diff --git a/discovery/src/test/java/org/wildfly/extension/discovery/DiscoverySubsystemTestCase.java b/discovery/src/test/java/org/wildfly/extension/discovery/DiscoverySubsystemTestCase.java index 3a3e9e41cf4..8532185abc9 100644 --- a/discovery/src/test/java/org/wildfly/extension/discovery/DiscoverySubsystemTestCase.java +++ b/discovery/src/test/java/org/wildfly/extension/discovery/DiscoverySubsystemTestCase.java @@ -25,6 +25,6 @@ public static Iterable parameters() { } public DiscoverySubsystemTestCase(DiscoverySubsystemSchema schema) { - super(DiscoverySubsystemRegistrar.NAME, new DiscoveryExtension(), schema, DiscoverySubsystemSchema.CURRENT); + super(DiscoverySubsystemResourceDescription.INSTANCE.getName(), new DiscoveryExtension(), schema, DiscoverySubsystemSchema.CURRENT); } } diff --git a/elytron/src/main/java/org/wildfly/extension/elytron/MapperParser.java b/elytron/src/main/java/org/wildfly/extension/elytron/MapperParser.java index 12f59cfb6e2..1130657e5e5 100644 --- a/elytron/src/main/java/org/wildfly/extension/elytron/MapperParser.java +++ b/elytron/src/main/java/org/wildfly/extension/elytron/MapperParser.java @@ -128,7 +128,7 @@ enum Version { .addAttribute(PrincipalTransformerDefinitions.MATCH) .build(); - private PersistentResourceXMLDescription casePrincipalTransformerParser = PersistentResourceXMLDescription.builder(PrincipalTransformerDefinitions.getCasePrincipalTransformerDefinition().getPathElement()) + private PersistentResourceXMLDescription casePrincipalTransformerParser = PersistentResourceXMLDescription.builder(PrincipalTransformerDefinitions.getCasePrincipalTransformerDefinition().getPathElement()) .addAttribute(PrincipalTransformerDefinitions.UPPER_CASE) .build(); @@ -396,7 +396,6 @@ private PersistentResourceXMLDescription getParser_10_0() { .addChild(aggregateEvidenceDecoderParser) .addChild(sourceAddressRoleDecoderParser) .addChild(aggregateRoleDecoderParser) - .addChild(regexPrincipalTransformerParser) .build(); } diff --git a/elytron/src/main/java/org/wildfly/extension/elytron/PolicyParser.java b/elytron/src/main/java/org/wildfly/extension/elytron/PolicyParser.java index f7ff03815f3..64edc6ea7bc 100644 --- a/elytron/src/main/java/org/wildfly/extension/elytron/PolicyParser.java +++ b/elytron/src/main/java/org/wildfly/extension/elytron/PolicyParser.java @@ -19,15 +19,14 @@ class PolicyParser { PersistentResourceXMLDescription parser_1_0 = PersistentResourceXMLDescription.builder(PathElement.pathElement(POLICY)) .setNameAttributeName(PolicyDefinitions.DEFAULT_POLICY.getName()) - .addAttribute(PolicyDefinitions.DEFAULT_POLICY) .addAttribute(JaccPolicyDefinition.POLICIES, AttributeParsers.UNWRAPPED_OBJECT_LIST_PARSER, AttributeMarshallers.OBJECT_LIST_UNWRAPPED) .addAttribute(CustomPolicyDefinition.POLICIES, AttributeParsers.UNWRAPPED_OBJECT_LIST_PARSER, AttributeMarshallers.OBJECT_LIST_UNWRAPPED) .build(); PersistentResourceXMLDescription parser_1_2 = PersistentResourceXMLDescription.builder(PathElement.pathElement(POLICY)) .addAttribute(PolicyDefinitions.JaccPolicyDefinition.POLICY) - .addAttribute(PolicyDefinitions.CustomPolicyDefinition.POLICY) - .build(); + .addAttribute(PolicyDefinitions.CustomPolicyDefinition.POLICY) + .build(); private static class JaccPolicyDefinition { static ObjectTypeAttributeDefinition POLICY = new ObjectTypeAttributeDefinition.Builder(JACC_POLICY, PolicyDefinitions.RESOURCE_NAME, PolicyDefinitions.JaccPolicyDefinition.POLICY_PROVIDER, PolicyDefinitions.JaccPolicyDefinition.CONFIGURATION_FACTORY, PolicyDefinitions.JaccPolicyDefinition.MODULE).build(); diff --git a/elytron/src/main/java/org/wildfly/extension/elytron/TlsParser.java b/elytron/src/main/java/org/wildfly/extension/elytron/TlsParser.java index cd8592d815e..1dbf164a584 100644 --- a/elytron/src/main/java/org/wildfly/extension/elytron/TlsParser.java +++ b/elytron/src/main/java/org/wildfly/extension/elytron/TlsParser.java @@ -83,7 +83,7 @@ class TlsParser { .addAttribute(KeyStoreDefinition.REQUIRED) .addAttribute(FileAttributeDefinitions.PATH) .addAttribute(FileAttributeDefinitions.RELATIVE_TO) - .addAttribute(CredentialReference.getAttributeDefinition()); + ; private PersistentResourceXMLBuilder ldapKeyStoreParser = PersistentResourceXMLDescription.builder(PathElement.pathElement(LDAP_KEY_STORE)) .addAttribute(LdapKeyStoreDefinition.DIR_CONTEXT) diff --git a/io/subsystem/src/main/java/org/wildfly/extension/io/BufferPoolResourceDefinition.java b/io/subsystem/src/main/java/org/wildfly/extension/io/BufferPoolResourceDefinition.java index 9453d53e933..b62b97e00ad 100644 --- a/io/subsystem/src/main/java/org/wildfly/extension/io/BufferPoolResourceDefinition.java +++ b/io/subsystem/src/main/java/org/wildfly/extension/io/BufferPoolResourceDefinition.java @@ -103,7 +103,7 @@ class BufferPoolResourceDefinition extends PersistentResourceDefinition { ); BufferPoolResourceDefinition() { - super(new SimpleResourceDefinition.Parameters(PATH, IOSubsystemRegistrar.RESOLVER.createChildResolver(PATH)) + super(new SimpleResourceDefinition.Parameters(PATH, IOSubsystemResourceRegistrar.RESOLVER.createChildResolver(PATH)) .setAddHandler(new BufferPoolAdd()) .setRemoveHandler(ReloadRequiredRemoveStepHandler.INSTANCE) .addCapabilities(IO_POOL_RUNTIME_CAPABILITY, diff --git a/io/subsystem/src/main/java/org/wildfly/extension/io/IOExtension.java b/io/subsystem/src/main/java/org/wildfly/extension/io/IOExtension.java index 34345f128c1..18fa370d58f 100644 --- a/io/subsystem/src/main/java/org/wildfly/extension/io/IOExtension.java +++ b/io/subsystem/src/main/java/org/wildfly/extension/io/IOExtension.java @@ -17,6 +17,6 @@ public class IOExtension extends SubsystemExtension { public IOExtension() { - super(SubsystemConfiguration.of(IOSubsystemRegistrar.NAME, IOSubsystemModel.CURRENT, IOSubsystemRegistrar::new), SubsystemPersistence.of(IOSubsystemSchema.CURRENT)); + super(SubsystemConfiguration.of(IOSubsystemResourceDescription.INSTANCE, IOSubsystemModel.CURRENT, IOSubsystemResourceRegistrar::new), SubsystemPersistence.of(IOSubsystemSchema.CURRENT)); } } diff --git a/io/subsystem/src/main/java/org/wildfly/extension/io/IOExtensionTransformerRegistration.java b/io/subsystem/src/main/java/org/wildfly/extension/io/IOExtensionTransformerRegistration.java index 2eb51a464d9..9922459e6da 100644 --- a/io/subsystem/src/main/java/org/wildfly/extension/io/IOExtensionTransformerRegistration.java +++ b/io/subsystem/src/main/java/org/wildfly/extension/io/IOExtensionTransformerRegistration.java @@ -13,6 +13,6 @@ public class IOExtensionTransformerRegistration extends SubsystemExtensionTransformerRegistration { public IOExtensionTransformerRegistration() { - super(IOSubsystemRegistrar.NAME, IOSubsystemModel.CURRENT, IOSubsystemTransformationDescriptionFactory.INSTANCE); + super(IOSubsystemResourceDescription.INSTANCE, IOSubsystemModel.CURRENT, IOSubsystemTransformationDescriptionFactory.INSTANCE); } } diff --git a/io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemResourceDescription.java b/io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemResourceDescription.java new file mode 100644 index 00000000000..1019b9f60dd --- /dev/null +++ b/io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemResourceDescription.java @@ -0,0 +1,39 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.extension.io; + +import java.util.stream.Stream; + +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.SubsystemResourceDescription; +import org.jboss.as.controller.capability.RuntimeCapability; +import org.wildfly.io.IOServiceDescriptor; +import org.wildfly.subsystem.resource.capability.CapabilityReference; +import org.wildfly.subsystem.resource.capability.CapabilityReferenceAttributeDefinition; +import org.xnio.XnioWorker; + +/** + * Describes the IO subsystem resource. + */ +public enum IOSubsystemResourceDescription implements SubsystemResourceDescription { + INSTANCE; + + static final RuntimeCapability DEFAULT_WORKER_CAPABILITY = RuntimeCapability.Builder.of(IOServiceDescriptor.DEFAULT_WORKER).build(); + + static final CapabilityReferenceAttributeDefinition DEFAULT_WORKER = new CapabilityReferenceAttributeDefinition.Builder<>("default-worker", CapabilityReference.builder(DEFAULT_WORKER_CAPABILITY, IOServiceDescriptor.NAMED_WORKER).build()) + .setRequired(false) + .build(); + + @Override + public String getName() { + return "io"; + } + + @Override + public Stream getAttributes() { + return Stream.of(DEFAULT_WORKER); + } +} diff --git a/io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemRegistrar.java b/io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemResourceRegistrar.java similarity index 69% rename from io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemRegistrar.java rename to io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemResourceRegistrar.java index ca603c1282c..39d9ad39ae4 100644 --- a/io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemRegistrar.java +++ b/io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemResourceRegistrar.java @@ -11,9 +11,7 @@ import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationFailedException; -import org.jboss.as.controller.PathElement; import org.jboss.as.controller.ResourceDefinition; -import org.jboss.as.controller.ResourceRegistration; import org.jboss.as.controller.SubsystemRegistration; import org.jboss.as.controller.capability.RuntimeCapability; import org.jboss.as.controller.descriptions.ParentResourceDescriptionResolver; @@ -26,8 +24,6 @@ import org.wildfly.subsystem.resource.ManagementResourceRegistrationContext; import org.wildfly.subsystem.resource.ResourceDescriptor; import org.wildfly.subsystem.resource.SubsystemResourceDefinitionRegistrar; -import org.wildfly.subsystem.resource.capability.CapabilityReference; -import org.wildfly.subsystem.resource.capability.CapabilityReferenceAttributeDefinition; import org.wildfly.subsystem.resource.operation.ResourceOperationRuntimeHandler; import org.wildfly.subsystem.service.ResourceServiceConfigurator; import org.wildfly.subsystem.service.ResourceServiceInstaller; @@ -38,32 +34,24 @@ /** * @author Tomaz Cerar (c) 2013 Red Hat Inc. */ -class IOSubsystemRegistrar implements SubsystemResourceDefinitionRegistrar, ResourceServiceConfigurator { +class IOSubsystemResourceRegistrar implements SubsystemResourceDefinitionRegistrar, ResourceServiceConfigurator { - static final String NAME = "io"; - static final PathElement PATH = SubsystemResourceDefinitionRegistrar.pathElement(NAME); - static final ParentResourceDescriptionResolver RESOLVER = new SubsystemResourceDescriptionResolver(NAME, IOSubsystemRegistrar.class); + static final ParentResourceDescriptionResolver RESOLVER = new SubsystemResourceDescriptionResolver(IOSubsystemResourceDescription.INSTANCE.getName(), IOSubsystemResourceRegistrar.class); static final RuntimeCapability MAX_THREADS_CAPABILITY = RuntimeCapability.Builder.of(IOServiceDescriptor.MAX_THREADS).build(); - static final RuntimeCapability DEFAULT_WORKER_CAPABILITY = RuntimeCapability.Builder.of(IOServiceDescriptor.DEFAULT_WORKER).build(); - static final ModelNode LEGACY_DEFAULT_WORKER = new ModelNode("default"); - static final CapabilityReferenceAttributeDefinition DEFAULT_WORKER = new CapabilityReferenceAttributeDefinition.Builder<>("default-worker", CapabilityReference.builder(DEFAULT_WORKER_CAPABILITY, IOServiceDescriptor.NAMED_WORKER).build()) - .setRequired(false) - .build(); - // Tracks max-threads for all workers private final AtomicInteger maxThreads = new AtomicInteger(); @Override public ManagementResourceRegistration register(SubsystemRegistration parent, ManagementResourceRegistrationContext context) { - ManagementResourceRegistration registration = parent.registerSubsystemModel(ResourceDefinition.builder(ResourceRegistration.of(PATH), RESOLVER).build()); + ManagementResourceRegistration registration = parent.registerSubsystemModel(ResourceDefinition.builder(IOSubsystemResourceDescription.INSTANCE, RESOLVER).build()); ResourceDescriptor descriptor = ResourceDescriptor.builder(RESOLVER) - .addAttributes(List.of(DEFAULT_WORKER)) - .addCapabilities(List.of(DEFAULT_WORKER_CAPABILITY, MAX_THREADS_CAPABILITY)) + .addAttributes(IOSubsystemResourceDescription.INSTANCE.getAttributes().toList()) + .addCapabilities(List.of(IOSubsystemResourceDescription.DEFAULT_WORKER_CAPABILITY, MAX_THREADS_CAPABILITY)) .withRuntimeHandler(ResourceOperationRuntimeHandler.configureParentService(this)) .build(); ManagementResourceRegistrar.of(descriptor).register(registration); @@ -82,9 +70,9 @@ public ResourceServiceInstaller configure(OperationContext context, ModelNode mo List installers = new ArrayList<>(2); installers.add(CapabilityServiceInstaller.builder(MAX_THREADS_CAPABILITY, AtomicInteger::intValue, Functions.constantSupplier(this.maxThreads)).build()); - ServiceDependency defaultWorker = DEFAULT_WORKER.resolve(context, model); + ServiceDependency defaultWorker = IOSubsystemResourceDescription.DEFAULT_WORKER.resolve(context, model); if (defaultWorker.isPresent()) { - installers.add(CapabilityServiceInstaller.builder(DEFAULT_WORKER_CAPABILITY, defaultWorker).build()); + installers.add(CapabilityServiceInstaller.builder(IOSubsystemResourceDescription.DEFAULT_WORKER_CAPABILITY, defaultWorker).build()); } return ResourceServiceInstaller.combine(installers); diff --git a/io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemSchema.java b/io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemSchema.java index 30e30890da5..87c5a11bc9c 100644 --- a/io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemSchema.java +++ b/io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemSchema.java @@ -6,22 +6,20 @@ package org.wildfly.extension.io; import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Stream; +import java.util.function.UnaryOperator; -import org.jboss.as.controller.PathAddress; -import org.jboss.as.controller.PersistentResourceXMLDescription; -import org.jboss.as.controller.PersistentSubsystemSchema; +import org.jboss.as.controller.ResourceDescription; import org.jboss.as.controller.SubsystemSchema; +import org.jboss.as.controller.persistence.xml.ResourceXMLElement; +import org.jboss.as.controller.persistence.xml.SubsystemResourceXMLSchema; import org.jboss.as.controller.xml.VersionedNamespace; import org.jboss.dmr.ModelNode; import org.jboss.staxmapper.IntVersion; -import org.wildfly.io.OptionAttributeDefinition; /** * Enumerates the supported schemas of the IO subsystem. */ -public enum IOSubsystemSchema implements PersistentSubsystemSchema { +public enum IOSubsystemSchema implements SubsystemResourceXMLSchema { VERSION_1_1(1, 1), // WildFly 8.1 - 10.1 VERSION_2_0(2, 0), // WildFly 11 - 12 VERSION_3_0(3, 0), // WildFly 13 - 31 @@ -32,7 +30,7 @@ public enum IOSubsystemSchema implements PersistentSubsystemSchema namespace; IOSubsystemSchema(int major, int minor) { - this.namespace = SubsystemSchema.createLegacySubsystemURN(IOSubsystemRegistrar.NAME, new IntVersion(major, minor)); + this.namespace = SubsystemSchema.createLegacySubsystemURN(IOSubsystemResourceDescription.INSTANCE.getName(), new IntVersion(major, minor)); } @Override @@ -41,32 +39,29 @@ public VersionedNamespace getNamespace() { } @Override - public PersistentResourceXMLDescription getXMLDescription() { - PersistentResourceXMLDescription.Factory factory = PersistentResourceXMLDescription.factory(this); - PersistentResourceXMLDescription.Builder builder = factory.builder(IOSubsystemRegistrar.PATH); - if (this.since(VERSION_4_0)) { - builder.addAttribute(IOSubsystemRegistrar.DEFAULT_WORKER); - } else { - builder.setAdditionalOperationsGenerator(new PersistentResourceXMLDescription.AdditionalOperationsGenerator() { + public ResourceXMLElement getSubsystemResourceXMLElement() { + ResourceXMLElement.Builder.Factory factory = ResourceXMLElement.Builder.Factory.newInstance(this); + ResourceXMLElement.Builder builder = factory.createBuilder(IOSubsystemResourceDescription.INSTANCE); + if (!this.since(VERSION_4_0)) { + builder.excludeAttribute(IOSubsystemResourceDescription.DEFAULT_WORKER); + builder.withOperationTransformation(new UnaryOperator<>() { @Override - public void additionalOperations(PathAddress address, ModelNode addOperation, List operations) { + public ModelNode apply(ModelNode operation) { // Apply "magic" default worker referenced by other subsystems - addOperation.get(IOSubsystemRegistrar.DEFAULT_WORKER.getName()).set(IOSubsystemRegistrar.LEGACY_DEFAULT_WORKER); + operation.get(IOSubsystemResourceDescription.DEFAULT_WORKER.getName()).set(IOSubsystemResourceRegistrar.LEGACY_DEFAULT_WORKER); + return operation; } }); } - - Stream workerAttributes = Stream.of(WorkerResourceDefinition.ATTRIBUTES); + ResourceXMLElement.Builder workerBuilder = factory.createBuilder(ResourceDescription.of(WorkerResourceDefinition.PATH, List.of(WorkerResourceDefinition.ATTRIBUTES))); if (!this.since(VERSION_3_0)) { - workerAttributes = workerAttributes.filter(Predicate.not(WorkerResourceDefinition.WORKER_TASK_CORE_THREADS::equals)); + workerBuilder.excludeAttribute(WorkerResourceDefinition.WORKER_TASK_CORE_THREADS); } - PersistentResourceXMLDescription.Builder workerBuilder = factory.builder(WorkerResourceDefinition.PATH).addAttributes(workerAttributes); if (this.since(VERSION_2_0)) { - workerBuilder.addChild(factory.builder(OutboundBindAddressResourceDefinition.PATH).addAttributes(OutboundBindAddressResourceDefinition.ATTRIBUTES.stream()).build()); + workerBuilder.addChild(factory.createBuilder(ResourceDescription.of(OutboundBindAddressResourceDefinition.PATH, OutboundBindAddressResourceDefinition.ATTRIBUTES)).build()); } - return builder.addChild(workerBuilder.build()) - .addChild(factory.builder(BufferPoolResourceDefinition.PATH).addAttributes(BufferPoolResourceDefinition.ATTRIBUTES.stream()).build()) + .addChild(factory.createBuilder(ResourceDescription.of(BufferPoolResourceDefinition.PATH, BufferPoolResourceDefinition.ATTRIBUTES)).build()) .build(); } } diff --git a/io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemTransformationDescriptionFactory.java b/io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemTransformationDescriptionFactory.java index fc25a8a59da..c3285854fba 100644 --- a/io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemTransformationDescriptionFactory.java +++ b/io/subsystem/src/main/java/org/wildfly/extension/io/IOSubsystemTransformationDescriptionFactory.java @@ -25,8 +25,8 @@ public TransformationDescription apply(ModelVersion version) { ResourceTransformationDescriptionBuilder builder = TransformationDescriptionBuilder.Factory.createSubsystemInstance(); if (IOSubsystemModel.VERSION_6_0_0.requiresTransformation(version)) { builder.getAttributeBuilder() - .setDiscard(new DiscardAttributeChecker.DiscardAttributeValueChecker(IOSubsystemRegistrar.LEGACY_DEFAULT_WORKER), IOSubsystemRegistrar.DEFAULT_WORKER) - .addRejectCheck(RejectAttributeChecker.DEFINED, IOSubsystemRegistrar.DEFAULT_WORKER) + .setDiscard(new DiscardAttributeChecker.DiscardAttributeValueChecker(IOSubsystemResourceRegistrar.LEGACY_DEFAULT_WORKER), IOSubsystemResourceDescription.DEFAULT_WORKER) + .addRejectCheck(RejectAttributeChecker.DEFINED, IOSubsystemResourceDescription.DEFAULT_WORKER) .end(); } return builder.build(); diff --git a/io/subsystem/src/main/java/org/wildfly/extension/io/OutboundBindAddressResourceDefinition.java b/io/subsystem/src/main/java/org/wildfly/extension/io/OutboundBindAddressResourceDefinition.java index daae9ec665f..d40ba3a1bea 100644 --- a/io/subsystem/src/main/java/org/wildfly/extension/io/OutboundBindAddressResourceDefinition.java +++ b/io/subsystem/src/main/java/org/wildfly/extension/io/OutboundBindAddressResourceDefinition.java @@ -54,7 +54,7 @@ public class OutboundBindAddressResourceDefinition extends PersistentResourceDef static final OutboundBindAddressResourceDefinition INSTANCE = new OutboundBindAddressResourceDefinition(); private OutboundBindAddressResourceDefinition() { - super(new SimpleResourceDefinition.Parameters(PATH, IOSubsystemRegistrar.RESOLVER.createChildResolver(PATH)) + super(new SimpleResourceDefinition.Parameters(PATH, IOSubsystemResourceRegistrar.RESOLVER.createChildResolver(PATH)) .setAddHandler(new OutboundBindAddressAddHandler()) .setRemoveHandler(new OutboundBindAddressRemoveHandler()) ); diff --git a/io/subsystem/src/main/java/org/wildfly/extension/io/WorkerResourceDefinition.java b/io/subsystem/src/main/java/org/wildfly/extension/io/WorkerResourceDefinition.java index db17ee3abf5..a2462805f45 100644 --- a/io/subsystem/src/main/java/org/wildfly/extension/io/WorkerResourceDefinition.java +++ b/io/subsystem/src/main/java/org/wildfly/extension/io/WorkerResourceDefinition.java @@ -101,7 +101,7 @@ class WorkerResourceDefinition extends PersistentResourceDefinition { private static final AttributeDefinition BUSY_WORKER_THREAD_COUNT = new SimpleAttributeDefinitionBuilder("busy-task-thread-count", ModelType.INT).build(); WorkerResourceDefinition(AtomicInteger maxThreads) { - super(new SimpleResourceDefinition.Parameters(PATH, IOSubsystemRegistrar.RESOLVER.createChildResolver(PATH)) + super(new SimpleResourceDefinition.Parameters(PATH, IOSubsystemResourceRegistrar.RESOLVER.createChildResolver(PATH)) .setAddHandler(new WorkerAdd(maxThreads)) .setRemoveHandler(ReloadRequiredRemoveStepHandler.INSTANCE) .addCapabilities(CAPABILITY)); diff --git a/io/subsystem/src/main/java/org/wildfly/extension/io/WorkerServerDefinition.java b/io/subsystem/src/main/java/org/wildfly/extension/io/WorkerServerDefinition.java index 659646a9d27..1365725736a 100644 --- a/io/subsystem/src/main/java/org/wildfly/extension/io/WorkerServerDefinition.java +++ b/io/subsystem/src/main/java/org/wildfly/extension/io/WorkerServerDefinition.java @@ -43,7 +43,7 @@ public class WorkerServerDefinition extends SimpleResourceDefinition { static final ModelNode NO_METRICS = new ModelNode(IOLogger.ROOT_LOGGER.noMetrics()); WorkerServerDefinition() { - super(new Parameters(PATH, IOSubsystemRegistrar.RESOLVER.createChildResolver(PathElement.pathElement(WorkerResourceDefinition.PATH.getKey(), PATH.getKey()))) + super(new Parameters(PATH, IOSubsystemResourceRegistrar.RESOLVER.createChildResolver(PathElement.pathElement(WorkerResourceDefinition.PATH.getKey(), PATH.getKey()))) .setRuntime()); } diff --git a/io/tests/src/test/java/org/wildfly/extension/io/IOSubsystemTestCase.java b/io/tests/src/test/java/org/wildfly/extension/io/IOSubsystemTestCase.java index 39e3a62f326..1468c7b1db5 100644 --- a/io/tests/src/test/java/org/wildfly/extension/io/IOSubsystemTestCase.java +++ b/io/tests/src/test/java/org/wildfly/extension/io/IOSubsystemTestCase.java @@ -45,7 +45,7 @@ public static Iterable parameters() { } public IOSubsystemTestCase(IOSubsystemSchema schema) { - super(IOSubsystemRegistrar.NAME, new IOExtension(), schema, IOSubsystemSchema.CURRENT); + super(IOSubsystemResourceDescription.INSTANCE.getName(), new IOExtension(), schema, IOSubsystemSchema.CURRENT); } @Test diff --git a/io/tests/src/test/java/org/wildfly/extension/io/IOSubsystemTransformerTestCase.java b/io/tests/src/test/java/org/wildfly/extension/io/IOSubsystemTransformerTestCase.java index ec8ff7f6ff2..0cc7291489a 100644 --- a/io/tests/src/test/java/org/wildfly/extension/io/IOSubsystemTransformerTestCase.java +++ b/io/tests/src/test/java/org/wildfly/extension/io/IOSubsystemTransformerTestCase.java @@ -40,7 +40,7 @@ public static Iterable parameters() { private final ModelVersion version; public IOSubsystemTransformerTestCase(ModelTestControllerVersion controller) { - super(IOSubsystemRegistrar.NAME, new IOExtension()); + super(IOSubsystemResourceDescription.INSTANCE.getName(), new IOExtension()); this.controller = controller; this.version = this.getModelVersion().getVersion(); this.additionalInitialization = AdditionalInitialization.MANAGEMENT; @@ -129,10 +129,10 @@ public void testRejections() throws Exception { private FailedOperationTransformationConfig createFailedOperationTransformationConfig() { FailedOperationTransformationConfig config = new FailedOperationTransformationConfig(); - PathAddress subsystemAddress = PathAddress.pathAddress(IOSubsystemRegistrar.PATH); + PathAddress subsystemAddress = PathAddress.pathAddress(IOSubsystemResourceDescription.INSTANCE.getPathElement()); if (IOSubsystemModel.VERSION_6_0_0.requiresTransformation(this.version)) { - config.addFailedAttribute(subsystemAddress, new FailedOperationTransformationConfig.NewAttributesConfig(IOSubsystemRegistrar.DEFAULT_WORKER.getName())); + config.addFailedAttribute(subsystemAddress, new FailedOperationTransformationConfig.NewAttributesConfig(IOSubsystemResourceDescription.DEFAULT_WORKER.getName())); } return config; diff --git a/pom.xml b/pom.xml index a51ac0a33e7..85296a03bde 100644 --- a/pom.xml +++ b/pom.xml @@ -56,8 +56,8 @@ 3.6.0 - - 11 + + 17 @@ -390,7 +390,7 @@ - + org.apache.maven.plugins maven-enforcer-plugin @@ -475,7 +475,7 @@ - require-java11 + require-java17 enforce @@ -483,7 +483,7 @@ - [11,12) + [17,18) diff --git a/subsystem/src/main/java/org/wildfly/subsystem/SubsystemConfiguration.java b/subsystem/src/main/java/org/wildfly/subsystem/SubsystemConfiguration.java index 3257ff8226d..f8d859a9290 100644 --- a/subsystem/src/main/java/org/wildfly/subsystem/SubsystemConfiguration.java +++ b/subsystem/src/main/java/org/wildfly/subsystem/SubsystemConfiguration.java @@ -7,6 +7,7 @@ import java.util.function.Supplier; import org.jboss.as.controller.SubsystemModel; +import org.jboss.as.controller.SubsystemResourceDescription; import org.wildfly.subsystem.resource.SubsystemResourceDefinitionRegistrar; /** @@ -33,6 +34,17 @@ public interface SubsystemConfiguration { */ SubsystemResourceDefinitionRegistrar getRegistrar(); + /** + * Factory method creating a basic SubsystemConfiguration. + * @param description the subsystem resource description + * @param currentModel the current subsystem model version + * @param registrarFactory a supplier of the resource definition registrar for this subsystem + * @return a new subsystem configuration + */ + static SubsystemConfiguration of(SubsystemResourceDescription description, SubsystemModel model, Supplier registrarFactory) { + return of(description.getName(), model, registrarFactory); + } + /** * Factory method creating a basic SubsystemConfiguration. * @param subsystemName the subsystem name diff --git a/subsystem/src/main/java/org/wildfly/subsystem/SubsystemPersistence.java b/subsystem/src/main/java/org/wildfly/subsystem/SubsystemPersistence.java index 37b5f4e30b5..c1706398f09 100644 --- a/subsystem/src/main/java/org/wildfly/subsystem/SubsystemPersistence.java +++ b/subsystem/src/main/java/org/wildfly/subsystem/SubsystemPersistence.java @@ -12,12 +12,12 @@ import java.util.Set; import org.jboss.as.controller.Feature; -import org.jboss.as.controller.PersistentResourceXMLDescription; -import org.jboss.as.controller.PersistentResourceXMLDescriptionReader; -import org.jboss.as.controller.PersistentResourceXMLDescriptionWriter; -import org.jboss.as.controller.PersistentSubsystemSchema; import org.jboss.as.controller.SubsystemSchema; import org.jboss.as.controller.persistence.SubsystemMarshallingContext; +import org.jboss.as.controller.persistence.xml.ResourceXMLElement; +import org.jboss.as.controller.persistence.xml.SubsystemResourceXMLElementReader; +import org.jboss.as.controller.persistence.xml.SubsystemResourceXMLElementWriter; +import org.jboss.as.controller.persistence.xml.SubsystemResourceXMLSchema; import org.jboss.as.version.Stability; import org.jboss.dmr.ModelNode; import org.jboss.staxmapper.XMLElementReader; @@ -58,7 +58,7 @@ default XMLElementReader> getReader(S schema) { * @param currentSchema the current schema version * @return a subsystem persistence configuration */ - static & PersistentSubsystemSchema> SubsystemPersistence of(S currentSchema) { + static & SubsystemResourceXMLSchema> SubsystemPersistence of(S currentSchema) { return of(EnumSet.of(currentSchema)); } @@ -68,13 +68,13 @@ static & PersistentSubsystemSchema> SubsystemPersistence & PersistentSubsystemSchema> SubsystemPersistence of(Set currentSchemas) { + static & SubsystemResourceXMLSchema> SubsystemPersistence of(Set currentSchemas) { Assert.assertFalse(currentSchemas.isEmpty()); Class schemaClass = currentSchemas.iterator().next().getDeclaringClass(); - // Build PersistentResourceXMLDescription for current schemas to share between reader and writer. - Map currentXMLDescriptions = new EnumMap<>(schemaClass); + // Build ResourceXMLElement for current schemas to share between reader and writer. + Map currentElements = new EnumMap<>(schemaClass); for (S currentSchema : currentSchemas) { - currentXMLDescriptions.put(currentSchema, currentSchema.getXMLDescription()); + currentElements.put(currentSchema, currentSchema.getSubsystemResourceXMLElement()); } Map currentSchemaPerStability = Feature.map(currentSchemas); return new SubsystemPersistence<>() { @@ -85,13 +85,13 @@ public Set getSchemas() { @Override public XMLElementReader> getReader(S schema) { - return Optional.ofNullable(currentXMLDescriptions.get(schema)).>>map(PersistentResourceXMLDescriptionReader::new).orElse(schema); + return Optional.ofNullable(currentElements.get(schema)).>>map(SubsystemResourceXMLElementReader::new).orElse(schema); } @Override public XMLElementWriter getWriter(Stability stability) { S currentSchema = currentSchemaPerStability.get(stability); - return new PersistentResourceXMLDescriptionWriter(currentXMLDescriptions.get(currentSchema)); + return new SubsystemResourceXMLElementWriter(currentElements.get(currentSchema)); } }; }