/* * JBoss, Home of Professional Open Source. * Copyright 2014, 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.jacorb; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; 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.descriptions.ModelDescriptionConstants.SUBSYSTEM; import static org.jboss.as.controller.parsing.ParseUtils.duplicateAttribute; import static org.jboss.as.controller.parsing.ParseUtils.duplicateNamedElement; import static org.jboss.as.controller.parsing.ParseUtils.missingRequired; 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.requireNoNamespaceAttribute; import static org.jboss.as.controller.parsing.ParseUtils.unexpectedAttribute; import static org.jboss.as.controller.parsing.ParseUtils.unexpectedElement; import static org.jboss.as.jacorb.JacORBSubsystemConstants.SECURITY; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import org.jboss.as.controller.SimpleAttributeDefinition; import org.jboss.as.controller.persistence.SubsystemMarshallingContext; import org.jboss.as.jacorb.logging.JacORBLogger; import org.jboss.dmr.ModelNode; import org.jboss.dmr.Property; import org.jboss.staxmapper.XMLElementReader; import org.jboss.staxmapper.XMLElementWriter; import org.jboss.staxmapper.XMLExtendedStreamReader; import org.jboss.staxmapper.XMLExtendedStreamWriter; /** * <p> * This class implements a parser for the JacORB subsystem. * </p> * * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a> * @author <a href="mailto:tadamski@redhat.com">Tomasz Adamski</a> */ public class JacORBSubsystemParser implements XMLStreamConstants, XMLElementReader<List<ModelNode>>, XMLElementWriter<SubsystemMarshallingContext> { static final JacORBSubsystemParser INSTANCE = new JacORBSubsystemParser(); /** * <p> * Private constructor required by the {@code Singleton} pattern. * </p> */ private JacORBSubsystemParser() { } @Override public void readElement(XMLExtendedStreamReader reader, List<ModelNode> nodes) throws XMLStreamException { // the subsystem element has no attributes. requireNoAttributes(reader); final ModelNode subsystem = new ModelNode(); subsystem.get(OP).set(ADD); subsystem.get(OP_ADDR).add(SUBSYSTEM, JacORBExtension.SUBSYSTEM_NAME); nodes.add(subsystem); Namespace readerNS = Namespace.forUri(reader.getNamespaceURI()); switch (readerNS) { case JacORB_1_0: { this.readElement_1_0(readerNS, reader, subsystem); break; } case JacORB_1_1: case JacORB_1_2: case JacORB_1_3: { this.readElement_1_1(readerNS, reader, subsystem); break; } case JacORB_1_4: { this.readElement_1_4(readerNS, reader, nodes); break; } case JacORB_2_0: { this.readElement_2_0(readerNS, reader, nodes); break; } default: { throw unexpectedElement(reader); } } } /** * <p> * Parses the JacORB subsystem configuration according to the XSD version 1.0. * </p> * * @param namespace the expected {@code Namespace} of the parsed elements. * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that will hold the parsed subsystem configuration. * @throws javax.xml.stream.XMLStreamException * if an error occurs while parsing the XML. */ private void readElement_1_0(Namespace namespace, XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException { final EnumSet<Element> encountered = EnumSet.noneOf(Element.class); while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { // check the element namespace. if (Namespace.JacORB_1_0 != Namespace.forUri(reader.getNamespaceURI())) throw unexpectedElement(reader); final Element element = Element.forName(reader.getLocalName()); // there can be multiple property elements. if (!encountered.add(element) && element != Element.PROPERTY) { throw duplicateNamedElement(reader, element.getLocalName()); } switch (element) { case ORB: { this.parseORBConfig_1_0(reader, node); break; } case POA: { this.parsePOAConfig(namespace, reader, node); break; } case INTEROP: { this.parseInteropConfig(reader, node); break; } case SECURITY: { this.parseSecurityConfig_1_0(reader, node); break; } case PROPERTY: { ModelNode propertiesNode = node.get(JacORBSubsystemConstants.PROPERTIES); this.parseGenericProperty_1_0(reader, propertiesNode); break; } case ORB_INITIALIZERS: { this.parseORBInitializersConfig_1_0(reader, node); break; } default: { throw unexpectedElement(reader); } } } } /** * <p> * Parses the JacORB subsystem configuration according to the XSD version 1.1 or higher. * </p> * * @param namespace the expected {@code Namespace} of the parsed elements. * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that will hold the parsed subsystem configuration. * @throws javax.xml.stream.XMLStreamException * if an error occurs while parsing the XML. */ private void readElement_1_1(Namespace namespace, XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException { final EnumSet<Element> encountered = EnumSet.noneOf(Element.class); while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { // check the element namespace. if (namespace != Namespace.forUri(reader.getNamespaceURI())) throw unexpectedElement(reader); final Element element = Element.forName(reader.getLocalName()); if (!encountered.add(element)) { throw duplicateNamedElement(reader, element.getLocalName()); } switch (element) { case ORB: { this.parseORBConfig(namespace, reader, node); break; } case POA: { this.parsePOAConfig(namespace, reader, node); break; } case NAMING: { this.parseNamingConfig(reader, node); break; } case INTEROP: { this.parseInteropConfig(reader, node); break; } case SECURITY: { this.parseSecurityConfig(reader, node); break; } case PROPERTIES: { this.parsePropertiesConfig(namespace, reader, node); break; } default: { throw unexpectedElement(reader); } } } } private void readElement_1_4(Namespace namespace, XMLExtendedStreamReader reader, List<ModelNode> nodes) throws XMLStreamException { final EnumSet<Element> encountered = EnumSet.noneOf(Element.class); while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { // check the element namespace. if (namespace != Namespace.forUri(reader.getNamespaceURI())) throw unexpectedElement(reader); final Element element = Element.forName(reader.getLocalName()); if (!encountered.add(element)) { throw duplicateNamedElement(reader, element.getLocalName()); } ModelNode node = nodes.get(0); // main subsystem node. switch (element) { case ORB: { this.parseORBConfig(namespace, reader, nodes.get(0)); break; } case POA: { this.parsePOAConfig(namespace, reader, node); break; } case NAMING: { this.parseNamingConfig(reader, node); break; } case INTEROP: { this.parseInteropConfig(reader, node); break; } case SECURITY: { this.parseSecurityConfig(reader, node); break; } case IOR_SETTINGS: { IORSettingsParser.INSTANCE.readElement(reader, nodes); break; } case PROPERTIES: { this.parsePropertiesConfig(namespace, reader, node); break; } default: { throw unexpectedElement(reader); } } } } private void readElement_2_0(Namespace namespace, XMLExtendedStreamReader reader, List<ModelNode> nodes) throws XMLStreamException { readElement_1_4(namespace, reader, nodes); } /** * <p> * Parses the {@code orb} section of the JacORB subsystem configuration according to the XSD version 1.0. * </p> * * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that will hold the parsed ORB configuration. * @throws javax.xml.stream.XMLStreamException * if an error occurs while parsing the XML. */ private void parseORBConfig_1_0(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException { // parse the orb config attributes. EnumSet<Attribute> expectedAttributes = EnumSet.of(Attribute.NAME, Attribute.ORB_PRINT_VERSION, Attribute.ORB_GIOP_MINOR_VERSION, Attribute.ORB_USE_BOM, Attribute.ORB_USE_IMR, Attribute.ORB_CACHE_POA_NAMES, Attribute.ORB_CACHE_TYPECODES); this.parseAttributes(reader, node, expectedAttributes, null); // parse the orb config elements. EnumSet<Element> encountered = EnumSet.noneOf(Element.class); while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { // check the element namespace. if (Namespace.JacORB_1_0 != Namespace.forUri(reader.getNamespaceURI())) throw unexpectedElement(reader); final Element element = Element.forName(reader.getLocalName()); // check for duplicate elements. if (!encountered.add(element)) { throw duplicateNamedElement(reader, element.getLocalName()); } switch (element) { case ORB_CONNECTION: { this.parseORBConnectionConfig(reader, node); break; } case NAMING: { this.parseNamingConfig(reader, node); break; } default: { throw unexpectedElement(reader); } } } } /** * <p> * Parses the {@code orb} section of the JacORB subsystem configuration according to the XSD version 1.1 or higher. * </p> * * @param namespace the expected {@code Namespace} of the parsed elements. * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that will hold the parsed ORB configuration. * @throws javax.xml.stream.XMLStreamException * if an error occurs while parsing the XML. */ private void parseORBConfig(Namespace namespace, XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException { // parse the orb config attributes. EnumSet<Attribute> expectedAttributes = EnumSet.of(Attribute.NAME, Attribute.ORB_PRINT_VERSION, Attribute.ORB_GIOP_MINOR_VERSION, Attribute.ORB_USE_BOM, Attribute.ORB_USE_IMR, Attribute.ORB_CACHE_POA_NAMES, Attribute.ORB_CACHE_TYPECODES); // version 1.2 of the schema allows for the configuration of the ORB socket bindings. if (namespace.ordinal() >= Namespace.JacORB_1_2.ordinal()) { expectedAttributes.add(Attribute.ORB_SOCKET_BINDING); expectedAttributes.add(Attribute.ORB_SSL_SOCKET_BINDING); } if (namespace.ordinal() >= Namespace.JacORB_2_0.ordinal()) { expectedAttributes.add(Attribute.ORB_PERSISTENT_SERVER_ID); } this.parseAttributes(reader, node, expectedAttributes, null); // parse the orb config elements. EnumSet<Element> encountered = EnumSet.noneOf(Element.class); while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { // check the element namespace. if (namespace != Namespace.forUri(reader.getNamespaceURI())) throw unexpectedElement(reader); final Element element = Element.forName(reader.getLocalName()); // check for duplicate elements. if (!encountered.add(element)) { throw duplicateNamedElement(reader, element.getLocalName()); } switch (element) { case ORB_CONNECTION: { this.parseORBConnectionConfig(reader, node); break; } case ORB_INITIALIZERS: { this.parseORBInitializersConfig(reader, node); break; } default: { throw unexpectedElement(reader); } } } } /** * <p> * Parses the ORB {@code connection} section of the JacORB subsystem configuration. * </p> * * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that will hold the parsed ORB connection configuration. * @throws javax.xml.stream.XMLStreamException * if an error occurs while parsing the XML. */ private void parseORBConnectionConfig(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException { // parse the orb connection config attributes. EnumSet<Attribute> attributes = EnumSet.of(Attribute.ORB_CONN_RETRIES, Attribute.ORB_CONN_RETRY_INTERVAL, Attribute.ORB_CONN_CLIENT_TIMEOUT, Attribute.ORB_CONN_SERVER_TIMEOUT, Attribute.ORB_CONN_MAX_SERVER_CONNECTIONS, Attribute.ORB_CONN_MAX_MANAGED_BUF_SIZE, Attribute.ORB_CONN_OUTBUF_SIZE, Attribute.ORB_CONN_OUTBUF_CACHE_TIMEOUT); this.parseAttributes(reader, node, attributes, null); // the connection sub-element doesn't have child elements. requireNoContent(reader); } /** * <p> * Parses the ORB {@code initializers} section of the JacORB subsystem configuration according to the XSD version 1.0. * </p> * * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that will hold the parsed ORB initializers configuration. * @throws javax.xml.stream.XMLStreamException * if an error occurs while parsing the XML. */ private void parseORBInitializersConfig_1_0(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException { requireNoAttributes(reader); // read the element text - a comma-separated list of initializers. String initializersList = reader.getElementText(); if (initializersList != null) { String[] initializers = initializersList.split(","); // read each configured initializer and set the appropriate values in the model node. for (String initializer : initializers) { SimpleAttributeDefinition definition = (SimpleAttributeDefinition)JacORBSubsystemDefinitions.valueOf(initializer); if (definition != null && JacORBSubsystemDefinitions.ORB_INIT_ATTRIBUTES.contains(definition)) node.get(definition.getName()).set(JacORBSubsystemConstants.ON); else throw JacORBLogger.ROOT_LOGGER.invalidInitializerConfig(initializer, reader.getLocation()); } } } /** * <p> * Parses the ORB {@code initializers} section of the JacORB subsystem configuration according to the XSD version 1.1 * or higher. * </p> * * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that will hold the parsed ORB initializers configuration. * @throws javax.xml.stream.XMLStreamException * if an error occurs while parsing the XML. */ private void parseORBInitializersConfig(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException { // parse the initializers config attributes. EnumSet<Attribute> attributes = EnumSet.of(Attribute.ORB_INIT_SECURITY, Attribute.ORB_INIT_TRANSACTIONS); this.parseAttributes(reader, node, attributes, null); // the initializers element doesn't have child elements. requireNoContent(reader); //if security="on" change it to security="identity" if(node.has(SECURITY) && node.get(SECURITY).asString().equals(JacORBSubsystemConstants.ON)) { node.get(SECURITY).set(SecurityAllowedValues.IDENTITY.toString()); } } /** * <p> * Parses the {@code poa} section of the JacORB subsystem configuration. * </p> * * @param namespace the expected {@code Namespace} of the parsed elements. * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that will hold the parsed POA configuration. * @throws javax.xml.stream.XMLStreamException * if an error occurs while parsing the XML. */ private void parsePOAConfig(Namespace namespace, XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException { // parse the poa config attributes. EnumSet<Attribute> expectedAttributes = EnumSet.of(Attribute.POA_MONITORING, Attribute.POA_QUEUE_WAIT, Attribute.POA_QUEUE_MIN, Attribute.POA_QUEUE_MAX); this.parseAttributes(reader, node, expectedAttributes, null); // parse the poa config elements. EnumSet<Element> encountered = EnumSet.noneOf(Element.class); while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { // check the element namespace. if (namespace != Namespace.forUri(reader.getNamespaceURI())) throw unexpectedElement(reader); final Element element = Element.forName(reader.getLocalName()); // check for duplicate elements. if (!encountered.add(element)) { throw duplicateNamedElement(reader, element.getLocalName()); } switch (element) { case POA_REQUEST_PROC: { // parse the poa request-processors config attributes. EnumSet<Attribute> attributes = EnumSet.of(Attribute.POA_REQUEST_PROC_POOL_SIZE, Attribute.POA_REQUEST_PROC_MAX_THREADS); this.parseAttributes(reader, node, attributes, null); // the request-processors element doesn't have child elements. requireNoContent(reader); break; } default: { throw unexpectedElement(reader); } } } } /** * <p> * Parses the {@code naming} section of the JacORB subsystem configuration. * </p> * * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that will hold the parsed interoperability configuration. * @throws javax.xml.stream.XMLStreamException * if an error occurs while parsing the XML. */ private void parseNamingConfig(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException { // parse all naming attributes. EnumSet<Attribute> expectedAttributes = EnumSet.of(Attribute.NAMING_ROOT_CONTEXT, Attribute.NAMING_EXPORT_CORBALOC); this.parseAttributes(reader, node, expectedAttributes, null); // the naming element doesn't have child elements. requireNoContent(reader); } /** * <p> * Parses the {@code interop} section of the JacORB subsystem configuration. * </p> * * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that will hold the parsed interoperability configuration. * @throws javax.xml.stream.XMLStreamException * if an error occurs while parsing the XML. */ private void parseInteropConfig(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException { // parse all interop attributes. EnumSet<Attribute> expectedAttributes = EnumSet.of(Attribute.INTEROP_SUN, Attribute.INTEROP_COMET, Attribute.INTEROP_IONA, Attribute.INTEROP_CHUNK_RMI_VALUETYPES, Attribute.INTEROP_LAX_BOOLEAN_ENCODING, Attribute.INTEROP_INDIRECTION_ENCODING_DISABLE, Attribute.INTEROP_STRICT_CHECK_ON_TC_CREATION); this.parseAttributes(reader, node, expectedAttributes, null); // the interop element doesn't have child elements. requireNoContent(reader); } /** * <p> * Parses the {@code security} section of the JacORB subsystem configuration according to the XSD version 1.0. * </p> * * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that will hold the parsed security configuration. * @throws javax.xml.stream.XMLStreamException * if an error occurs while parsing the XML. */ private void parseSecurityConfig_1_0(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException { // parse all security attributes. EnumSet<Attribute> expectedAttributes = EnumSet.of(Attribute.SECURITY_SUPPORT_SSL, Attribute.SECURITY_ADD_COMPONENT_INTERCEPTOR, Attribute.SECURITY_CLIENT_SUPPORTS, Attribute.SECURITY_CLIENT_REQUIRES, Attribute.SECURITY_SERVER_SUPPORTS, Attribute.SECURITY_SERVER_REQUIRES, Attribute.SECURITY_USE_DOMAIN_SF, Attribute.SECURITY_USE_DOMAIN_SSF); EnumSet<Attribute> parsedAttributes = EnumSet.noneOf(Attribute.class); for (int i = 0; i < reader.getAttributeCount(); i++) { requireNoNamespaceAttribute(reader, i); String attrValue = reader.getAttributeValue(i); final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i)); // check for unexpected attributes. if (!expectedAttributes.contains(attribute)) throw unexpectedAttribute(reader, i); // check for duplicate attributes. if (!parsedAttributes.add(attribute)) { throw duplicateAttribute(reader, attribute.getLocalName()); } switch (attribute) { // check the attributes that need to be converted from int to string. case SECURITY_CLIENT_SUPPORTS: case SECURITY_CLIENT_REQUIRES: case SECURITY_SERVER_SUPPORTS: case SECURITY_SERVER_REQUIRES: SSLConfigValue value = SSLConfigValue.fromValue(attrValue); if (value == null) throw JacORBLogger.ROOT_LOGGER.invalidSSLConfig(attrValue, reader.getLocation()); attrValue = value.toString(); default: SimpleAttributeDefinition definition = ((SimpleAttributeDefinition) JacORBSubsystemDefinitions. valueOf(attribute.getLocalName())); // a null definition represents an attribute that has been deprecated and is no longer used. if (definition != null) definition.parseAndSetParameter(attrValue, node, reader); } } // the security element doesn't have child elements. requireNoContent(reader); } /** * <p> * Parses the {@code security} section of the JacORB subsystem configuration according to the XSD version 1.1 or higher. * </p> * * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that will hold the parsed security configuration. * @throws javax.xml.stream.XMLStreamException * if an error occurs while parsing the XML. */ private void parseSecurityConfig(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException { // parse all security attributes. EnumSet<Attribute> expectedAttributes = EnumSet.of(Attribute.SECURITY_SUPPORT_SSL, Attribute.SECURITY_SECURITY_DOMAIN, Attribute.SECURITY_ADD_COMPONENT_INTERCEPTOR, Attribute.SECURITY_CLIENT_SUPPORTS, Attribute.SECURITY_CLIENT_REQUIRES, Attribute.SECURITY_SERVER_SUPPORTS, Attribute.SECURITY_SERVER_REQUIRES); this.parseAttributes(reader, node, expectedAttributes, null); // the security element doesn't have child elements. requireNoContent(reader); } /** * <p> * Parses the {@code properties} section of the JacORB subsystem configuration. * </p> * * @param namespace the expected {@code Namespace} of the parsed elements. * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that will hold the parsed properties. * @throws XMLStreamException if an error occurs while parsing the XML. */ private void parsePropertiesConfig(Namespace namespace, XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException { // the properties element doesn't define any attributes, just sub-elements. requireNoAttributes(reader); while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { // check the element namespace. if (namespace != Namespace.forUri(reader.getNamespaceURI())) throw unexpectedElement(reader); final Element element = Element.forName(reader.getLocalName()); switch (element) { case PROPERTY: { // parse the property element. this.parseGenericProperty(reader, node.get(JacORBSubsystemConstants.PROPERTIES)); break; } default: { throw unexpectedElement(reader); } } } } /** * <p> * Parses a {@code property} element according to the XSD version 1.0 and adds the key/value pair to the specified * {@code ModelNode}. * </p> * * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that contains all parsed ORB properties. * @throws javax.xml.stream.XMLStreamException * if an error occurs while parsing the XML. */ private void parseGenericProperty_1_0(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException { String name = null; String val = null; EnumSet<Attribute> required = EnumSet.of(Attribute.PROP_KEY, Attribute.PROP_VALUE); final int count = reader.getAttributeCount(); for (int i = 0; i < count; i++) { requireNoNamespaceAttribute(reader, i); final String value = reader.getAttributeValue(i); final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i)); required.remove(attribute); switch (attribute) { case PROP_KEY: { name = value; break; } case PROP_VALUE: { val = value; break; } default: throw unexpectedAttribute(reader, i); } } if (!required.isEmpty()) { throw missingRequired(reader, required); } node.get(name).set(val); requireNoContent(reader); } /** * <p> * Parses a {@code property} element according to the XSD version 1.1 or higher and adds the name/value pair to the * specified {@code ModelNode}. * </p> * * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that contains all parsed ORB properties. * @throws javax.xml.stream.XMLStreamException * if an error occurs while parsing the XML. */ private void parseGenericProperty(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException { String name = null; ModelNode val = null; EnumSet<Attribute> required = EnumSet.of(Attribute.NAME, Attribute.PROP_VALUE); final int count = reader.getAttributeCount(); for (int i = 0; i < count; i++) { requireNoNamespaceAttribute(reader, i); final String value = reader.getAttributeValue(i); final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i)); required.remove(attribute); switch (attribute) { case NAME: { name = value; break; } case PROP_VALUE: { val = JacORBSubsystemDefinitions.PROPERTIES.parse(value, reader.getLocation()); break; } default: throw unexpectedAttribute(reader, i); } } if (!required.isEmpty()) { throw missingRequired(reader, required); } node.get(name).set(val); requireNoContent(reader); } /** * <p> * Parses all attributes from the current element and sets them in the specified {@code ModelNode}. * </p> * * @param reader the {@code XMLExtendedStreamReader} used to read the configuration XML. * @param node the {@code ModelNode} that will hold the parsed attributes. * @param expectedAttributes an {@code EnumSet} containing all expected attributes. If the parsed attribute is not * one of the expected attributes, an exception is thrown. * @param requiredAttributes an {@code EnumSet} containing all required attributes. If a required attribute is not * found, an exception is thrown. * @throws XMLStreamException if an error occurs while parsing the XML, if an attribute is not one of the expected * attributes or if one of the required attributes is not parsed. */ private void parseAttributes(XMLExtendedStreamReader reader, ModelNode node, EnumSet<Attribute> expectedAttributes, EnumSet<Attribute> requiredAttributes) throws XMLStreamException { EnumSet<Attribute> parsedAttributes = EnumSet.noneOf(Attribute.class); if (requiredAttributes == null) { requiredAttributes = EnumSet.noneOf(Attribute.class); } for (int i = 0; i < reader.getAttributeCount(); i++) { requireNoNamespaceAttribute(reader, i); final String attrValue = reader.getAttributeValue(i); final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i)); // check for unexpected attributes. if (!expectedAttributes.contains(attribute)) throw unexpectedAttribute(reader, i); // check for duplicate attributes. if (!parsedAttributes.add(attribute)) { throw duplicateAttribute(reader, attribute.getLocalName()); } requiredAttributes.remove(attribute); ((SimpleAttributeDefinition)JacORBSubsystemDefinitions.valueOf(attribute.getLocalName())). parseAndSetParameter(attrValue, node, reader); } // throw an exception if a required attribute wasn't found. if (!requiredAttributes.isEmpty()) { throw missingRequired(reader, requiredAttributes); } } @Override public void writeContent(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException { context.startSubsystemElement(Namespace.CURRENT.getUriString(), false); ModelNode node = context.getModelNode(); // write the orb configuration section if there are any orb properties to be written. this.writeORBConfig(writer, node); // write the poa configuration section if there are any poa properties to be written. this.writePOAConfig(writer, node); // write the naming configuration section if there are any naming properties to be written. this.writeNamingConfig(writer, node); // write the interop configuration section if there are any interop properties to be written. this.writeInteropConfig(writer, node); // write the security configuration section if there are any security properties to be written. this.writeSecurityConfig(writer, node); // write all defined generic properties. String properties = JacORBSubsystemConstants.PROPERTIES; if (node.hasDefined(properties)) { this.writeGenericProperties(writer, node.get(properties)); } // write the ior-settings configuration section if there are any security properties to be written. if (node.hasDefined(JacORBSubsystemConstants.IOR_SETTINGS)) IORSettingsParser.INSTANCE.writeContent(writer, node.get(JacORBSubsystemConstants.IOR_SETTINGS, JacORBSubsystemConstants.DEFAULT)); writer.writeEndElement(); // End of subsystem element } /** * <p> * Writes the {@code orb} section of the JacORB subsystem configuration using the contents of the provided * {@code ModelNode}. * </p> * * @param writer the {@code XMLExtendedStreamWriter} used to write the configuration XML. * @param node the {@code ModelNode} that might contain ORB configuration properties. * @throws XMLStreamException if an error occurs while writing the ORB configuration. */ private void writeORBConfig(XMLExtendedStreamWriter writer, ModelNode node) throws XMLStreamException { boolean writeORB = this.isWritable(node, JacORBSubsystemDefinitions.ORB_ATTRIBUTES); boolean writeORBConnection = this.isWritable(node, JacORBSubsystemDefinitions.ORB_CONN_ATTRIBUTES); boolean writeORBInitializer = this.isWritable(node, JacORBSubsystemDefinitions.ORB_INIT_ATTRIBUTES); // if no connection or initializers properties are available, just write the orb properties (if any) in an empty element. if (!writeORBConnection && !writeORBInitializer) { if (writeORB) { writer.writeEmptyElement(JacORBSubsystemConstants.ORB); this.writeAttributes(writer, node, JacORBSubsystemDefinitions.ORB_ATTRIBUTES); } } // otherwise write the orb element with the appropriate sub-elements. else { writer.writeStartElement(JacORBSubsystemConstants.ORB); this.writeAttributes(writer, node, JacORBSubsystemDefinitions.ORB_ATTRIBUTES); if (writeORBConnection) { writer.writeEmptyElement(JacORBSubsystemConstants.ORB_CONN); this.writeAttributes(writer, node, JacORBSubsystemDefinitions.ORB_CONN_ATTRIBUTES); } if (writeORBInitializer) { writer.writeEmptyElement(JacORBSubsystemConstants.ORB_INIT); this.writeAttributes(writer, node, JacORBSubsystemDefinitions.ORB_INIT_ATTRIBUTES); } writer.writeEndElement(); } } /** * <p> * Writes the {@code poa} section of the JacORB subsystem configuration using the contents of the provided * {@code ModelNode}. * </p> * * @param writer the {@code XMLExtendedStreamWriter} used to write the configuration XML. * @param node the {@code ModelNode} that might contain POA configuration properties. * @throws XMLStreamException if an error occurs while writing the POA configuration. */ private void writePOAConfig(XMLExtendedStreamWriter writer, ModelNode node) throws XMLStreamException { boolean writePOA = this.isWritable(node, JacORBSubsystemDefinitions.POA_ATTRIBUTES); boolean writePOARP = this.isWritable(node, JacORBSubsystemDefinitions.POA_RP_ATTRIBUTES); // if no request processor properties are available, just write the poa properties (if any) in an empty element. if (!writePOARP) { if (writePOA) { writer.writeEmptyElement(JacORBSubsystemConstants.POA); this.writeAttributes(writer, node, JacORBSubsystemDefinitions.POA_ATTRIBUTES); } } // otherwise write the poa element with the appropriate sub-elements. else { writer.writeStartElement(JacORBSubsystemConstants.POA); this.writeAttributes(writer, node, JacORBSubsystemDefinitions.POA_ATTRIBUTES); writer.writeEmptyElement(JacORBSubsystemConstants.POA_RP); this.writeAttributes(writer, node, JacORBSubsystemDefinitions.POA_RP_ATTRIBUTES); writer.writeEndElement(); } } /** * <p> * Writes the {@code naming} section of the JacORB subsystem configuration using the contents of the provided * {@code ModelNode}. * </p> * * @param writer the {@code XMLExtendedStreamWriter} used to write the configuration XML. * @param node the {@code ModelNode} that contains the naming configuration properties. * @throws XMLStreamException if an error occurs while writing the interop configuration. */ private void writeNamingConfig(XMLExtendedStreamWriter writer, ModelNode node) throws XMLStreamException { boolean writeNaming = this.isWritable(node, JacORBSubsystemDefinitions.NAMING_ATTRIBUTES); if (writeNaming) { writer.writeEmptyElement(JacORBSubsystemConstants.NAMING); this.writeAttributes(writer, node, JacORBSubsystemDefinitions.NAMING_ATTRIBUTES); } } /** * <p> * Writes the {@code interop} section of the JacORB subsystem configuration using the contents of the provided * {@code ModelNode}. * </p> * * @param writer the {@code XMLExtendedStreamWriter} used to write the configuration XML. * @param node the {@code ModelNode} that contains the interoperability configuration properties. * @throws XMLStreamException if an error occurs while writing the interop configuration. */ private void writeInteropConfig(XMLExtendedStreamWriter writer, ModelNode node) throws XMLStreamException { boolean writeInterop = this.isWritable(node, JacORBSubsystemDefinitions.INTEROP_ATTRIBUTES); if (writeInterop) { writer.writeEmptyElement(JacORBSubsystemConstants.INTEROP); this.writeAttributes(writer, node, JacORBSubsystemDefinitions.INTEROP_ATTRIBUTES); } } /** * <p> * Writes the {@code security} section of the JacORB subsystem configuration using the contents of the provided * {@code ModelNode}. * </p> * * @param writer the {@code XMLExtendedStreamWriter} used to write the configuration XML. * @param node the {@code ModelNode} that contains the security configuration properties. * @throws XMLStreamException if an error occurs while writing the security configuration. */ private void writeSecurityConfig(XMLExtendedStreamWriter writer, ModelNode node) throws XMLStreamException { boolean writeSecurity = this.isWritable(node, JacORBSubsystemDefinitions.SECURITY_ATTRIBUTES); if (writeSecurity) { writer.writeEmptyElement(SECURITY); this.writeAttributes(writer, node, JacORBSubsystemDefinitions.SECURITY_ATTRIBUTES); } } /** * <p> * Writes a {@code property} element for each generic property contained in the specified {@code ModelNode}. * </p> * * @param writer the {@code XMLExtendedStreamWriter} used to write the configuration XML. * @param node the {@code ModelNode} that contains all properties to be written. * @throws javax.xml.stream.XMLStreamException * if an error occurs while writing the property elements. */ private void writeGenericProperties(XMLExtendedStreamWriter writer, ModelNode node) throws XMLStreamException { writer.writeStartElement(JacORBSubsystemConstants.PROPERTIES); for (Property prop : node.asPropertyList()) { writer.writeEmptyElement(JacORBSubsystemConstants.PROPERTY); writer.writeAttribute(JacORBSubsystemConstants.NAME, prop.getName()); writer.writeAttribute(JacORBSubsystemConstants.PROPERTY_VALUE, prop.getValue().asString()); } writer.writeEndElement(); } /** * <p> * Writes the attributes contained in the specified {@code ModelNode} to the current element. * </p> * * @param writer the {@code XMLExtendedStreamWriter} used to write the configuration XML. * @param node the {@code ModelNode} that contains the attributes to be written. * @param attributes the list of attributes to be written if they have been defined. * @throws XMLStreamException if an error occurs while writing the attributes to the current element. */ private void writeAttributes(XMLExtendedStreamWriter writer, ModelNode node, List<SimpleAttributeDefinition> attributes) throws XMLStreamException { for (SimpleAttributeDefinition definition : attributes) definition.marshallAsAttribute(node, writer); } /** * <p> * Iterates through the specified attribute definitions and checks if any of the attributes can be written to XML * by verifying if the attribute has been defined in the supplied node. * </p> * * @param node the {@code ModelNode} that contains the configuration attributes. * @param attributeDefinitions the {@code AttributeDefinition}s of the attributes that might be writable. * @return {@code true} if the node has defined any of the attributes; {@code false} otherwise. */ private boolean isWritable(ModelNode node, List<SimpleAttributeDefinition> attributeDefinitions) { boolean isWritable = false; for (SimpleAttributeDefinition attributeDefinition : attributeDefinitions) { if (attributeDefinition.isMarshallable(node)) { isWritable = true; break; } } return isWritable; } // helper enum types that encapsulate the subsystem namespace, elements, and attributes. /** * <p> * Enumeration of available JacORB subsystem namespaces. * </p> * * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a> */ enum Namespace { UNKNOWN(null), JacORB_1_0("urn:jboss:domain:jacorb:1.0"), JacORB_1_1("urn:jboss:domain:jacorb:1.1"), JacORB_1_2("urn:jboss:domain:jacorb:1.2"), JacORB_1_3("urn:jboss:domain:jacorb:1.3"), JacORB_1_4("urn:jboss:domain:jacorb:1.4"), JacORB_2_0("urn:jboss:domain:jacorb:2.0"); static final Namespace CURRENT = JacORB_2_0; private final String namespaceURI; /** * <p> * {@code Namespace} constructor. Sets the namespace {@code URI}. * </p> * * @param namespaceURI a {@code String} representing the namespace {@code URI}. */ private Namespace(final String namespaceURI) { this.namespaceURI = namespaceURI; } /** * <p> * Obtains the {@code URI} of this namespace. * </p> * * @return a {@code String} representing the namespace {@code URI}. */ String getUriString() { return namespaceURI; } // a map that caches all available namespaces by URI. private static final Map<String, Namespace> MAP; static { final Map<String, Namespace> map = new HashMap<String, Namespace>(); for (final Namespace namespace : values()) { final String name = namespace.getUriString(); if (name != null) map.put(name, namespace); } MAP = map; } /** * <p> * Gets the {@code Namespace} identified by the specified {@code URI}. * </p> * * @param uri a {@code String} representing the namespace {@code URI}. * @return the {@code Namespace} identified by the {@code URI}. If no namespace can be found, the * {@code Namespace.UNKNOWN} type is returned. */ static Namespace forUri(final String uri) { final Namespace element = MAP.get(uri); return element == null ? UNKNOWN : element; } } /** * <p> * Enumeration of the JacORB subsystem XML configuration elements. * </p> * * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a> * @author <a href="mailto:tadamski@redhat.com">Tomasz Adamski</a> */ enum Element { UNKNOWN(null), // elements used to configure the ORB. ORB(JacORBSubsystemConstants.ORB), ORB_CONNECTION(JacORBSubsystemConstants.ORB_CONN), ORB_INITIALIZERS(JacORBSubsystemConstants.ORB_INIT), // elements used to configure the POA. POA(JacORBSubsystemConstants.POA), POA_REQUEST_PROC(JacORBSubsystemConstants.POA_RP), // elements used to configure the naming service, ORB interoperability and ORB security. NAMING(JacORBSubsystemConstants.NAMING), INTEROP(JacORBSubsystemConstants.INTEROP), SECURITY(JacORBSubsystemConstants.SECURITY), // elements used to configure generic properties. PROPERTIES(JacORBSubsystemConstants.PROPERTIES), PROPERTY(JacORBSubsystemConstants.PROPERTY), IOR_SETTINGS(JacORBSubsystemConstants.IOR_SETTINGS), IOR_TRANSPORT_CONFIG(JacORBSubsystemConstants.IOR_TRANSPORT_CONFIG), IOR_AS_CONTEXT(JacORBSubsystemConstants.IOR_AS_CONTEXT), IOR_SAS_CONTEXT(JacORBSubsystemConstants.IOR_SAS_CONTEXT); private final String name; /** * <p> * {@code Element} constructor. Sets the element name. * </p> * * @param name a {@code String} representing the local name of the element. */ Element(final String name) { this.name = name; } /** * <p> * Obtains the local name of this element. * </p> * * @return a {@code String} representing the element's local name. */ public String getLocalName() { return name; } // a map that caches all available elements by name. private static final Map<String, Element> MAP; static { final Map<String, Element> map = new HashMap<String, Element>(); for (Element element : values()) { final String name = element.getLocalName(); if (name != null) map.put(name, element); } MAP = map; } /** * <p> * Gets the {@code Element} identified by the specified name. * </p> * * @param localName a {@code String} representing the local name of the element. * @return the {@code Element} identified by the name. If no attribute can be found, the {@code Element.UNKNOWN} * type is returned. */ public static Element forName(String localName) { final Element element = MAP.get(localName); return element == null ? UNKNOWN : element; } } /** * <p> * Enumeration of the JacORB subsystem XML configuration attributes. * </p> * * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a> */ enum Attribute { UNKNOWN(null), // attributes of the orb element. NAME(JacORBSubsystemConstants.NAME), ORB_PRINT_VERSION(JacORBSubsystemConstants.ORB_PRINT_VERSION), ORB_USE_IMR(JacORBSubsystemConstants.ORB_USE_IMR), ORB_USE_BOM(JacORBSubsystemConstants.ORB_USE_BOM), ORB_CACHE_TYPECODES(JacORBSubsystemConstants.ORB_CACHE_TYPECODES), ORB_CACHE_POA_NAMES(JacORBSubsystemConstants.ORB_CACHE_POA_NAMES), ORB_GIOP_MINOR_VERSION(JacORBSubsystemConstants.ORB_GIOP_MINOR_VERSION), ORB_SOCKET_BINDING(JacORBSubsystemConstants.ORB_SOCKET_BINDING), ORB_SSL_SOCKET_BINDING(JacORBSubsystemConstants.ORB_SSL_SOCKET_BINDING), ORB_PERSISTENT_SERVER_ID(JacORBSubsystemConstants.ORB_PERSISTENT_SERVER_ID), // attributes of the connection element. ORB_CONN_RETRIES(JacORBSubsystemConstants.ORB_CONN_RETRIES), ORB_CONN_RETRY_INTERVAL(JacORBSubsystemConstants.ORB_CONN_RETRY_INTERVAL), ORB_CONN_CLIENT_TIMEOUT(JacORBSubsystemConstants.ORB_CONN_CLIENT_TIMEOUT), ORB_CONN_SERVER_TIMEOUT(JacORBSubsystemConstants.ORB_CONN_SERVER_TIMEOUT), ORB_CONN_MAX_SERVER_CONNECTIONS(JacORBSubsystemConstants.ORB_CONN_MAX_SERVER_CONNECTIONS), ORB_CONN_MAX_MANAGED_BUF_SIZE(JacORBSubsystemConstants.ORB_CONN_MAX_MANAGED_BUF_SIZE), ORB_CONN_OUTBUF_SIZE(JacORBSubsystemConstants.ORB_CONN_OUTBUF_SIZE), ORB_CONN_OUTBUF_CACHE_TIMEOUT(JacORBSubsystemConstants.ORB_CONN_OUTBUF_CACHE_TIMEOUT), // attributes of the initializers element. ORB_INIT_SECURITY(JacORBSubsystemConstants.ORB_INIT_SECURITY), ORB_INIT_TRANSACTIONS(JacORBSubsystemConstants.ORB_INIT_TRANSACTIONS), // attributes of the poa element. POA_MONITORING(JacORBSubsystemConstants.POA_MONITORING), POA_QUEUE_WAIT(JacORBSubsystemConstants.POA_QUEUE_WAIT), POA_QUEUE_MIN(JacORBSubsystemConstants.POA_QUEUE_MIN), POA_QUEUE_MAX(JacORBSubsystemConstants.POA_QUEUE_MAX), // attributes of the request-processor element. POA_REQUEST_PROC_POOL_SIZE(JacORBSubsystemConstants.POA_RP_POOL_SIZE), POA_REQUEST_PROC_MAX_THREADS(JacORBSubsystemConstants.POA_RP_MAX_THREADS), // attributes of the naming element - the ORB service will build the relevant JacORB properties from these values. NAMING_EXPORT_CORBALOC(JacORBSubsystemConstants.NAMING_EXPORT_CORBALOC), NAMING_ROOT_CONTEXT(JacORBSubsystemConstants.NAMING_ROOT_CONTEXT), // attributes of the interop element. INTEROP_SUN(JacORBSubsystemConstants.INTEROP_SUN), INTEROP_COMET(JacORBSubsystemConstants.INTEROP_COMET), INTEROP_IONA(JacORBSubsystemConstants.INTEROP_IONA), INTEROP_CHUNK_RMI_VALUETYPES(JacORBSubsystemConstants.INTEROP_CHUNK_RMI_VALUETYPES), INTEROP_LAX_BOOLEAN_ENCODING(JacORBSubsystemConstants.INTEROP_LAX_BOOLEAN_ENCODING), INTEROP_INDIRECTION_ENCODING_DISABLE(JacORBSubsystemConstants.INTEROP_INDIRECTION_ENCODING_DISABLE), INTEROP_STRICT_CHECK_ON_TC_CREATION(JacORBSubsystemConstants.INTEROP_STRICT_CHECK_ON_TC_CREATION), // attributes of the security element. SECURITY_SUPPORT_SSL(JacORBSubsystemConstants.SECURITY_SUPPORT_SSL), SECURITY_SECURITY_DOMAIN(JacORBSubsystemConstants.SECURITY_SECURITY_DOMAIN), SECURITY_ADD_COMPONENT_INTERCEPTOR(JacORBSubsystemConstants.SECURITY_ADD_COMP_VIA_INTERCEPTOR), SECURITY_CLIENT_SUPPORTS(JacORBSubsystemConstants.SECURITY_CLIENT_SUPPORTS), SECURITY_CLIENT_REQUIRES(JacORBSubsystemConstants.SECURITY_CLIENT_REQUIRES), SECURITY_SERVER_SUPPORTS(JacORBSubsystemConstants.SECURITY_SERVER_SUPPORTS), SECURITY_SERVER_REQUIRES(JacORBSubsystemConstants.SECURITY_SERVER_REQUIRES), // if enabled the ORB service will configure JacORB to use the JBoss SSL socket factory classes by building the // appropriate properties. SECURITY_USE_DOMAIN_SF(JacORBSubsystemConstants.SECURITY_USE_DOMAIN_SF), SECURITY_USE_DOMAIN_SSF(JacORBSubsystemConstants.SECURITY_USE_DOMAIN_SSF), IOR_TRANSPORT_CONFIDENTIALITY(JacORBSubsystemConstants.IOR_TRANSPORT_CONFIDENTIALITY), IOR_TRANSPORT_DETECT_MISORDERING(JacORBSubsystemConstants.IOR_TRANSPORT_DETECT_MISORDERING), IOR_TRANSPORT_DETECT_REPLAY(JacORBSubsystemConstants.IOR_TRANSPORT_DETECT_REPLAY), IOR_TRANSPORT_INTEGRITY(JacORBSubsystemConstants.IOR_TRANSPORT_INTEGRITY), IOR_TRANSPORT_TRUST_IN_CLIENT(JacORBSubsystemConstants.IOR_TRANSPORT_TRUST_IN_CLIENT), IOR_TRANSPORT_TRUST_IN_TARGET(JacORBSubsystemConstants.IOR_TRANSPORT_TRUST_IN_TARGET), IOR_AS_CONTEXT_AUTH_METHOD(JacORBSubsystemConstants.IOR_AS_CONTEXT_AUTH_METHOD), IOR_AS_CONTEXT_REALM(JacORBSubsystemConstants.IOR_AS_CONTEXT_REALM), IOR_AS_CONTEXT_REQUIRED(JacORBSubsystemConstants.IOR_AS_CONTEXT_REQUIRED), IOR_SAS_CONTEXT_CALLER_PROPAGATION(JacORBSubsystemConstants.IOR_SAS_CONTEXT_CALLER_PROPAGATION), // attributes of the generic property element. PROP_KEY(JacORBSubsystemConstants.PROPERTY_KEY), PROP_VALUE(JacORBSubsystemConstants.PROPERTY_VALUE); private final String name; /** * <p> * {@code Attribute} constructor. Sets the attribute name. * </p> * * @param name a {@code String} representing the local name of the attribute. */ Attribute(final String name) { this.name = name; } /** * <p> * Obtains the local name of this attribute. * </p> * * @return a {@code String} representing the attribute local name. */ public String getLocalName() { return this.name; } // a map that caches all available attributes by name. private static final Map<String, Attribute> MAP; static { final Map<String, Attribute> map = new HashMap<String, Attribute>(); for (Attribute attribute : values()) { final String name = attribute.name; if (name != null) map.put(name, attribute); } MAP = map; } /** * <p> * Gets the {@code Attribute} identified by the specified name. * </p> * * @param localName a {@code String} representing the local name of the attribute. * @return the {@code Attribute} identified by the name. If no attribute can be found, the {@code Attribute.UNKNOWN} * type is returned. */ public static Attribute forName(String localName) { final Attribute attribute = MAP.get(localName); return attribute == null ? UNKNOWN : attribute; } } }