/*
 * 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.domain.management.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.APPLICATION_CLASSIFICATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CLASSIFICATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CONSTRAINT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.EXCLUDE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.GROUP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST_SCOPED_ROLE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST_SCOPED_ROLES;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.INCLUDE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME;
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.REALM;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ROLE_MAPPING;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SENSITIVITY_CLASSIFICATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER_GROUP_SCOPED_ROLE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.TYPE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.USER;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.VAULT_EXPRESSION;
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.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.unexpectedAttribute;
import static org.jboss.as.controller.parsing.ParseUtils.unexpectedElement;
import static org.jboss.as.controller.parsing.WriteUtils.writeAttribute;

import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import javax.xml.stream.XMLStreamException;

import org.jboss.as.controller.ListAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinition;
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.Namespace;
import org.jboss.as.controller.parsing.ParseUtils;
import org.jboss.as.domain.management.access.AccessAuthorizationResourceDefinition;
import org.jboss.as.domain.management.access.ApplicationClassificationConfigResourceDefinition;
import org.jboss.as.domain.management.access.ApplicationClassificationTypeResourceDefinition;
import org.jboss.as.domain.management.access.HostScopedRolesResourceDefinition;
import org.jboss.as.domain.management.access.PrincipalResourceDefinition;
import org.jboss.as.domain.management.access.RoleMappingResourceDefinition;
import org.jboss.as.domain.management.access.SensitivityClassificationTypeResourceDefinition;
import org.jboss.as.domain.management.access.SensitivityResourceDefinition;
import org.jboss.as.domain.management.access.ServerGroupScopedRoleResourceDefinition;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
import org.jboss.staxmapper.XMLExtendedStreamReader;
import org.jboss.staxmapper.XMLExtendedStreamWriter;

/**
 * Bits of parsing and marshaling logic that are related to {@code <access-control>} elements.
 *
 * @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
 */
public class AccessControlXml {

    private final Namespace namespace;

    private AccessControlXml(final Namespace namespace) {
        this.namespace = namespace;
    }

    public static AccessControlXml newInstance(Namespace namespace) {
        return new AccessControlXml(namespace);
    }

    public void parseAccessControlConstraints(final XMLExtendedStreamReader reader, final ModelNode accAuthzAddr, final List<ModelNode> list) throws XMLStreamException {
        ParseUtils.requireNoAttributes(reader);

        while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
            requireNamespace(reader, namespace);
            final Element element = Element.forName(reader.getLocalName());
            switch (element) {
                case VAULT_EXPRESSION_SENSITIVITY: {
                    ModelNode vaultAddr = accAuthzAddr.clone().add(CONSTRAINT, VAULT_EXPRESSION);
                    parseClassificationType(reader, vaultAddr, list, true);
                    break;
                }
                case SENSITIVE_CLASSIFICATIONS: {
                    ModelNode sensAddr = accAuthzAddr.clone().add(CONSTRAINT, SENSITIVITY_CLASSIFICATION);
                    parseSensitiveClassifications(reader, sensAddr, list);
                    break;
                }
                case APPLICATION_CLASSIFICATIONS: {
                    ModelNode applAddr = accAuthzAddr.clone().add(CONSTRAINT, APPLICATION_CLASSIFICATION);
                    parseApplicationClassifications(reader, applAddr, list);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }
    }

    public void parseAccessControlRoleMapping(final XMLExtendedStreamReader reader, final ModelNode accContAddr,
            final List<ModelNode> list) throws XMLStreamException {
        requireNoAttributes(reader);

        while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
            requireNamespace(reader, namespace);
            final Element element = Element.forName(reader.getLocalName());
            if (element == Element.ROLE) {
                parseRole(reader, accContAddr, list);
            } else {
                throw unexpectedElement(reader);
            }
        }
    }

