/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, 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.appclient.subsystem.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.NAME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SOCKET_BINDING_GROUP;
import static org.jboss.as.controller.parsing.Namespace.DOMAIN_1_0;
import static org.jboss.as.controller.parsing.ParseUtils.isNoNamespaceAttribute;
import static org.jboss.as.controller.parsing.ParseUtils.missingRequired;
import static org.jboss.as.controller.parsing.ParseUtils.nextElement;
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.unexpectedAttribute;
import static org.jboss.as.controller.parsing.ParseUtils.unexpectedElement;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.xml.stream.XMLStreamException;
import org.jboss.as.appclient.logging.AppClientLogger;
import org.jboss.as.controller.extension.ExtensionRegistry;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.controller.parsing.Attribute;
import org.jboss.as.controller.parsing.Element;
import org.jboss.as.controller.parsing.ExtensionXml;
import org.jboss.as.controller.parsing.Namespace;
import org.jboss.as.controller.parsing.ParseUtils;
import org.jboss.as.controller.persistence.ModelMarshallingContext;
import org.jboss.as.server.parsing.CommonXml;
import org.jboss.as.server.parsing.SocketBindingsXml;
import org.jboss.as.server.services.net.SocketBindingGroupResourceDefinition;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
import org.jboss.modules.ModuleLoader;
import org.jboss.staxmapper.XMLExtendedStreamReader;
import org.jboss.staxmapper.XMLExtendedStreamWriter;
/**
* A mapper between an AS server's configuration model and XML representations, particularly {@code appclient.xml}.
*
* @author Stuart Douglas
*/
public class AppClientXml extends CommonXml {
private final ExtensionXml extensionXml;
public AppClientXml(final ModuleLoader loader, final ExtensionRegistry extensionRegistry) {
super(new AppClientSocketBindingsXml());
extensionXml = new ExtensionXml(loader, null, extensionRegistry);
}
public void readElement(final XMLExtendedStreamReader reader, final List<ModelNode> operationList)
throws XMLStreamException {
final ModelNode address = new ModelNode().setEmptyList();
if (Element.forName(reader.getLocalName()) != Element.SERVER) {
throw unexpectedElement(reader);
}
Namespace readerNS = Namespace.forUri(reader.getNamespaceURI());
switch (readerNS) {
case DOMAIN_1_0:
readServerElement_1_0(reader, address, operationList);
break;
default:
// Instead of having to list the remaining versions we just check it is actually a valid version.
for (Namespace current : Namespace.domainValues()) {
if (readerNS.equals(current)) {
readServerElement_1_1(readerNS, reader, address, operationList);
return;
}
}
throw unexpectedElement(reader);
}
}
/**
* Read the <server/> element based on version 1.0 of the schema.
*
* @param reader the xml stream reader
* @param address address of the parent resource of any resources this method will add
* @param list the list of boot operations to which any new operations should be added
* @throws XMLStreamException if a parsing error occurs
*/
private void readServerElement_1_0(final XMLExtendedStreamReader reader, final ModelNode address, final List<ModelNode> list)
throws XMLStreamException {
parseNamespaces(reader, address, list);
String serverName = null;
// attributes
final int count = reader.getAttributeCount();
for (int i = 0; i < count; i++) {
switch (Namespace.forUri(reader.getAttributeNamespace(i))) {
case NONE: {
final String value = reader.getAttributeValue(i);
final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
switch (attribute) {
case NAME: {
serverName = value;
break;
}
default:
throw unexpectedAttribute(reader, i);
}
break;
}
case XML_SCHEMA_INSTANCE: {
switch (Attribute.forName(reader.getAttributeLocalName(i))) {
case SCHEMA_LOCATION: {
parseSchemaLocations(reader, address, list, i);
break;
}
case NO_NAMESPACE_SCHEMA_LOCATION: {
// todo, jeez
break;
}
default: {
throw unexpectedAttribute(reader, i);
}
}
break;
}
default:
throw unexpectedAttribute(reader, i);
}
}
setServerName(address, list, serverName);
// elements - sequence
Element element = nextElement(reader, DOMAIN_1_0);
if (element == Element.EXTENSIONS) {
extensionXml.parseExtensions(reader, address, DOMAIN_1_0, list);
element = nextElement(reader, DOMAIN_1_0);
}
// System properties
if (element == Element.SYSTEM_PROPERTIES) {
parseSystemProperties(reader, address, DOMAIN_1_0, list, true);
element = nextElement(reader, DOMAIN_1_0);
}
if (element == Element.PATHS) {
parsePaths(reader, address, DOMAIN_1_0, list, true);
element = nextElement(reader, DOMAIN_1_0);
}
// Single profile
if (element == Element.PROFILE) {
parseServerProfile(reader, address, list);
element = nextElement(reader, DOMAIN_1_0);
}
// Interfaces
final Set<String> interfaceNames = new HashSet<String>();
if (element == Element.INTERFACES) {
parseInterfaces(reader, interfaceNames, address, DOMAIN_1_0, list, true);
element = nextElement(reader, DOMAIN_1_0);
}
// Single socket binding group
if (element == Element.SOCKET_BINDING_GROUP) {
parseSocketBindingGroup(reader, interfaceNames, address, DOMAIN_1_0, list);
element = nextElement(reader, DOMAIN_1_0);
}
if (element != null) {
throw unexpectedElement(reader);
}
}
/**
* Read the <server/> element based on version 1.1 of the schema.
*
* @param reader the xml stream reader
* @param address address of the parent resource of any resources this method will add
* @param list the list of boot operations to which any new operations should be added
* @throws XMLStreamException if a parsing error occurs
*/
private void readServerElement_1_1(final Namespace namespace, final XMLExtendedStreamReader reader, final ModelNode address, final List<ModelNode> list)
throws XMLStreamException {
parseNamespaces(reader, address, list);
String serverName = null;
// attributes
final int count = reader.getAttributeCount();
for (int i = 0; i < count; i++) {
switch (Namespace.forUri(reader.getAttributeNamespace(i))) {
case NONE: {
final String value = reader.getAttributeValue(i);
final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
switch (attribute) {
case NAME: {
serverName = value;
break;
}
default:
throw unexpectedAttribute(reader, i);
}
break;
}
case XML_SCHEMA_INSTANCE: {
switch (Attribute.forName(reader.getAttributeLocalName(i))) {
case SCHEMA_LOCATION: {
parseSchemaLocations(reader, address, list, i);
break;
}
case NO_NAMESPACE_SCHEMA_LOCATION: {
// todo, jeez
break;
}
default: {
throw unexpectedAttribute(reader, i);
}
}
break;
}
default:
throw unexpectedAttribute(reader, i);
}
}
setServerName(address, list, serverName);
// elements - sequence
Element element = nextElement(reader, namespace);
if (element == Element.EXTENSIONS) {
extensionXml.parseExtensions(reader, address, namespace, list);
element = nextElement(reader, namespace);
}
// System properties
if (element == Element.SYSTEM_PROPERTIES) {
parseSystemProperties(reader, address, namespace, list, true);
element = nextElement(reader, namespace);
}
if (element == Element.PATHS) {
parsePaths(reader, address, namespace, list, true);
element = nextElement(reader, namespace);
}
if (element == Element.VAULT) {
parseVault(reader, address, namespace, list);
element = nextElement(reader, namespace);
}
// Single profile
if (element == Element.PROFILE) {
parseServerProfile(reader, address, list);
element = nextElement(reader, namespace);
}
// Interfaces
final Set<String> interfaceNames = new HashSet<String>();
if (element == Element.INTERFACES) {
parseInterfaces(reader, interfaceNames, address, namespace, list, true);
element = nextElement(reader, namespace);
}
// Single socket binding group
if (element == Element.SOCKET_BINDING_GROUP) {
parseSocketBindingGroup(reader, interfaceNames, address, namespace, list);
element = nextElement(reader, namespace);
}
if (element != null) {
throw unexpectedElement(reader);
}
}
private void parseSocketBindingGroup(final XMLExtendedStreamReader reader, final Set<String> interfaces,
final ModelNode address, final Namespace expectedNs, final List<ModelNode> updates) throws XMLStreamException {
// unique names for both socket-binding and outbound-socket-binding(s)
final Set<String> uniqueBindingNames = new HashSet<String>();
ModelNode op = Util.getEmptyOperation(ADD, null);
// Handle attributes
String socketBindingGroupName = null;
final EnumSet<Attribute> required = EnumSet.of(Attribute.NAME, Attribute.DEFAULT_INTERFACE);
final int count = reader.getAttributeCount();
for (int i = 0; i < count; i++) {
final String value = reader.getAttributeValue(i);
if (!isNoNamespaceAttribute(reader, i)) {
throw unexpectedAttribute(reader, i);
}
final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
switch (attribute) {
case NAME: {
socketBindingGroupName = value;
required.remove(attribute);
break;
}
case DEFAULT_INTERFACE: {
SocketBindingGroupResourceDefinition.DEFAULT_INTERFACE.parseAndSetParameter(value, op, reader);
required.remove(attribute);
break;
}
case PORT_OFFSET: {
SocketBindingGroupResourceDefinition.PORT_OFFSET.parseAndSetParameter(value, op, reader);
break;
}
default:
throw ParseUtils.unexpectedAttribute(reader, i);
}
}
if (!required.isEmpty()) {
throw missingRequired(reader, required);
}
ModelNode groupAddress = address.clone().add(SOCKET_BINDING_GROUP, socketBindingGroupName);
op.get(OP_ADDR).set(groupAddress);
updates.add(op);
// Handle elements
while (reader.nextTag() != END_ELEMENT) {
requireNamespace(reader, expectedNs);
final Element element = Element.forName(reader.getLocalName());
switch (element) {
case SOCKET_BINDING: {
// FIXME JBAS-8825
final String bindingName = parseSocketBinding(reader, interfaces, groupAddress, updates);
if (!uniqueBindingNames.add(bindingName)) {
throw ControllerLogger.ROOT_LOGGER.alreadyDeclared(Element.SOCKET_BINDING.getLocalName(), Element.OUTBOUND_SOCKET_BINDING.getLocalName(), bindingName, Element.SOCKET_BINDING_GROUP.getLocalName(), socketBindingGroupName, reader.getLocation());
}
break;
}
case OUTBOUND_SOCKET_BINDING: {
final String bindingName = parseOutboundSocketBinding(reader, interfaces, groupAddress, updates);
if (!uniqueBindingNames.add(bindingName)) {
throw ControllerLogger.ROOT_LOGGER.alreadyDeclared(Element.SOCKET_BINDING.getLocalName(), Element.OUTBOUND_SOCKET_BINDING.getLocalName(), bindingName, Element.SOCKET_BINDING_GROUP.getLocalName(), socketBindingGroupName, reader.getLocation());
}
break;
}
default:
throw unexpectedElement(reader);
}
}
}
private void parseServerProfile(final XMLExtendedStreamReader reader, final ModelNode address, final List<ModelNode> list)
throws XMLStreamException {
// Attributes
requireNoAttributes(reader);
// Content
final Set<String> configuredSubsystemTypes = new HashSet<String>();
while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
if (Element.forName(reader.getLocalName()) != Element.SUBSYSTEM) {
throw unexpectedElement(reader);
}
if (!configuredSubsystemTypes.add(reader.getNamespaceURI())) {
throw AppClientLogger.ROOT_LOGGER.duplicateSubsystemDeclaration(reader.getLocation());
}
// parse subsystem
final List<ModelNode> subsystems = new ArrayList<ModelNode>();
reader.handleAny(subsystems);
// Process subsystems
for (final ModelNode update : subsystems) {
// Process relative subsystem path address
final ModelNode subsystemAddress = address.clone();
for (final Property path : update.get(OP_ADDR).asPropertyList()) {
subsystemAddress.add(path.getName(), path.getValue().asString());
}
update.get(OP_ADDR).set(subsystemAddress);
list.add(update);
}
}
}
private void setServerName(final ModelNode address, final List<ModelNode> operationList, final String value) {
if (value != null && value.length() > 0) {
final ModelNode update = Util.getWriteAttributeOperation(address, NAME, value);
operationList.add(update);
}
}
public void writeContent(final XMLExtendedStreamWriter writer, final ModelMarshallingContext context)
throws XMLStreamException {
// we don't marshall appclient.xml
}
static class AppClientSocketBindingsXml extends SocketBindingsXml {
@Override
protected void writeExtraAttributes(XMLExtendedStreamWriter writer, ModelNode bindingGroup) throws XMLStreamException {
SocketBindingGroupResourceDefinition.PORT_OFFSET.marshallAsAttribute(bindingGroup, writer);
}
}
}