/* * JBoss, Home of Professional Open Source. * Copyright 2015, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.server.parsing; import static javax.xml.stream.XMLStreamConstants.END_ELEMENT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ANY; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NOT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR; import static org.jboss.as.controller.parsing.ParseUtils.parsePossibleExpression; import static org.jboss.as.controller.parsing.ParseUtils.requireNamespace; import static org.jboss.as.controller.parsing.ParseUtils.requireNoAttributes; import static org.jboss.as.controller.parsing.ParseUtils.requireNoContent; import static org.jboss.as.controller.parsing.ParseUtils.requireSingleAttribute; import static org.jboss.as.controller.parsing.ParseUtils.unexpectedElement; import static org.jboss.as.controller.parsing.ParseUtils.unexpectedEndElement; import static org.jboss.as.controller.parsing.WriteUtils.writeAttribute; import java.util.List; import java.util.Set; import javax.xml.stream.XMLStreamException; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.logging.ControllerLogger; import org.jboss.as.controller.parsing.Attribute; import org.jboss.as.controller.parsing.Element; import org.jboss.as.controller.parsing.Namespace; import org.jboss.as.controller.parsing.ParseUtils; import org.jboss.as.controller.parsing.WriteUtils; 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; /** * Parsing and marshalling logic specific to interfaces. * * The contents of this file have been pulled from {@see CommonXml}, see the commit history of that file for true author * attribution. * * Note: This class is only indented to support versions 1, 2, and 3 of the schema, if later major versions of the schema * include updates to the types represented by this class then this class should be forked. * * @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a> */ class InterfacesXml { public void parseInterfaces(final XMLExtendedStreamReader reader, final Set<String> names, final ModelNode address, final Namespace expectedNs, final List<ModelNode> list, final boolean checkSpecified) throws XMLStreamException { requireNoAttributes(reader); while (reader.nextTag() != END_ELEMENT) { requireNamespace(reader, expectedNs); Element element = Element.forName(reader.getLocalName()); if (Element.INTERFACE != element) { throw unexpectedElement(reader); } // Attributes requireSingleAttribute(reader, Attribute.NAME.getLocalName()); final String name = reader.getAttributeValue(0); if (!names.add(name)) { throw ControllerLogger.ROOT_LOGGER.duplicateInterfaceDeclaration(reader.getLocation()); } final ModelNode interfaceAdd = new ModelNode(); interfaceAdd.get(OP_ADDR).set(address).add(ModelDescriptionConstants.INTERFACE, name); interfaceAdd.get(OP).set(ADD); final ModelNode criteriaNode = interfaceAdd; parseInterfaceCriteria(reader, expectedNs, interfaceAdd); if (checkSpecified && criteriaNode.getType() != ModelType.STRING && criteriaNode.getType() != ModelType.EXPRESSION && criteriaNode.asInt() == 0) { throw unexpectedEndElement(reader); } list.add(interfaceAdd); } } private void parseInterfaceCriteria(final XMLExtendedStreamReader reader, final Namespace expectedNs, final ModelNode interfaceModel) throws XMLStreamException { // all subsequent elements are criteria elements if (reader.nextTag() == END_ELEMENT) { return; } requireNamespace(reader, expectedNs); Element element = Element.forName(reader.getLocalName()); switch (element) { case ANY_IPV4_ADDRESS: case ANY_IPV6_ADDRESS: { if (expectedNs.getMajorVersion() >= 3) { throw ParseUtils.unexpectedElement(reader); } else { throw ParseUtils.unsupportedElement(reader, Element.ANY_ADDRESS.getLocalName()); } } case ANY_ADDRESS: { interfaceModel.get(Element.ANY_ADDRESS.getLocalName()).set(true); requireNoContent(reader); // consume this element requireNoContent(reader); // consume rest of criteria (no further content allowed) return; } } do { requireNamespace(reader, expectedNs); element = Element.forName(reader.getLocalName()); switch (element) { case ANY: parseCompoundInterfaceCriterion(reader, expectedNs, interfaceModel.get(ANY).setEmptyObject()); break; case NOT: parseCompoundInterfaceCriterion(reader, expectedNs, interfaceModel.get(NOT).setEmptyObject()); break; default: { // parseSimpleInterfaceCriterion(reader, criteria.add().set(element.getLocalName(), new // ModelNode()).get(element.getLocalName())); parseSimpleInterfaceCriterion(reader, interfaceModel, false); break; } } } while (reader.nextTag() != END_ELEMENT); } private void parseCompoundInterfaceCriterion(final XMLExtendedStreamReader reader, final Namespace expectedNs, final ModelNode subModel) throws XMLStreamException { requireNoAttributes(reader); while (reader.nextTag() != END_ELEMENT) { requireNamespace(reader, expectedNs); parseSimpleInterfaceCriterion(reader, subModel, true); } } /** * Creates the appropriate AbstractInterfaceCriteriaElement for simple criterion. * <p/> * Note! changes/additions made here will likely need to be added to the corresponding write method that handles the write * of the element. Failure to do so will result in a configuration that can be read, but not written out. * * @throws javax.xml.stream.XMLStreamException if an error occurs * @see {@link #writeInterfaceCriteria(org.jboss.staxmapper.XMLExtendedStreamWriter, org.jboss.dmr.ModelNode, boolean)} */ private void parseSimpleInterfaceCriterion(final XMLExtendedStreamReader reader, final ModelNode subModel, boolean nested) throws XMLStreamException { final Element element = Element.forName(reader.getLocalName()); final String localName = element.getLocalName(); switch (element) { case INET_ADDRESS: { requireSingleAttribute(reader, Attribute.VALUE.getLocalName()); final String value = reader.getAttributeValue(0); ModelNode valueNode = parsePossibleExpression(value); requireNoContent(reader); // todo: validate IP address if (nested) { subModel.get(localName).add(valueNode); } else { subModel.get(localName).set(valueNode); } break; } case LOOPBACK_ADDRESS: { requireSingleAttribute(reader, Attribute.VALUE.getLocalName()); final String value = reader.getAttributeValue(0); ModelNode valueNode = parsePossibleExpression(value); requireNoContent(reader); // todo: validate IP address subModel.get(localName).set(valueNode); break; } case LINK_LOCAL_ADDRESS: case LOOPBACK: case MULTICAST: case POINT_TO_POINT: case PUBLIC_ADDRESS: case SITE_LOCAL_ADDRESS: case UP: case VIRTUAL: { requireNoAttributes(reader); requireNoContent(reader); subModel.get(localName).set(true); break; } case NIC: { requireSingleAttribute(reader, Attribute.NAME.getLocalName()); final String value = reader.getAttributeValue(0); requireNoContent(reader); // todo: validate NIC name if (nested) { subModel.get(localName).add(value); } else { subModel.get(localName).set(value); } break; } case NIC_MATCH: { requireSingleAttribute(reader, Attribute.PATTERN.getLocalName()); final String value = reader.getAttributeValue(0); requireNoContent(reader); // todo: validate pattern if (nested) { subModel.get(localName).add(value); } else { subModel.get(localName).set(value); } break; } case SUBNET_MATCH: { requireSingleAttribute(reader, Attribute.VALUE.getLocalName()); final String value = reader.getAttributeValue(0); requireNoContent(reader); if (nested) { subModel.get(localName).add(value); } else { subModel.get(localName).set(value); } break; } default: throw unexpectedElement(reader); } } /** * Write the interfaces including the criteria elements. * * @param writer the xml stream writer * @param modelNode the model * @throws XMLStreamException */ void writeInterfaces(final XMLExtendedStreamWriter writer, final ModelNode modelNode) throws XMLStreamException { writer.writeStartElement(Element.INTERFACES.getLocalName()); final Set<String> interfaces = modelNode.keys(); for (String ifaceName : interfaces) { final ModelNode iface = modelNode.get(ifaceName); writer.writeStartElement(Element.INTERFACE.getLocalName()); writeAttribute(writer, Attribute.NAME, ifaceName); // <any-* /> is just handled at the root if (iface.get(Element.ANY_ADDRESS.getLocalName()).asBoolean(false)) { writer.writeEmptyElement(Element.ANY_ADDRESS.getLocalName()); } else { // Write the other criteria elements writeInterfaceCriteria(writer, iface, false); } writer.writeEndElement(); } writer.writeEndElement(); } /** * Write the criteria elements, extracting the information of the sub-model. * * @param writer the xml stream writer * @param subModel the interface model * @param nested whether it the criteria elements are nested as part of <not /> or <any /> * @throws XMLStreamException */ private void writeInterfaceCriteria(final XMLExtendedStreamWriter writer, final ModelNode subModel, final boolean nested) throws XMLStreamException { for (final Property property : subModel.asPropertyList()) { if (property.getValue().isDefined()) { writeInterfaceCriteria(writer, property, nested); } } } private void writeInterfaceCriteria(final XMLExtendedStreamWriter writer, final Property property, final boolean nested) throws XMLStreamException { final Element element = Element.forName(property.getName()); switch (element) { case INET_ADDRESS: writeInterfaceCriteria(writer, element, Attribute.VALUE, property.getValue(), nested); break; case LOOPBACK_ADDRESS: writeInterfaceCriteria(writer, element, Attribute.VALUE, property.getValue(), false); break; case LINK_LOCAL_ADDRESS: case LOOPBACK: case MULTICAST: case POINT_TO_POINT: case PUBLIC_ADDRESS: case SITE_LOCAL_ADDRESS: case UP: case VIRTUAL: { if (property.getValue().asBoolean(false)) { writer.writeEmptyElement(element.getLocalName()); } break; } case NIC: writeInterfaceCriteria(writer, element, Attribute.NAME, property.getValue(), nested); break; case NIC_MATCH: writeInterfaceCriteria(writer, element, Attribute.PATTERN, property.getValue(), nested); break; case SUBNET_MATCH: writeInterfaceCriteria(writer, element, Attribute.VALUE, property.getValue(), nested); break; case ANY: case NOT: if (nested) { break; } writer.writeStartElement(element.getLocalName()); writeInterfaceCriteria(writer, property.getValue(), true); writer.writeEndElement(); break; case NAME: // not a criteria element; ignore break; case ANY_ADDRESS: assert property.getValue().asBoolean(false) == false; // not a criteria element; ignore break; default: { // TODO we perhaps should just log a warning. throw ControllerLogger.ROOT_LOGGER.unknownCriteriaInterfaceProperty(property.getName()); } } } private static void writeInterfaceCriteria(final XMLExtendedStreamWriter writer, final Element element, final Attribute attribute, final ModelNode subModel, boolean asList) throws XMLStreamException { if (asList) { // Nested criteria elements are represented as list in the model WriteUtils.writeListAsMultipleElements(writer, element, attribute, subModel); } else { WriteUtils.writeSingleElement(writer, element, attribute, subModel); } } }