    public void parseServerGroupScopedRoles(final XMLExtendedStreamReader reader, final ModelNode address, final List<ModelNode> list) throws XMLStreamException {

        ParseUtils.requireNoAttributes(reader);

        String scopedRoleType = ServerGroupScopedRoleResourceDefinition.PATH_ELEMENT.getKey();

        while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
            requireNamespace(reader, namespace);
            final Element element = Element.forName(reader.getLocalName());
            switch (element) {
                case ROLE: {
                    parseScopedRole(reader, address, list, scopedRoleType, Element.SERVER_GROUP,
                            ServerGroupScopedRoleResourceDefinition.BASE_ROLE, ServerGroupScopedRoleResourceDefinition.SERVER_GROUPS, true);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }
    }

    public void parseHostScopedRoles(final XMLExtendedStreamReader reader, final ModelNode address, final List<ModelNode> list) throws XMLStreamException {

        ParseUtils.requireNoAttributes(reader);

        String scopedRoleType = HostScopedRolesResourceDefinition.PATH_ELEMENT.getKey();

        while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
            requireNamespace(reader, namespace);
            final Element element = Element.forName(reader.getLocalName());
            switch (element) {
                case ROLE: {
                    parseScopedRole(reader, address, list, scopedRoleType, Element.HOST,
                            HostScopedRolesResourceDefinition.BASE_ROLE, HostScopedRolesResourceDefinition.HOSTS, false);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }

    }

    private void parseScopedRole(XMLExtendedStreamReader reader, ModelNode address,
                                 List<ModelNode> ops, String scopedRoleType, final Element listElement,
                                 SimpleAttributeDefinition baseRoleDefinition, ListAttributeDefinition listDefinition,
                                 boolean requireChildren) throws XMLStreamException {

        final ModelNode addOp = Util.createAddOperation();
        ops.add(addOp);
        final ModelNode ourAddress = addOp.get(OP_ADDR).set(address);
        final Set<Attribute> required = EnumSet.of(Attribute.NAME, Attribute.BASE_ROLE);
        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));
            required.remove(attribute);
            switch (attribute) {
                case NAME:
                    ourAddress.add(scopedRoleType, value);
                    break;
                case BASE_ROLE:
                    baseRoleDefinition.parseAndSetParameter(value, addOp, reader);
                    break;
                default: {
                    throw unexpectedAttribute(reader, i);
                }
            }
        }

        if (!required.isEmpty()) {
            throw missingRequired(reader, required);
        }

        boolean missingChildren = requireChildren;
        while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
            boolean named = false;
            requireNamespace(reader, namespace);
            final Element element = Element.forName(reader.getLocalName());
            if (element == listElement) {
                missingChildren = false;
                final int groupCount = reader.getAttributeCount();
                for (int i = 0; i < groupCount; i++) {
                    final String value = reader.getAttributeValue(i);
                    if (!isNoNamespaceAttribute(reader, i)) {
                        throw unexpectedAttribute(reader, i);
                    }
                    final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
                    required.remove(attribute);
                    if (attribute == Attribute.NAME) {
                        named = true;
                        listDefinition.parseAndAddParameterElement(value, addOp, reader);
                    } else {
                        throw unexpectedAttribute(reader, i);
                    }
                }
            } else {
                throw unexpectedElement(reader);
            }

            if (!named) {
                throw missingRequired(reader, EnumSet.of(Attribute.NAME));
            }

            requireNoContent(reader);
        }

        if (missingChildren) {
            throw missingRequired(reader, EnumSet.of(listElement));
        }
    }

    private void parseRole(final XMLExtendedStreamReader reader, final ModelNode address,
            final List<ModelNode> list) throws XMLStreamException {
        final ModelNode add = new ModelNode();
        list.add(add);
        String name = null;

        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);
            } else {
                final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
                switch (attribute) {
                    case NAME:
                        name = value;
                        break;
                    case INCLUDE_ALL: {
                        RoleMappingResourceDefinition.INCLUDE_ALL.parseAndSetParameter(value, add, reader);
                        break;
                    }
                    default: {
                        throw unexpectedAttribute(reader, i);
                    }
                }
            }
        }

        if (name == null) {
            throw missingRequired(reader, Collections.singleton(Attribute.NAME));
        }

        ModelNode addr = address.clone().add(ROLE_MAPPING, name);
        add.get(OP_ADDR).set(addr);
        add.get(OP).set(ADD);

        while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
            requireNamespace(reader, namespace);
            final Element element = Element.forName(reader.getLocalName());
            switch (element) {
                case INCLUDE: {
                    parseIncludeExclude(reader, addr, INCLUDE, list);
                    break;
                }
                case EXCLUDE: {
                    parseIncludeExclude(reader, addr, EXCLUDE, list);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }
    }

    private void parseIncludeExclude(final XMLExtendedStreamReader reader, final ModelNode parentAddress, final String incExcType,
            final List<ModelNode> list) throws XMLStreamException {
        ParseUtils.requireNoAttributes(reader);

        while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
            requireNamespace(reader, namespace);
            final Element element = Element.forName(reader.getLocalName());
            switch (element) {
                case GROUP: {
                    parsePrincipal(reader, parentAddress, incExcType, GROUP, list);
                    break;
                }
                case USER: {
                    parsePrincipal(reader, parentAddress, incExcType, USER, list);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }
    }

    private void parsePrincipal(final XMLExtendedStreamReader reader, final ModelNode parentAddress, final String incExcType, final String principalType,
            final List<ModelNode> list) throws XMLStreamException {
        String alias = null;
        String realm = null;
        String name = null;

        ModelNode addOp = new ModelNode();
        addOp.get(OP).set(ADD);
        addOp.get(TYPE).set(principalType);

        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);
            } else {
                final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
                switch (attribute) {
                    case ALIAS: {
                        alias = value;
                        break;
                    }
                    case NAME: {
                        name = value;
                        PrincipalResourceDefinition.NAME.parseAndSetParameter(value, addOp, reader);
                        break;
                    }
                    case REALM: {
                        realm = value;
                        PrincipalResourceDefinition.REALM.parseAndSetParameter(value, addOp, reader);
                        break;
                    }
                    default: {
                        throw unexpectedAttribute(reader, i);
                    }
                }
            }
        }

        if (name == null) {
            throw ParseUtils.missingRequired(reader, Collections.singleton(Attribute.NAME));
        }

        String addrValue = alias == null ? generateAlias(principalType, name, realm) : alias;
        ModelNode addAddr = parentAddress.clone().add(incExcType, addrValue);
        addOp.get(OP_ADDR).set(addAddr);
        list.add(addOp);

        ParseUtils.requireNoContent(reader);
    }

    static String generateAlias(final String type, final String name, final String realm) {
        return type + "-" + name + (realm != null ? "@" + realm : "");
    }

    private void parseSensitiveClassifications(final XMLExtendedStreamReader reader, final ModelNode address,
            final List<ModelNode> list) throws XMLStreamException {

        ParseUtils.requireNoAttributes(reader);

        while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
            requireNamespace(reader, namespace);
            final Element element = Element.forName(reader.getLocalName());
            switch (element) {
                case SENSITIVE_CLASSIFICATION: {
                    parseSensitivityClassification(reader, address, list);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }
    }

    private void parseClassificationType(final XMLExtendedStreamReader reader, final ModelNode address,
            final List<ModelNode> list, boolean vault) throws XMLStreamException {
        final int count = reader.getAttributeCount();
        String name = null;
        String type = null;
        Map<String, ModelNode> values = new HashMap<String, ModelNode>();
        for (int i = 0; i < count; i++) {
            final String value = reader.getAttributeValue(i);
            if (!isNoNamespaceAttribute(reader, i)) {
                throw unexpectedAttribute(reader, i);
            } else {
                final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
                switch (attribute) {
                    case NAME: {
                        name = value;
                        break;
                    }
                    case TYPE: {
                        type = value;
                        break;
                    }
                    case REQUIRES_READ: {
                        values.put(SensitivityResourceDefinition.CONFIGURED_REQUIRES_READ.getName(),
                                SensitivityResourceDefinition.CONFIGURED_REQUIRES_READ.parse(value, reader));
                        break;
                    }
                    case REQUIRES_WRITE: {
                        values.put(SensitivityResourceDefinition.CONFIGURED_REQUIRES_WRITE.getName(),
                                SensitivityResourceDefinition.CONFIGURED_REQUIRES_WRITE.parse(value, reader));
                        break;
                    }
                    case REQUIRES_ADDRESSABLE: {
                        if (!vault) {
                            values.put(SensitivityResourceDefinition.CONFIGURED_REQUIRES_ADDRESSABLE.getName(),
                                SensitivityResourceDefinition.CONFIGURED_REQUIRES_ADDRESSABLE.parse(value, reader));
                            break;
                        }
                    }
                    default: {
                        throw unexpectedAttribute(reader, i);
                    }
                }
            }
        }

        if (name == null && !vault) {
            throw ParseUtils.missingRequired(reader, Collections.singleton(Attribute.NAME));
        }

        if (type == null && !vault) {
            throw ParseUtils.missingRequired(reader, Collections.singleton(Attribute.TYPE));
        }

        final ModelNode newAddress = vault ? address :
            address.clone()
            .add(SensitivityClassificationTypeResourceDefinition.PATH_ELEMENT.getKey(), type)
            .add(SensitivityResourceDefinition.PATH_ELEMENT.getKey(), name);

        for (Map.Entry<String, ModelNode> entry : values.entrySet()) {
            list.add(Util.getWriteAttributeOperation(newAddress, entry.getKey(), entry.getValue()));
        }
        ParseUtils.requireNoContent(reader);
    }


    private void parseApplicationClassifications(final XMLExtendedStreamReader reader, final ModelNode address,
            final List<ModelNode> list) throws XMLStreamException {

        ParseUtils.requireNoAttributes(reader);

        while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
            requireNamespace(reader, namespace);
            final Element element = Element.forName(reader.getLocalName());
            switch (element) {
                case APPLICATION_CLASSIFICATION: {
                    parseApplicationClassification(reader, address, list);
                    break;
                }
                default: {
                    throw unexpectedElement(reader);
                }
            }
        }
    }

    private void parseApplicationClassification(final XMLExtendedStreamReader reader, final ModelNode address,
            final List<ModelNode> list) throws XMLStreamException {
        String name = null;
        String type = null;
        Boolean applicationValue = null;
        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);
            } else {
                final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
                switch (attribute) {
                    case NAME:
                        name = value;
                        break;
                    case TYPE:
                        type = value;
                        break;
                    case APPLICATION:
                        applicationValue = Boolean.valueOf(value);
                        break;
                    default: {
                        throw unexpectedAttribute(reader, i);
                    }
                }
            }
        }

        if (name == null) {
            throw ParseUtils.missingRequired(reader, Collections.singleton(NAME));
        }
        if (type == null) {
            throw ParseUtils.missingRequired(reader, Collections.singleton(Attribute.TYPE));
        }
        if (applicationValue == null) {
            throw ParseUtils.missingRequired(reader, Collections.singleton(Attribute.APPLICATION));
        }

        ModelNode newAddress = address.clone()
                .add(ApplicationClassificationTypeResourceDefinition.PATH_ELEMENT.getKey(), type)
                .add(ApplicationClassificationConfigResourceDefinition.PATH_ELEMENT.getKey(), name);


        list.add(Util.getWriteAttributeOperation(newAddress, ApplicationClassificationConfigResourceDefinition.CONFIGURED_APPLICATION.getName(), applicationValue.toString()));
        ParseUtils.requireNoContent(reader);
    }

    private void parseSensitivityClassification(final XMLExtendedStreamReader reader, final ModelNode address,
            final List<ModelNode> list) throws XMLStreamException {
        parseClassificationType(reader, address, list, false);
    }

    public void writeAccessControl(final XMLExtendedStreamWriter writer, final ModelNode accessAuthorization) throws XMLStreamException {
        if (accessAuthorization == null || accessAuthorization.isDefined()==false) {
            return; // All subsequent checks are based on this being defined.
        }

        boolean hasServerGroupRoles =  accessAuthorization.hasDefined(SERVER_GROUP_SCOPED_ROLE);
        boolean hasHostRoles = accessAuthorization.hasDefined(HOST_SCOPED_ROLE) || accessAuthorization.hasDefined(HOST_SCOPED_ROLES);
        boolean hasRoleMapping = accessAuthorization.hasDefined(ROLE_MAPPING);
        Map<String, Map<String, Set<String>>> configuredAccessConstraints = getConfiguredAccessConstraints(accessAuthorization);
        boolean hasProvider = accessAuthorization.hasDefined(AccessAuthorizationResourceDefinition.PROVIDER.getName());
        boolean hasCombinationPolicy = accessAuthorization.hasDefined(AccessAuthorizationResourceDefinition.PERMISSION_COMBINATION_POLICY.getName());

        if (!hasProvider && !hasCombinationPolicy && !hasServerGroupRoles && !hasHostRoles
                && !hasRoleMapping && configuredAccessConstraints.size() == 0) {
            return;
        }

        writer.writeStartElement(Element.ACCESS_CONTROL.getLocalName());

        AccessAuthorizationResourceDefinition.PROVIDER.marshallAsAttribute(accessAuthorization, writer);
        AccessAuthorizationResourceDefinition.USE_IDENTITY_ROLES.marshallAsAttribute(accessAuthorization, writer);
        AccessAuthorizationResourceDefinition.PERMISSION_COMBINATION_POLICY.marshallAsAttribute(accessAuthorization, writer);

        if (hasServerGroupRoles) {
            ModelNode serverGroupRoles = accessAuthorization.get(SERVER_GROUP_SCOPED_ROLE);
            if (serverGroupRoles.asInt() > 0) {
                writeServerGroupScopedRoles(writer, serverGroupRoles);
            }
        }

        if (hasHostRoles) {
            ModelNode serverGroupRoles = accessAuthorization.get(HOST_SCOPED_ROLE);
            if (serverGroupRoles.asInt() > 0) {
                writeHostScopedRoles(writer, serverGroupRoles);
            }
        }

        if (hasRoleMapping) {
            writeRoleMapping(writer, accessAuthorization);
        }

        if (configuredAccessConstraints.size() > 0) {
            writeAccessConstraints(writer, accessAuthorization, configuredAccessConstraints);
        }

        writer.writeEndElement();
    }

    private void writeServerGroupScopedRoles(XMLExtendedStreamWriter writer, ModelNode scopedRoles) throws XMLStreamException {
        writer.writeStartElement(Element.SERVER_GROUP_SCOPED_ROLES.getLocalName());

        for (Property property : scopedRoles.asPropertyList()) {
            writer.writeStartElement(Element.ROLE.getLocalName());
            writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
            ModelNode value = property.getValue();
            ServerGroupScopedRoleResourceDefinition.BASE_ROLE.marshallAsAttribute(value, writer);
            ServerGroupScopedRoleResourceDefinition.SERVER_GROUPS.marshallAsElement(value, writer);
            writer.writeEndElement();
        }
        writer.writeEndElement();
    }

    private void writeHostScopedRoles(XMLExtendedStreamWriter writer, ModelNode scopedRoles) throws XMLStreamException {
        writer.writeStartElement(Element.HOST_SCOPED_ROLES.getLocalName());

        for (Property property : scopedRoles.asPropertyList()) {
            writer.writeStartElement(Element.ROLE.getLocalName());
            writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
            ModelNode value = property.getValue();
            HostScopedRolesResourceDefinition.BASE_ROLE.marshallAsAttribute(value, writer);
            HostScopedRolesResourceDefinition.HOSTS.marshallAsElement(value, writer);
            writer.writeEndElement();
        }
        writer.writeEndElement();
    }

    private void writeAccessConstraints(XMLExtendedStreamWriter writer, ModelNode accessAuthorization, Map<String, Map<String, Set<String>>> configuredConstraints) throws XMLStreamException {
        writer.writeStartElement(Element.CONSTRAINTS.getLocalName());

        if (configuredConstraints.containsKey(SensitivityResourceDefinition.VAULT_ELEMENT.getKey())){
            writer.writeEmptyElement(Element.VAULT_EXPRESSION_SENSITIVITY.getLocalName());
            ModelNode model = accessAuthorization.get(SensitivityResourceDefinition.VAULT_ELEMENT.getKey(),
                    SensitivityResourceDefinition.VAULT_ELEMENT.getValue());
            SensitivityResourceDefinition.CONFIGURED_REQUIRES_READ.marshallAsAttribute(model, writer);
            SensitivityResourceDefinition.CONFIGURED_REQUIRES_WRITE.marshallAsAttribute(model, writer);
        }

        if (configuredConstraints.containsKey(SENSITIVITY_CLASSIFICATION)) {
            writer.writeStartElement(Element.SENSITIVE_CLASSIFICATIONS.getLocalName());
            Map<String, Set<String>> constraints = configuredConstraints.get(SENSITIVITY_CLASSIFICATION);
            for (Map.Entry<String, Set<String>> entry : constraints.entrySet()) {
                for (String classification : entry.getValue()) {
                    writer.writeEmptyElement(Element.SENSITIVE_CLASSIFICATION.getLocalName());
                    ModelNode model = accessAuthorization.get(CONSTRAINT, SENSITIVITY_CLASSIFICATION, TYPE, entry.getKey(), CLASSIFICATION, classification);
                    writeAttribute(writer, Attribute.TYPE, entry.getKey());
                    writeAttribute(writer, Attribute.NAME, classification);
                    SensitivityResourceDefinition.CONFIGURED_REQUIRES_ADDRESSABLE.marshallAsAttribute(model, writer);
                    SensitivityResourceDefinition.CONFIGURED_REQUIRES_READ.marshallAsAttribute(model, writer);
                    SensitivityResourceDefinition.CONFIGURED_REQUIRES_WRITE.marshallAsAttribute(model, writer);
                }
            }
            writer.writeEndElement();
        }
        if (configuredConstraints.containsKey(APPLICATION_CLASSIFICATION)) {
            writer.writeStartElement(Element.APPLICATION_CLASSIFICATIONS.getLocalName());
            Map<String, Set<String>> constraints = configuredConstraints.get(APPLICATION_CLASSIFICATION);
            for (Map.Entry<String, Set<String>> entry : constraints.entrySet()) {

                for (String classification : entry.getValue()) {
                    writer.writeEmptyElement(Element.APPLICATION_CLASSIFICATION.getLocalName());
                    ModelNode model = accessAuthorization.get(CONSTRAINT, APPLICATION_CLASSIFICATION, TYPE, entry.getKey(), CLASSIFICATION, classification);
                    writeAttribute(writer, Attribute.TYPE, entry.getKey());
                    writeAttribute(writer, Attribute.NAME, classification);
                    ApplicationClassificationConfigResourceDefinition.CONFIGURED_APPLICATION.marshallAsAttribute(model, writer);
                }
            }
            writer.writeEndElement();
        }
        writer.writeEndElement();
    }

    private void writeRoleMapping(XMLExtendedStreamWriter writer, ModelNode accessAuthorization)
            throws XMLStreamException {
        writer.writeStartElement(Element.ROLE_MAPPING.getLocalName());


        if (accessAuthorization.hasDefined(ROLE_MAPPING)) {
            ModelNode roleMappings = accessAuthorization.get(ROLE_MAPPING);

            for (Property variable : roleMappings.asPropertyList()) {
                writer.writeStartElement(Element.ROLE.getLocalName());
                writeAttribute(writer, Attribute.NAME, variable.getName());
                ModelNode role = variable.getValue();
                RoleMappingResourceDefinition.INCLUDE_ALL.marshallAsAttribute(role, writer);
                if (role.hasDefined(INCLUDE)) {
                    writeIncludeExclude(writer, Element.INCLUDE.getLocalName(), role.get(INCLUDE));
                }

                if (role.hasDefined(EXCLUDE)) {
                    writeIncludeExclude(writer, Element.EXCLUDE.getLocalName(), role.get(EXCLUDE));
                }

                writer.writeEndElement();
            }
        }

        writer.writeEndElement();
    }

    private void writeIncludeExclude(XMLExtendedStreamWriter writer, String elementName, ModelNode includeExclude)
            throws XMLStreamException {
        List<Property> list = includeExclude.asPropertyList();
        if (list.isEmpty()) {
            return;
        }

        writer.writeStartElement(elementName);
        for (Property current : list) {
            // The names where only arbitrary to allow unique referencing.
            writePrincipal(writer, current.getName(), current.getValue());
        }

        writer.writeEndElement();
    }

    private void writePrincipal(XMLExtendedStreamWriter writer, String alias, ModelNode principal) throws XMLStreamException {
        String elementName = principal.require(TYPE).asString().equalsIgnoreCase(GROUP) ? Element.GROUP.getLocalName() : Element.USER.getLocalName();
        writer.writeStartElement(elementName);

        String realm = principal.get(REALM).isDefined() ? principal.require(REALM).asString() : null;
        String name = principal.require(NAME).asString();

        String expectedAlias = AccessControlXml.generateAlias(elementName, name, realm);
        if (alias.equals(expectedAlias)==false) {
            writeAttribute(writer, Attribute.ALIAS, alias);
        }

        PrincipalResourceDefinition.REALM.marshallAsAttribute(principal, writer);

        PrincipalResourceDefinition.NAME.marshallAsAttribute(principal, writer);

        writer.writeEndElement();
    }


    static Map<String, Map<String, Set<String>>> getConfiguredAccessConstraints(ModelNode accessAuthorization) {
        Map<String, Map<String, Set<String>>> configuredConstraints = new HashMap<String, Map<String, Set<String>>>();
        if (accessAuthorization != null && accessAuthorization.hasDefined(CONSTRAINT)) {
            ModelNode constraint = accessAuthorization.get(CONSTRAINT);

            configuredConstraints.putAll(getVaultConstraints(constraint));
            configuredConstraints.putAll(getSensitivityClassificationConstraints(constraint));
            configuredConstraints.putAll(getApplicationClassificationConstraints(constraint));
        }

        return configuredConstraints;
    }

    static Map<String, Map<String, Set<String>>> getVaultConstraints(final ModelNode constraint) {
        Map<String, Map<String, Set<String>>> configuredConstraints = new HashMap<String, Map<String, Set<String>>>();

        if (constraint.hasDefined(VAULT_EXPRESSION)) {
            ModelNode classification = constraint.require(VAULT_EXPRESSION);
            if (classification.hasDefined(SensitivityResourceDefinition.CONFIGURED_REQUIRES_WRITE.getName())
                    || classification.hasDefined(SensitivityResourceDefinition.CONFIGURED_REQUIRES_READ.getName())) {
                configuredConstraints.put(SensitivityResourceDefinition.VAULT_ELEMENT.getKey(),
                        Collections.<String, Set<String>> emptyMap());
            }
        }

        return configuredConstraints;
    }

    static Map<String, Map<String, Set<String>>> getSensitivityClassificationConstraints(final ModelNode constraint) {
        Map<String, Map<String, Set<String>>> configuredConstraints = new HashMap<String, Map<String, Set<String>>>();

        if (constraint.hasDefined(SENSITIVITY_CLASSIFICATION)) {
            ModelNode sensitivityParent = constraint.require(SENSITIVITY_CLASSIFICATION);

            if (sensitivityParent.hasDefined(TYPE)) {
                for (Property typeProperty : sensitivityParent.get(TYPE).asPropertyList()) {
                    if (typeProperty.getValue().hasDefined(CLASSIFICATION)) {
                        for (Property sensitivityProperty : typeProperty.getValue().get(CLASSIFICATION).asPropertyList()) {
                            ModelNode classification = sensitivityProperty.getValue();
                            if (classification.hasDefined(SensitivityResourceDefinition.CONFIGURED_REQUIRES_ADDRESSABLE.getName())
                                    || classification.hasDefined(SensitivityResourceDefinition.CONFIGURED_REQUIRES_WRITE
                                            .getName())
                                    || classification.hasDefined(SensitivityResourceDefinition.CONFIGURED_REQUIRES_READ
                                            .getName())) {
                                Map<String, Set<String>> constraintMap = configuredConstraints.get(SENSITIVITY_CLASSIFICATION);
                                if (constraintMap == null) {
                                    constraintMap = new TreeMap<String, Set<String>>();
                                    configuredConstraints.put(SENSITIVITY_CLASSIFICATION, constraintMap);
                                }
                                Set<String> types = constraintMap.get(typeProperty.getName());
                                if (types == null) {
                                    types = new TreeSet<String>();
                                    constraintMap.put(typeProperty.getName(), types);
                                }
                                types.add(sensitivityProperty.getName());
                            }
                        }
                    }
                }
            }
        }

        return configuredConstraints;
    }

    static Map<String, Map<String, Set<String>>> getApplicationClassificationConstraints(final ModelNode constraint) {
        Map<String, Map<String, Set<String>>> configuredConstraints = new HashMap<String, Map<String, Set<String>>>();

        if (constraint.hasDefined(APPLICATION_CLASSIFICATION)) {
            ModelNode appTypeParent = constraint.require(APPLICATION_CLASSIFICATION);

            if (appTypeParent.hasDefined(TYPE)) {
                for (Property typeProperty : appTypeParent.get(TYPE).asPropertyList()) {
                    if (typeProperty.getValue().hasDefined(CLASSIFICATION)) {
                        for (Property applicationProperty : typeProperty.getValue().get(CLASSIFICATION).asPropertyList()) {
                            ModelNode applicationType = applicationProperty.getValue();
                            if (applicationType.hasDefined(ApplicationClassificationConfigResourceDefinition.CONFIGURED_APPLICATION.getName())) {
                                Map<String, Set<String>> constraintMap = configuredConstraints.get(APPLICATION_CLASSIFICATION);
                                if (constraintMap == null) {
                                    constraintMap = new TreeMap<String, Set<String>>();
                                    configuredConstraints.put(APPLICATION_CLASSIFICATION, constraintMap);
                                }
                                Set<String> types = constraintMap.get(typeProperty.getName());
                                if (types == null) {
                                    types = new TreeSet<String>();
                                    constraintMap.put(typeProperty.getName(), types);
                                }
                                types.add(applicationProperty.getName());
                            }
                        }
                    }
                }
            }
        }

        return configuredConstraints;
    }
}
