/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, Red Hat Middleware LLC, 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.connector.subsystems.resourceadapters;

import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
import static org.jboss.as.connector.subsystems.common.pool.Constants.BACKGROUNDVALIDATION;
import static org.jboss.as.connector.subsystems.common.pool.Constants.BACKGROUNDVALIDATIONMILLIS;
import static org.jboss.as.connector.subsystems.common.pool.Constants.BLOCKING_TIMEOUT_WAIT_MILLIS;
import static org.jboss.as.connector.subsystems.common.pool.Constants.CAPACITY_DECREMENTER_CLASS;
import static org.jboss.as.connector.subsystems.common.pool.Constants.CAPACITY_DECREMENTER_PROPERTIES;
import static org.jboss.as.connector.subsystems.common.pool.Constants.CAPACITY_INCREMENTER_CLASS;
import static org.jboss.as.connector.subsystems.common.pool.Constants.CAPACITY_INCREMENTER_PROPERTIES;
import static org.jboss.as.connector.subsystems.common.pool.Constants.IDLETIMEOUTMINUTES;
import static org.jboss.as.connector.subsystems.common.pool.Constants.INITIAL_POOL_SIZE;
import static org.jboss.as.connector.subsystems.common.pool.Constants.MAX_POOL_SIZE;
import static org.jboss.as.connector.subsystems.common.pool.Constants.MIN_POOL_SIZE;
import static org.jboss.as.connector.subsystems.common.pool.Constants.POOL_FAIR;
import static org.jboss.as.connector.subsystems.common.pool.Constants.POOL_FLUSH_STRATEGY;
import static org.jboss.as.connector.subsystems.common.pool.Constants.POOL_PREFILL;
import static org.jboss.as.connector.subsystems.common.pool.Constants.POOL_USE_STRICT_MIN;
import static org.jboss.as.connector.subsystems.common.pool.Constants.USE_FAST_FAIL;
import static org.jboss.as.connector.subsystems.common.pool.Constants.VALIDATE_ON_MATCH;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.ALLOCATION_RETRY;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.ALLOCATION_RETRY_WAIT_MILLIS;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.APPLICATION;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.AUTHENTICATION_CONTEXT;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.AUTHENTICATION_CONTEXT_AND_APPLICATION;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.CLASS_NAME;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.CONFIG_PROPERTY_VALUE;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.CONNECTABLE;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.ELYTRON_ENABLED;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.ENABLED;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.ENLISTMENT;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.ENLISTMENT_TRACE;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.INTERLEAVING;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.JNDINAME;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.MCP;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.NOTXSEPARATEPOOL;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.NO_RECOVERY;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.PAD_XID;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.POOL_NAME_NAME;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERLUGIN_CLASSNAME;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERLUGIN_PROPERTIES;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERY_AUTHENTICATION_CONTEXT;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERY_CREDENTIAL_REFERENCE;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERY_ELYTRON_ENABLED;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERY_PASSWORD;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERY_SECURITY_DOMAIN;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERY_USERNAME;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.SAME_RM_OVERRIDE;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.SECURITY_DOMAIN;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.SECURITY_DOMAIN_AND_APPLICATION;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.SHARABLE;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.TRACKING;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.USE_CCM;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.USE_JAVA_CONTEXT;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.WRAP_XA_RESOURCE;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.XA_RESOURCE_TIMEOUT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;

import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;

import javax.xml.stream.XMLStreamException;

import org.jboss.as.connector.metadata.api.common.Credential;
import org.jboss.as.connector.metadata.api.common.Security;
import org.jboss.as.connector.util.AbstractParser;
import org.jboss.as.connector.util.ParserException;
import org.jboss.as.controller.parsing.ParseUtils;
import org.jboss.dmr.ModelNode;
import org.jboss.jca.common.CommonBundle;
import org.jboss.jca.common.api.metadata.common.Capacity;
import org.jboss.jca.common.api.metadata.common.Pool;
import org.jboss.jca.common.api.metadata.common.Recovery;
import org.jboss.jca.common.api.metadata.common.TimeOut;
import org.jboss.jca.common.api.metadata.common.Validation;
import org.jboss.jca.common.api.metadata.common.XaPool;
import org.jboss.jca.common.api.metadata.ds.DataSource;
import org.jboss.jca.common.api.metadata.ds.DsPool;
import org.jboss.jca.common.api.metadata.ds.XaDataSource;
import org.jboss.jca.common.api.metadata.resourceadapter.Activation;
import org.jboss.jca.common.api.metadata.resourceadapter.AdminObject;
import org.jboss.jca.common.api.metadata.resourceadapter.ConnectionDefinition;
import org.jboss.jca.common.api.validator.ValidateException;
import org.jboss.logging.Messages;
import org.jboss.staxmapper.XMLExtendedStreamReader;

/**
 * A CommonIronJacamarParser.
 *
 * @author Stefano Maestri
 */
public abstract class CommonIronJacamarParser extends AbstractParser {
    /**
     * The bundle
     */
    private static CommonBundle bundle = Messages.getBundle(CommonBundle.class);

    protected void parseConfigProperties(final XMLExtendedStreamReader reader, final Map<String, ModelNode> map) throws XMLStreamException, ParserException {
        String name = rawAttributeText(reader, "name");
        final ModelNode operation = new ModelNode();
        operation.get(OP).set(ADD);
        String value = rawElementText(reader);
        CONFIG_PROPERTY_VALUE.parseAndSetParameter(value, operation, reader);

        if (map.containsKey(name)) {
            throw ParseUtils.unexpectedElement(reader);
        }
        map.put(name, operation);
    }

    /**
     * parse a single connection-definition tag
     *
     * @param reader the reader
     * @throws javax.xml.stream.XMLStreamException
     *                         XMLStreamException
     * @throws ParserException ParserException
     * @throws org.jboss.jca.common.api.validator.ValidateException
     *                         ValidateException
     */
    protected void parseConnectionDefinitions_3_0(final XMLExtendedStreamReader reader, final Map<String, ModelNode> map,
                                              final Map<String, HashMap<String, ModelNode>> configMap, final boolean isXa)
            throws XMLStreamException, ParserException, ValidateException {

        final ModelNode connectionDefinitionNode = new ModelNode();
        connectionDefinitionNode.get(OP).set(ADD);

        String poolName = null;
        String jndiName = null;
        int attributeSize = reader.getAttributeCount();
        boolean poolDefined = Boolean.FALSE;

        for (int i = 0; i < attributeSize; i++) {
            ConnectionDefinition.Attribute attribute = ConnectionDefinition.Attribute.forName(reader.getAttributeLocalName(i));
            String value = reader.getAttributeValue(i);
            switch (attribute) {
                case ENABLED: {
                    ENABLED.parseAndSetParameter(value, connectionDefinitionNode, reader);

                    break;
                }
                case CONNECTABLE: {
                    CONNECTABLE.parseAndSetParameter(value, connectionDefinitionNode, reader);

                    break;
                }
                case TRACKING: {
                    TRACKING.parseAndSetParameter(value, connectionDefinitionNode, reader);

                    break;
                }
                case JNDI_NAME: {
                    jndiName = value;
                    JNDINAME.parseAndSetParameter(jndiName, connectionDefinitionNode, reader);
                    break;
                }
                case POOL_NAME: {
                    poolName = value;
                    break;
                }
                case USE_JAVA_CONTEXT: {
                    USE_JAVA_CONTEXT.parseAndSetParameter(value, connectionDefinitionNode, reader);

                    break;
                }

                case USE_CCM: {
                    USE_CCM.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                }

                case SHARABLE: {
                    SHARABLE.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                }

                case ENLISTMENT: {
                    ENLISTMENT.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                }

                case CLASS_NAME: {
                    CLASS_NAME.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                }
                default:
                    throw ParseUtils.unexpectedAttribute(reader, i);
            }
        }
        if (poolName == null || poolName.trim().equals("")) {
            if (jndiName != null && jndiName.trim().length() != 0) {
                if (jndiName.contains("/")) {
                    poolName = jndiName.substring(jndiName.lastIndexOf("/") + 1);
                } else {
                    poolName = jndiName.substring(jndiName.lastIndexOf(":") + 1);
                }
            } else {
                throw ParseUtils.missingRequired(reader, EnumSet.of(ConnectionDefinition.Attribute.JNDI_NAME));
            }
        }

        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case END_ELEMENT: {
                    if (Activation.Tag.forName(reader.getLocalName()) == Activation.Tag.CONNECTION_DEFINITION) {

                        map.put(poolName, connectionDefinitionNode);
                        return;
                    } else {
                        if (ConnectionDefinition.Tag.forName(reader.getLocalName()) == ConnectionDefinition.Tag.UNKNOWN) {
                            throw ParseUtils.unexpectedEndElement(reader);
                        }
                    }
                    break;
                }
                case START_ELEMENT: {
                    switch (ConnectionDefinition.Tag.forName(reader.getLocalName())) {
                        case CONFIG_PROPERTY: {
                            if (!configMap.containsKey(poolName)) {
                                configMap.put(poolName, new HashMap<String, ModelNode>(0));
                            }
                            parseConfigProperties(reader, configMap.get(poolName));
                            break;
                        }
                        case SECURITY: {
                            parseSecuritySettings(reader, connectionDefinitionNode);
                            break;
                        }
                        case TIMEOUT: {
                            parseTimeOut(reader, isXa, connectionDefinitionNode);
                            break;
                        }
                        case VALIDATION: {
                            parseValidation(reader, connectionDefinitionNode);
                            break;
                        }
                        case XA_POOL: {
                            if (!isXa) {
                                throw ParseUtils.unexpectedElement(reader);
                            }
                            if (poolDefined) {
                                throw new ParserException(bundle.multiplePools());
                            }
                            parseXaPool(reader, connectionDefinitionNode);
                            poolDefined = true;
                            break;
                        }
                        case POOL: {
                            if (isXa) {
                                throw ParseUtils.unexpectedElement(reader);
                            }
                            if (poolDefined) {
                                throw new ParserException(bundle.multiplePools());
                            }
                            parsePool(reader, connectionDefinitionNode);
                            poolDefined = true;
                            break;
                        }
                        case RECOVERY: {
                            parseRecovery(reader, connectionDefinitionNode);
                            break;
                        }
                        default:
                            throw ParseUtils.unexpectedElement(reader);
                    }
                    break;
                }
            }
        }
        throw ParseUtils.unexpectedEndElement(reader);

    }

    /**
     * parse a single connection-definition tag
     *
     * @param reader the reader
     * @throws javax.xml.stream.XMLStreamException
     *                         XMLStreamException
     * @throws ParserException ParserException
     * @throws org.jboss.jca.common.api.validator.ValidateException
     *                         ValidateException
     */
    protected void parseConnectionDefinitions_4_0(final XMLExtendedStreamReader reader, final Map<String, ModelNode> map,
                                                  final Map<String, HashMap<String, ModelNode>> configMap, final boolean isXa)
            throws XMLStreamException, ParserException, ValidateException {

        final ModelNode connectionDefinitionNode = new ModelNode();
        connectionDefinitionNode.get(OP).set(ADD);

        String poolName = null;
        String jndiName = null;
        int attributeSize = reader.getAttributeCount();
        boolean poolDefined = Boolean.FALSE;

        for (int i = 0; i < attributeSize; i++) {
            ConnectionDefinition.Attribute attribute = ConnectionDefinition.Attribute.forName(reader.getAttributeLocalName(i));
            String value = reader.getAttributeValue(i);
            switch (attribute) {
                case ENABLED: {
                    ENABLED.parseAndSetParameter(value, connectionDefinitionNode, reader);

                    break;
                }
                case CONNECTABLE: {
                    CONNECTABLE.parseAndSetParameter(value, connectionDefinitionNode, reader);

                    break;
                }
                case TRACKING: {
                    TRACKING.parseAndSetParameter(value, connectionDefinitionNode, reader);

                    break;
                }
                case JNDI_NAME: {
                    jndiName = value;
                    JNDINAME.parseAndSetParameter(jndiName, connectionDefinitionNode, reader);
                    break;
                }
                case POOL_NAME: {
                    poolName = value;
                    break;
                }
                case USE_JAVA_CONTEXT: {
                    USE_JAVA_CONTEXT.parseAndSetParameter(value, connectionDefinitionNode, reader);

                    break;
                }

                case USE_CCM: {
                    USE_CCM.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                }

                case SHARABLE: {
                    SHARABLE.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                }

                case ENLISTMENT: {
                    ENLISTMENT.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                }

                case CLASS_NAME: {
                    CLASS_NAME.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                }
                case MCP: {
                    MCP.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                }
                case ENLISTMENT_TRACE:
                    ENLISTMENT_TRACE.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                default:
                    throw ParseUtils.unexpectedAttribute(reader, i);
            }
        }
        if (poolName == null || poolName.trim().equals("")) {
            if (jndiName != null && jndiName.trim().length() != 0) {
                if (jndiName.contains("/")) {
                    poolName = jndiName.substring(jndiName.lastIndexOf("/") + 1);
                } else {
                    poolName = jndiName.substring(jndiName.lastIndexOf(":") + 1);
                }
            } else {
                throw ParseUtils.missingRequired(reader, EnumSet.of(ConnectionDefinition.Attribute.JNDI_NAME));
            }
        }

        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case END_ELEMENT: {
                    if (Activation.Tag.forName(reader.getLocalName()) == Activation.Tag.CONNECTION_DEFINITION) {

                        map.put(poolName, connectionDefinitionNode);
                        return;
                    } else {
                        if (ConnectionDefinition.Tag.forName(reader.getLocalName()) == ConnectionDefinition.Tag.UNKNOWN) {
                            throw ParseUtils.unexpectedEndElement(reader);
                        }
                    }
                    break;
                }
                case START_ELEMENT: {
                    switch (ConnectionDefinition.Tag.forName(reader.getLocalName())) {
                        case CONFIG_PROPERTY: {
                            if (!configMap.containsKey(poolName)) {
                                configMap.put(poolName, new HashMap<String, ModelNode>(0));
                            }
                            parseConfigProperties(reader, configMap.get(poolName));
                            break;
                        }
                        case SECURITY: {
                            parseSecuritySettings(reader, connectionDefinitionNode);
                            break;
                        }
                        case TIMEOUT: {
                            parseTimeOut(reader, isXa, connectionDefinitionNode);
                            break;
                        }
                        case VALIDATION: {
                            parseValidation(reader, connectionDefinitionNode);
                            break;
                        }
                        case XA_POOL: {
                            if (!isXa) {
                                throw ParseUtils.unexpectedElement(reader);
                            }
                            if (poolDefined) {
                                throw new ParserException(bundle.multiplePools());
                            }
                            parseXaPool(reader, connectionDefinitionNode);
                            poolDefined = true;
                            break;
                        }
                        case POOL: {
                            if (isXa) {
                                throw ParseUtils.unexpectedElement(reader);
                            }
                            if (poolDefined) {
                                throw new ParserException(bundle.multiplePools());
                            }
                            parsePool(reader, connectionDefinitionNode);
                            poolDefined = true;
                            break;
                        }
                        case RECOVERY: {
                            parseRecovery(reader, connectionDefinitionNode);
                            break;
                        }
                        default:
                            throw ParseUtils.unexpectedElement(reader);
                    }
                    break;
                }
            }
        }
        throw ParseUtils.unexpectedEndElement(reader);

    }

    /**
     * Parses connection attributes for version 5.0
     * @param reader the xml reader
     * @param connectionDefinitionNode the connection definition add node
     * @return the pool name
     * @throws XMLStreamException
     */
    private String parseConnectionAttributes_5_0(final XMLExtendedStreamReader reader,  final ModelNode connectionDefinitionNode)
            throws XMLStreamException {
        String poolName = null;
        String jndiName = null;
        int attributeSize = reader.getAttributeCount();

        for (int i = 0; i < attributeSize; i++) {
            ConnectionDefinition.Attribute attribute = ConnectionDefinition.Attribute.forName(reader.getAttributeLocalName(i));
            String value = reader.getAttributeValue(i);
            switch (attribute) {
                case ENABLED: {
                    ENABLED.parseAndSetParameter(value, connectionDefinitionNode, reader);

                    break;
                }
                case CONNECTABLE: {
                    CONNECTABLE.parseAndSetParameter(value, connectionDefinitionNode, reader);

                    break;
                }
                case TRACKING: {
                    TRACKING.parseAndSetParameter(value, connectionDefinitionNode, reader);

                    break;
                }
                case JNDI_NAME: {
                    jndiName = value;
                    JNDINAME.parseAndSetParameter(jndiName, connectionDefinitionNode, reader);
                    break;
                }
                case POOL_NAME: {
                    poolName = value;
                    break;
                }
                case USE_JAVA_CONTEXT: {
                    USE_JAVA_CONTEXT.parseAndSetParameter(value, connectionDefinitionNode, reader);

                    break;
                }

                case USE_CCM: {
                    USE_CCM.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                }

                case SHARABLE: {
                    SHARABLE.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                }

                case ENLISTMENT: {
                    ENLISTMENT.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                }

                case CLASS_NAME: {
                    CLASS_NAME.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                }
                case MCP: {
                    MCP.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                }
                case ENLISTMENT_TRACE:
                    ENLISTMENT_TRACE.parseAndSetParameter(value, connectionDefinitionNode, reader);
                    break;
                default:
                    throw ParseUtils.unexpectedAttribute(reader, i);
            }
        }
        if (poolName == null || poolName.trim().equals("")) {
            if (jndiName != null && jndiName.trim().length() != 0) {
                if (jndiName.contains("/")) {
                    poolName = jndiName.substring(jndiName.lastIndexOf("/") + 1);
                } else {
                    poolName = jndiName.substring(jndiName.lastIndexOf(":") + 1);
                }
            } else {
                throw ParseUtils.missingRequired(reader, EnumSet.of(ConnectionDefinition.Attribute.JNDI_NAME));
            }
        }
        return poolName;
    }

    /**
     * parse a single connection-definition tag
     *
     * @param reader the reader
     * @throws javax.xml.stream.XMLStreamException
     *                         XMLStreamException
     * @throws ParserException ParserException
     * @throws org.jboss.jca.common.api.validator.ValidateException
     *                         ValidateException
     */
    protected void parseConnectionDefinitions_5_0(final XMLExtendedStreamReader reader, final Map<String, ModelNode> map,
                                                  final Map<String, HashMap<String, ModelNode>> configMap, final boolean isXa)
            throws XMLStreamException, ParserException, ValidateException {

        final ModelNode connectionDefinitionNode = new ModelNode();
        connectionDefinitionNode.get(OP).set(ADD);

        final String poolName = parseConnectionAttributes_5_0(reader, connectionDefinitionNode);
        boolean poolDefined = Boolean.FALSE;

        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case END_ELEMENT: {
                    if (Activation.Tag.forName(reader.getLocalName()) == Activation.Tag.CONNECTION_DEFINITION) {

                        map.put(poolName, connectionDefinitionNode);
                        return;
                    } else {
                        if (ConnectionDefinition.Tag.forName(reader.getLocalName()) == ConnectionDefinition.Tag.UNKNOWN) {
                            throw ParseUtils.unexpectedEndElement(reader);
                        }
                    }
                    break;
                }
                case START_ELEMENT: {
                    switch (ConnectionDefinition.Tag.forName(reader.getLocalName())) {
                        case CONFIG_PROPERTY: {
                            if (!configMap.containsKey(poolName)) {
                                configMap.put(poolName, new HashMap<String, ModelNode>(0));
                            }
                            parseConfigProperties(reader, configMap.get(poolName));
                            break;
                        }
                        case SECURITY: {
                            parseElytronSupportedSecuritySettings(reader, connectionDefinitionNode);
                            break;
                        }
                        case TIMEOUT: {
                            parseTimeOut(reader, isXa, connectionDefinitionNode);
                            break;
                        }
                        case VALIDATION: {
                            parseValidation(reader, connectionDefinitionNode);
                            break;
                        }
                        case XA_POOL: {
                            if (!isXa) {
                                throw ParseUtils.unexpectedElement(reader);
                            }
                            if (poolDefined) {
                                throw new ParserException(bundle.multiplePools());
                            }
                            parseXaPool(reader, connectionDefinitionNode);
                            poolDefined = true;
                            break;
                        }
                        case POOL: {
                            if (isXa) {
                                throw ParseUtils.unexpectedElement(reader);
                            }
                            if (poolDefined) {
                                throw new ParserException(bundle.multiplePools());
                            }
                            parsePool(reader, connectionDefinitionNode);
                            poolDefined = true;
                            break;
                        }
                        case RECOVERY: {
                            parseElytronSupportedRecovery(reader, connectionDefinitionNode);
                            break;
                        }
                        default:
                            throw ParseUtils.unexpectedElement(reader);
                    }
                    break;
                }
            }
        }
        throw ParseUtils.unexpectedEndElement(reader);

    }

    /**
         * parse a single connection-definition tag
         *
         * @param reader the reader
         * @throws javax.xml.stream.XMLStreamException
         *                         XMLStreamException
         * @throws ParserException ParserException
         * @throws org.jboss.jca.common.api.validator.ValidateException
         *                         ValidateException
         */
        protected void parseConnectionDefinitions_1_0(final XMLExtendedStreamReader reader, final Map<String, ModelNode> map,
                                                  final Map<String, HashMap<String, ModelNode>> configMap, final boolean isXa)
                throws XMLStreamException, ParserException, ValidateException {

            final ModelNode connectionDefinitionNode = new ModelNode();
            connectionDefinitionNode.get(OP).set(ADD);

            String poolName = null;
            String jndiName = null;
            int attributeSize = reader.getAttributeCount();
            boolean poolDefined = Boolean.FALSE;

            for (int i = 0; i < attributeSize; i++) {
                ConnectionDefinition.Attribute attribute = ConnectionDefinition.Attribute.forName(reader.getAttributeLocalName(i));
                String value = reader.getAttributeValue(i);
                switch (attribute) {
                    case ENABLED: {
                        ENABLED.parseAndSetParameter(value, connectionDefinitionNode, reader);

                        break;
                    }
                    case JNDI_NAME: {
                        jndiName = value;
                        JNDINAME.parseAndSetParameter(jndiName, connectionDefinitionNode, reader);
                        break;
                    }
                    case POOL_NAME: {
                        poolName = value;
                        break;
                    }
                    case USE_JAVA_CONTEXT: {
                        USE_JAVA_CONTEXT.parseAndSetParameter(value, connectionDefinitionNode, reader);

                        break;
                    }

                    case USE_CCM: {
                        USE_CCM.parseAndSetParameter(value, connectionDefinitionNode, reader);
                        break;
                    }

                    case SHARABLE: {
                        SHARABLE.parseAndSetParameter(value, connectionDefinitionNode, reader);
                        break;
                    }

                    case ENLISTMENT: {
                        ENLISTMENT.parseAndSetParameter(value, connectionDefinitionNode, reader);
                        break;
                    }

                    case CLASS_NAME: {
                        CLASS_NAME.parseAndSetParameter(value, connectionDefinitionNode, reader);
                        break;
                    }
                    default:
                        throw ParseUtils.unexpectedAttribute(reader,i);
                }
            }
            if (poolName == null || poolName.trim().equals("")) {
                if (jndiName != null && jndiName.trim().length() != 0) {
                    if (jndiName.contains("/")) {
                        poolName = jndiName.substring(jndiName.lastIndexOf("/") + 1);
                    } else {
                        poolName = jndiName.substring(jndiName.lastIndexOf(":") + 1);
                    }
                } else {
                    throw ParseUtils.missingRequired(reader, EnumSet.of(ConnectionDefinition.Attribute.JNDI_NAME));
                }
            }

            while (reader.hasNext()) {
                switch (reader.nextTag()) {
                    case END_ELEMENT: {
                        if (Activation.Tag.forName(reader.getLocalName()) == Activation.Tag.CONNECTION_DEFINITION) {

                            map.put(poolName, connectionDefinitionNode);
                            return;
                        } else {
                            if (ConnectionDefinition.Tag.forName(reader.getLocalName()) == ConnectionDefinition.Tag.UNKNOWN) {
                                throw ParseUtils.unexpectedEndElement(reader);
                            }
                        }
                        break;
                    }
                    case START_ELEMENT: {
                        switch (ConnectionDefinition.Tag.forName(reader.getLocalName())) {
                            case CONFIG_PROPERTY: {
                                if (!configMap.containsKey(poolName)) {
                                    configMap.put(poolName, new HashMap<String, ModelNode>(0));
                                }
                                parseConfigProperties(reader, configMap.get(poolName));
                                break;
                            }
                            case SECURITY: {
                                parseSecuritySettings(reader, connectionDefinitionNode);
                                break;
                            }
                            case TIMEOUT: {
                                parseTimeOut(reader, isXa, connectionDefinitionNode);
                                break;
                            }
                            case VALIDATION: {
                                parseValidation(reader, connectionDefinitionNode);
                                break;
                            }
                            case XA_POOL: {
                                if (!isXa) {
                                    throw ParseUtils.unexpectedElement(reader);
                                }
                                if (poolDefined) {
                                    throw new ParserException(bundle.multiplePools());
                                }
                                parseXaPool(reader, connectionDefinitionNode);
                                poolDefined = true;
                                break;
                            }
                            case POOL: {
                                if (isXa) {
                                    throw ParseUtils.unexpectedElement(reader);
                                }
                                if (poolDefined) {
                                    throw new ParserException(bundle.multiplePools());
                                }
                                parsePool(reader, connectionDefinitionNode);
                                poolDefined = true;
                                break;
                            }
                            case RECOVERY: {
                                parseRecovery(reader, connectionDefinitionNode);
                                break;
                            }
                            default:
                                throw ParseUtils.unexpectedElement(reader);
                        }
                        break;
                    }
                }
            }
            throw ParseUtils.unexpectedEndElement(reader);

        }

    private void parseValidation(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException, ParserException {

        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case END_ELEMENT: {
                    if (ConnectionDefinition.Tag.forName(reader.getLocalName()) == ConnectionDefinition.Tag.VALIDATION) {

                        return;
                    } else {
                        if (ConnectionDefinition.Tag.forName(reader.getLocalName()) == ConnectionDefinition.Tag.UNKNOWN) {
                            throw ParseUtils.unexpectedEndElement(reader);
                        }
                    }
                    break;
                }
                case START_ELEMENT: {
                    switch (Validation.Tag.forName(reader.getLocalName())) {
                        case BACKGROUND_VALIDATION: {
                            String value = rawElementText(reader);
                            BACKGROUNDVALIDATION.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case BACKGROUND_VALIDATION_MILLIS: {
                            String value = rawElementText(reader);
                            BACKGROUNDVALIDATIONMILLIS.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case USE_FAST_FAIL: {
                            String value = rawElementText(reader);
                            USE_FAST_FAIL.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case VALIDATE_ON_MATCH: {
                            String value = rawElementText(reader);
                            VALIDATE_ON_MATCH.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        default:
                            throw ParseUtils.unexpectedElement(reader);

                    }
                    break;
                }
            }
        }
        throw ParseUtils.unexpectedEndElement(reader);
    }

    private void parseTimeOut(XMLExtendedStreamReader reader, Boolean isXa, ModelNode node) throws XMLStreamException,
            ParserException, ValidateException {

        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case END_ELEMENT: {
                    if (ConnectionDefinition.Tag.forName(reader.getLocalName()) == ConnectionDefinition.Tag.TIMEOUT) {

                        return;
                    } else {
                        if (TimeOut.Tag.forName(reader.getLocalName()) == TimeOut.Tag.UNKNOWN) {
                            throw ParseUtils.unexpectedElement(reader);
                        }
                    }
                    break;
                }
                case START_ELEMENT: {
                    String value = rawElementText(reader);
                    switch (TimeOut.Tag.forName(reader.getLocalName())) {
                        case ALLOCATION_RETRY: {
                            ALLOCATION_RETRY.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case ALLOCATION_RETRY_WAIT_MILLIS: {
                            ALLOCATION_RETRY_WAIT_MILLIS.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case BLOCKING_TIMEOUT_MILLIS: {
                            BLOCKING_TIMEOUT_WAIT_MILLIS.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case IDLE_TIMEOUT_MINUTES: {
                            IDLETIMEOUTMINUTES.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case XA_RESOURCE_TIMEOUT: {
                            XA_RESOURCE_TIMEOUT.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        default:
                            throw ParseUtils.unexpectedElement(reader);
                    }
                    break;
                }
            }
        }
        throw ParseUtils.unexpectedEndElement(reader);
    }

    protected void parseAdminObjects(final XMLExtendedStreamReader reader, final Map<String, ModelNode> map, final Map<String, HashMap<String, ModelNode>> configMap)
            throws XMLStreamException, ParserException, ValidateException {

        final ModelNode adminObjectNode = new ModelNode();
        adminObjectNode.get(OP).set(ADD);
        int attributeSize = reader.getAttributeCount();

        String poolName = null;
        String jndiName = null;
        for (int i = 0; i < attributeSize; i++) {
            AdminObject.Attribute attribute = AdminObject.Attribute.forName(reader
                    .getAttributeLocalName(i));
            switch (attribute) {
                case ENABLED: {
                    String value = rawAttributeText(reader, ENABLED.getXmlName());
                    if (value != null) {
                        ENABLED.parseAndSetParameter(value, adminObjectNode, reader);
                    }
                    break;
                }
                case JNDI_NAME: {
                    jndiName = rawAttributeText(reader, JNDINAME.getXmlName());
                    if (jndiName != null) {
                        JNDINAME.parseAndSetParameter(jndiName, adminObjectNode, reader);
                    }
                    break;
                }
                case POOL_NAME: {
                    poolName = rawAttributeText(reader, POOL_NAME_NAME);
                    break;
                }
                case USE_JAVA_CONTEXT: {
                    String value = rawAttributeText(reader, USE_JAVA_CONTEXT.getXmlName());
                    if (value != null) {
                        USE_JAVA_CONTEXT.parseAndSetParameter(value, adminObjectNode, reader);
                    }
                    break;
                }
                case CLASS_NAME: {
                    String value = rawAttributeText(reader, CLASS_NAME.getXmlName());
                    if (value != null) {
                        CLASS_NAME.parseAndSetParameter(value, adminObjectNode, reader);
                    }
                    break;
                }
                default:
                    throw ParseUtils.unexpectedAttribute(reader, i);
            }
        }
        if (poolName == null || poolName.trim().equals("")) {
            if (jndiName != null && jndiName.trim().length() != 0) {
                if (jndiName.contains("/")) {
                    poolName = jndiName.substring(jndiName.lastIndexOf("/") + 1);
                } else {
                    poolName = jndiName.substring(jndiName.lastIndexOf(":") + 1);
                }
            } else {
                throw ParseUtils.missingRequired(reader, EnumSet.of(AdminObject.Attribute.JNDI_NAME));

            }
        }
        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case END_ELEMENT: {
                    if (Activation.Tag.forName(reader.getLocalName()) == Activation.Tag.ADMIN_OBJECT) {

                        map.put(poolName, adminObjectNode);
                        return;
                    } else {
                        if (AdminObject.Tag.forName(reader.getLocalName()) == AdminObject.Tag.UNKNOWN) {
                            throw ParseUtils.unexpectedEndElement(reader);
                        }
                    }
                    break;
                }
                case START_ELEMENT: {
                    switch (AdminObject.Tag.forName(reader.getLocalName())) {
                        case CONFIG_PROPERTY: {
                            if (!configMap.containsKey(poolName)) {
                                configMap.put(poolName, new HashMap<String, ModelNode>(0));
                            }
                            parseConfigProperties(reader, configMap.get(poolName));
                            break;
                        }
                        default:
                            throw ParseUtils.unexpectedElement(reader);
                    }
                    break;
                }
            }
        }
        throw ParseUtils.unexpectedEndElement(reader);
    }

    /**
     * parse a {@link XaPool} object
     *
     * @param reader reader
     * @throws XMLStreamException XMLStreamException
     * @throws ParserException
     * @throws ValidateException  ValidateException
     */
    protected void parseXaPool(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException, ParserException, ValidateException {

        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case END_ELEMENT: {
                    if (XaDataSource.Tag.forName(reader.getLocalName()) == XaDataSource.Tag.XA_POOL) {

                        return;

                    } else {
                        if (XaPool.Tag.forName(reader.getLocalName()) == XaPool.Tag.UNKNOWN) {
                            throw ParseUtils.unexpectedEndElement(reader);
                        }
                    }
                    break;
                }
                case START_ELEMENT: {
                    switch (XaPool.Tag.forName(reader.getLocalName())) {
                        case MAX_POOL_SIZE: {
                            String value = rawElementText(reader);
                            MAX_POOL_SIZE.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case MIN_POOL_SIZE: {
                            String value = rawElementText(reader);
                            MIN_POOL_SIZE.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case INITIAL_POOL_SIZE: {
                            String value = rawElementText(reader);
                            INITIAL_POOL_SIZE.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case PREFILL: {
                            String value = rawElementText(reader);
                            POOL_PREFILL.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case FAIR: {
                            String value = rawElementText(reader);
                            POOL_FAIR.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case USE_STRICT_MIN: {
                            String value = rawElementText(reader);
                            POOL_USE_STRICT_MIN.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case FLUSH_STRATEGY: {
                            String value = rawElementText(reader);
                            POOL_FLUSH_STRATEGY.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case INTERLEAVING: {
                            String value = rawElementText(reader);
                            //just presence means true
                            value = value == null ? "true" : value;
                            INTERLEAVING.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case IS_SAME_RM_OVERRIDE: {
                            String value = rawElementText(reader);
                            SAME_RM_OVERRIDE.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case NO_TX_SEPARATE_POOLS: {
                            String value = rawElementText(reader);
                            //just presence means true
                            value = value == null ? "true" : value;
                            NOTXSEPARATEPOOL.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case PAD_XID: {
                            String value = rawElementText(reader);
                            PAD_XID.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case WRAP_XA_RESOURCE: {
                            String value = rawElementText(reader);
                            WRAP_XA_RESOURCE.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case CAPACITY: {
                            parseCapacity(reader, node);
                            break;
                        }

                        default:
                            throw ParseUtils.unexpectedElement(reader);
                    }
                    break;
                }
            }
        }
        throw ParseUtils.unexpectedEndElement(reader);
    }

    protected void parsePool(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException, ParserException,
            ValidateException {

        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case END_ELEMENT: {
                    if (DataSource.Tag.forName(reader.getLocalName()) == DataSource.Tag.POOL) {

                        return;

                    } else {
                        if (Pool.Tag.forName(reader.getLocalName()) == Pool.Tag.UNKNOWN) {
                            throw ParseUtils.unexpectedEndElement(reader);
                        }
                    }
                    break;
                }
                case START_ELEMENT: {
                    switch (Pool.Tag.forName(reader.getLocalName())) {
                        case MAX_POOL_SIZE: {
                            String value = rawElementText(reader);
                            MAX_POOL_SIZE.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case INITIAL_POOL_SIZE: {
                            String value = rawElementText(reader);
                            INITIAL_POOL_SIZE.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case MIN_POOL_SIZE: {
                            String value = rawElementText(reader);
                            MIN_POOL_SIZE.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case PREFILL: {
                            String value = rawElementText(reader);
                            POOL_PREFILL.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case FAIR: {
                            String value = rawElementText(reader);
                            POOL_FAIR.parseAndSetParameter( value, node, reader );
                            break;
                        }
                        case USE_STRICT_MIN: {
                            String value = rawElementText(reader);
                            POOL_USE_STRICT_MIN.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case FLUSH_STRATEGY: {
                            String value = rawElementText(reader);
                            POOL_FLUSH_STRATEGY.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case CAPACITY: {
                            parseCapacity(reader, node);
                            break;
                        }
                        default:
                            throw ParseUtils.unexpectedElement(reader);
                    }
                    break;
                }
            }
        }
        throw ParseUtils.unexpectedEndElement(reader);
    }

    private void parseCapacity(XMLExtendedStreamReader reader, final ModelNode operation) throws XMLStreamException, ParserException,
            ValidateException {

        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case END_ELEMENT: {
                    if (DsPool.Tag.forName(reader.getLocalName()) == DsPool.Tag.CAPACITY) {

                        return;
                    } else {
                        if (Capacity.Tag.forName(reader.getLocalName()) == Capacity.Tag.UNKNOWN) {
                            throw new ParserException(bundle.unexpectedEndTag(reader.getLocalName()));
                        }
                    }
                    break;
                }
                case START_ELEMENT: {
                    switch (Capacity.Tag.forName(reader.getLocalName())) {
                        case INCREMENTER: {
                            parseExtension(reader, reader.getLocalName(), operation, CAPACITY_INCREMENTER_CLASS, CAPACITY_INCREMENTER_PROPERTIES);
                            break;
                        }
                        case DECREMENTER: {
                            parseExtension(reader, reader.getLocalName(), operation, CAPACITY_DECREMENTER_CLASS, CAPACITY_DECREMENTER_PROPERTIES);
                            break;
                        }

                        default:
                            throw new ParserException(bundle.unexpectedElement(reader.getLocalName()));
                    }
                    break;
                }
            }
        }
        throw new ParserException(bundle.unexpectedEndOfDocument());
    }

    protected void parseRecovery(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException, ParserException,
            ValidateException {

        for (Recovery.Attribute attribute : Recovery.Attribute.values()) {
            switch (attribute) {
                case NO_RECOVERY: {
                    String value = rawAttributeText(reader, NO_RECOVERY.getXmlName());
                    if (value != null) {
                        NO_RECOVERY.parseAndSetParameter(value, node, reader);
                    }
                    break;
                }
                default:
                    break;
            }
        }

        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case END_ELEMENT: {
                    if (XaDataSource.Tag.forName(reader.getLocalName()) == XaDataSource.Tag.RECOVERY) {
                        return;
                    } else {
                        if (Recovery.Tag.forName(reader.getLocalName()) == Recovery.Tag.UNKNOWN) {
                            throw ParseUtils.unexpectedEndElement(reader);
                        }
                    }
                    break;
                }
                case START_ELEMENT: {
                    Recovery.Tag tag = Recovery.Tag.forName(reader.getLocalName());
                    switch (tag) {
                        case RECOVER_CREDENTIAL: {
                            parseRecoveryCredential(reader, node);
                            break;
                        }
                        case RECOVER_PLUGIN: {
                            parseExtension(reader, tag.getLocalName(), node, RECOVERLUGIN_CLASSNAME, RECOVERLUGIN_PROPERTIES);
                            break;
                        }
                        default:
                            throw ParseUtils.unexpectedElement(reader);
                    }
                    break;
                }
            }
        }
        throw ParseUtils.unexpectedEndElement(reader);
    }

    protected void parseElytronSupportedRecovery(XMLExtendedStreamReader reader, ModelNode node)
            throws XMLStreamException, ParserException, ValidateException {

        for (Recovery.Attribute attribute : Recovery.Attribute.values()) {
            switch (attribute) {
                case NO_RECOVERY: {
                    String value = rawAttributeText(reader, NO_RECOVERY.getXmlName());
                    if (value != null) {
                        NO_RECOVERY.parseAndSetParameter(value, node, reader);
                    }
                    break;
                }
                default:
                    break;
            }
        }

        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case END_ELEMENT: {
                    if (XaDataSource.Tag.forName(reader.getLocalName()) == XaDataSource.Tag.RECOVERY) {
                        return;
                    } else {
                        if (Recovery.Tag.forName(reader.getLocalName()) == Recovery.Tag.UNKNOWN) {
                            throw ParseUtils.unexpectedEndElement(reader);
                        }
                    }
                    break;
                }
                case START_ELEMENT: {
                    Recovery.Tag tag = Recovery.Tag.forName(reader.getLocalName());
                    switch (tag) {
                        case RECOVER_CREDENTIAL: {
                            parseElytronSupportedRecoveryCredential(reader, node);
                            break;
                        }
                        case RECOVER_PLUGIN: {
                            parseExtension(reader, tag.getLocalName(), node, RECOVERLUGIN_CLASSNAME, RECOVERLUGIN_PROPERTIES);
                            break;
                        }
                        default:
                            throw ParseUtils.unexpectedElement(reader);
                    }
                    break;
                }
            }
        }
        throw ParseUtils.unexpectedEndElement(reader);
    }

    private void parseSecuritySettings(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException, ParserException,
            ValidateException {

        boolean securtyDomainMatched = false;
        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case END_ELEMENT: {
                    if (DataSource.Tag.forName(reader.getLocalName()) == DataSource.Tag.SECURITY) {

                        return;
                    } else {
                        if (Security.Tag.forName(reader.getLocalName()) == Security.Tag.UNKNOWN) {
                            throw ParseUtils.unexpectedEndElement(reader);
                        }
                    }
                    break;
                }
                case START_ELEMENT: {
                    switch (Security.Tag.forName(reader.getLocalName())) {

                        case SECURITY_DOMAIN: {
                            if (securtyDomainMatched) {
                                throw ParseUtils.unexpectedElement(reader);
                            }
                            String value = rawElementText(reader);
                            SECURITY_DOMAIN.parseAndSetParameter(value, node, reader);
                            securtyDomainMatched = true;
                            break;
                        }
                        case SECURITY_DOMAIN_AND_APPLICATION: {
                            String value = rawElementText(reader);
                            SECURITY_DOMAIN_AND_APPLICATION.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case APPLICATION: {
                            String value = rawElementText(reader);
                            //just presence means true
                            value = value == null ? "true" : value;
                            APPLICATION.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        default:
                            throw ParseUtils.unexpectedElement(reader);
                    }
                    break;
                }
            }
        }
        throw ParseUtils.unexpectedEndElement(reader);
    }

    private void parseElytronSupportedSecuritySettings(XMLExtendedStreamReader reader, ModelNode node)
            throws XMLStreamException, ParserException, ValidateException {

        boolean securityDomainMatched = false;
        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case END_ELEMENT: {
                    if (DataSource.Tag.forName(reader.getLocalName()) == DataSource.Tag.SECURITY) {

                        return;
                    } else {
                        if (Security.Tag.forName(reader.getLocalName()) == Security.Tag.UNKNOWN) {
                            throw ParseUtils.unexpectedEndElement(reader);
                        }
                    }
                    break;
                }
                case START_ELEMENT: {
                    switch (Security.Tag.forName(reader.getLocalName())) {

                        case SECURITY_DOMAIN: {
                            if (securityDomainMatched) {
                                throw ParseUtils.unexpectedElement(reader);
                            }
                            String value = rawElementText(reader);
                            SECURITY_DOMAIN.parseAndSetParameter(value, node, reader);
                            securityDomainMatched = true;
                            break;
                        }
                        case SECURITY_DOMAIN_AND_APPLICATION: {
                            String value = rawElementText(reader);
                            SECURITY_DOMAIN_AND_APPLICATION.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case ELYTRON_ENABLED: {
                            String value = rawElementText(reader);
                            value = value == null? "true": value;
                            ELYTRON_ENABLED.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case AUTHENTICATION_CONTEXT: {
                            String value = rawElementText(reader);
                            AUTHENTICATION_CONTEXT.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case AUTHENTICATION_CONTEXT_AND_APPLICATION: {
                            String value = rawElementText(reader);
                            AUTHENTICATION_CONTEXT_AND_APPLICATION.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case APPLICATION: {
                            String value = rawElementText(reader);
                            // just presence means true
                            value = value == null ? "true" : value;
                            APPLICATION.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        default:
                            throw ParseUtils.unexpectedElement(reader);
                    }
                    break;
                }
            }
        }
        throw ParseUtils.unexpectedEndElement(reader);
    }

    private void parseRecoveryCredential(XMLExtendedStreamReader reader, ModelNode node) throws XMLStreamException, ParserException,
            ValidateException {

        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case END_ELEMENT: {
                    if (DataSource.Tag.forName(reader.getLocalName()) == DataSource.Tag.SECURITY ||
                            Recovery.Tag.forName(reader.getLocalName()) == Recovery.Tag.RECOVER_CREDENTIAL) {

                        return;
                    } else {
                        if (Credential.Tag.forName(reader.getLocalName()) == Credential.Tag.UNKNOWN) {
                            throw ParseUtils.unexpectedEndElement(reader);
                        }
                    }
                    break;
                }
                case START_ELEMENT: {
                    switch (Credential.Tag.forName(reader.getLocalName())) {
                        case PASSWORD: {
                            String value = rawElementText(reader);
                            RECOVERY_PASSWORD.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case USER_NAME: {
                            String value = rawElementText(reader);
                            RECOVERY_USERNAME.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case CREDENTIAL_REFERENCE: {
                            RECOVERY_CREDENTIAL_REFERENCE.getParser().parseAndSetParameter(RECOVERY_CREDENTIAL_REFERENCE, null, node, reader);
                        }
                        case SECURITY_DOMAIN: {
                            String value = rawElementText(reader);
                            RECOVERY_SECURITY_DOMAIN.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        default:
                            throw ParseUtils.unexpectedElement(reader);
                    }
                    break;
                }
            }
        }
        throw ParseUtils.unexpectedEndElement(reader);
    }

    private void parseElytronSupportedRecoveryCredential(XMLExtendedStreamReader reader, ModelNode node)
            throws XMLStreamException, ParserException, ValidateException {

        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case END_ELEMENT: {
                    if (DataSource.Tag.forName(reader.getLocalName()) == DataSource.Tag.SECURITY
                            || Recovery.Tag.forName(reader.getLocalName()) == Recovery.Tag.RECOVER_CREDENTIAL) {

                        return;
                    } else {
                        if (Credential.Tag.forName(reader.getLocalName()) == Credential.Tag.UNKNOWN) {
                            throw ParseUtils.unexpectedEndElement(reader);
                        }
                    }
                    break;
                }
                case START_ELEMENT: {
                    switch (Credential.Tag.forName(reader.getLocalName())) {
                        case PASSWORD: {
                            String value = rawElementText(reader);
                            RECOVERY_PASSWORD.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case CREDENTIAL_REFERENCE: {
                            RECOVERY_CREDENTIAL_REFERENCE.getParser().parseAndSetParameter(RECOVERY_CREDENTIAL_REFERENCE, null, node, reader);
                            break;
                        }
                        case USER_NAME: {
                            String value = rawElementText(reader);
                            RECOVERY_USERNAME.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case SECURITY_DOMAIN: {
                            String value = rawElementText(reader);
                            RECOVERY_SECURITY_DOMAIN.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case ELYTRON_ENABLED: {
                            String value = rawElementText(reader);
                            value = value == null? "true": value;
                            RECOVERY_ELYTRON_ENABLED.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        case AUTHENTICATION_CONTEXT: {
                            String value = rawElementText(reader);
                            RECOVERY_AUTHENTICATION_CONTEXT.parseAndSetParameter(value, node, reader);
                            break;
                        }
                        default:
                            throw ParseUtils.unexpectedElement(reader);
                    }
                    break;
                }
            }
        }
        throw ParseUtils.unexpectedEndElement(reader);
    }

}


/*
 * 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.ee.component;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import org.jboss.as.ee.logging.EeLogger;
import org.jboss.as.ee.component.interceptors.InvocationType;
import org.jboss.as.naming.ImmediateManagedReference;
import org.jboss.as.naming.ManagedReference;
import org.jboss.as.naming.context.NamespaceContextSelector;
import org.jboss.invocation.Interceptor;
import org.jboss.invocation.InterceptorContext;
import org.jboss.invocation.InterceptorFactory;
import org.jboss.invocation.InterceptorFactoryContext;
import org.jboss.invocation.SimpleInterceptorFactoryContext;
import org.jboss.msc.service.ServiceName;

/**
 * A basic component implementation.
 *
 * @author John Bailey
 * @author David M. Lloyd
 */
public class BasicComponent implements Component {

    private final String componentName;
    private final Class<?> componentClass;
    private final InterceptorFactory postConstruct;
    private final InterceptorFactory preDestroy;
    private final Map<Method, InterceptorFactory> interceptorFactoryMap;
    private final NamespaceContextSelector namespaceContextSelector;
    private final ServiceName createServiceName;

    private volatile boolean gate;
    private final AtomicBoolean stopping = new AtomicBoolean();

    private Interceptor postConstructInterceptor;
    private Interceptor preDestroyInterceptor;
    private Map<Method, Interceptor> interceptorInstanceMap;

    /**
     * Construct a new instance.
     *
     * @param createService the create service which created this component
     */
    public BasicComponent(final BasicComponentCreateService createService) {
        componentName = createService.getComponentName();
        componentClass = createService.getComponentClass();
        postConstruct = createService.getPostConstruct();
        preDestroy = createService.getPreDestroy();
        interceptorFactoryMap = createService.getComponentInterceptors();
        namespaceContextSelector = createService.getNamespaceContextSelector();
        createServiceName = createService.getServiceName();
    }

    /**
     * {@inheritDoc}
     */
    public ComponentInstance createInstance() {
        BasicComponentInstance instance = constructComponentInstance(null, true);
        return instance;
    }

    /**
     * Wraps an existing object instance in a ComponentInstance, and run the post construct interceptor chain on it.
     *
     * @param instance The instance to wrap
     * @return The new ComponentInstance
     */
    public ComponentInstance createInstance(Object instance) {
        BasicComponentInstance obj = constructComponentInstance(new ImmediateManagedReference(instance), true);
        obj.constructionFinished();
        return obj;
    }

    public void waitForComponentStart() {
        if (!gate) {
            EeLogger.ROOT_LOGGER.tracef("Waiting for component %s (%s)", componentName, componentClass);
            // Block until successful start
            synchronized (this) {
                if (stopping.get()) {
                    throw EeLogger.ROOT_LOGGER.componentIsStopped();
                }
                while (!gate) {
                    // TODO: check for failure condition
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        throw EeLogger.ROOT_LOGGER.componentNotAvailable();
                    }
                }
            }
            EeLogger.ROOT_LOGGER.tracef("Finished waiting for component %s (%s)", componentName, componentClass);
        }
    }
    /**
     * Construct the component instance.  Upon return, the object instance should have injections and lifecycle
     * invocations completed already.
     *
     *
     * @param instance An instance to be wrapped, or null if a new instance should be created
     * @return the component instance
     */
    protected BasicComponentInstance constructComponentInstance(ManagedReference instance, boolean invokePostConstruct) {
        return constructComponentInstance(instance, invokePostConstruct, Collections.emptyMap());
    }

    /**
     * Construct the component instance.  Upon return, the object instance should have injections and lifecycle
     * invocations completed already.
     *
     *
     * @param instance An instance to be wrapped, or null if a new instance should be created
     * @return the component instance
     */
    protected BasicComponentInstance constructComponentInstance(ManagedReference instance, boolean invokePostConstruct, final Map<Object, Object> context) {
        waitForComponentStart();
        // create the component instance
        final BasicComponentInstance basicComponentInstance = this.instantiateComponentInstance(preDestroyInterceptor, interceptorInstanceMap, context);
        if(instance != null) {
            basicComponentInstance.setInstanceData(BasicComponentInstance.INSTANCE_KEY, instance);
        }
        if (invokePostConstruct) {
            // now invoke the postconstruct interceptors
            final InterceptorContext interceptorContext = new InterceptorContext();
            interceptorContext.putPrivateData(Component.class, this);
            interceptorContext.putPrivateData(ComponentInstance.class, basicComponentInstance);
            interceptorContext.putPrivateData(InvocationType.class, InvocationType.POST_CONSTRUCT);
            interceptorContext.setContextData(new HashMap<String, Object>());

            try {
                postConstructInterceptor.processInvocation(interceptorContext);
            } catch (Exception e) {
                throw EeLogger.ROOT_LOGGER.componentConstructionFailure(e);
            }
        }
        componentInstanceCreated(basicComponentInstance);
        // return the component instance
        return basicComponentInstance;
    }

    /**
     * Method that can be overridden to perform setup on the instance after it has been created
     *
     * @param basicComponentInstance The component instance
     *
     */
    protected void componentInstanceCreated(final BasicComponentInstance basicComponentInstance) {

    }

    /**
     * Responsible for instantiating the {@link BasicComponentInstance}. This method is *not* responsible for
     * handling the post construct activities like injection and lifecycle invocation. That is handled by
     * {@link #constructComponentInstance(org.jboss.as.naming.ManagedReference, boolean)}.
     *

     *
     * @return the component instance
     */
    protected BasicComponentInstance instantiateComponentInstance(final Interceptor preDestroyInterceptor, final Map<Method, Interceptor> methodInterceptors, Map<Object, Object> context) {
        // create and return the component instance
        return new BasicComponentInstance(this, preDestroyInterceptor, methodInterceptors);

    }

    /**
     * Get the class of this bean component.
     *
     * @return the class
     */
    public Class<?> getComponentClass() {
        return componentClass;
    }

    /**
     * Get the name of this bean component.
     *
     * @return the component name
     */
    public String getComponentName() {
        return componentName;
    }

    public ServiceName getCreateServiceName() {
        return createServiceName;
    }

    /**
     * {@inheritDoc}
     */
    public synchronized void start() {
        init();

        this.stopping.set(false);
        gate = true;
        notifyAll();
    }

    public synchronized void init() {
        final InterceptorFactoryContext context = new SimpleInterceptorFactoryContext();
        context.getContextData().put(Component.class, this);
        createInterceptors(context);
    }

    protected void createInterceptors(InterceptorFactoryContext context) {
        // Create the post-construct interceptors for the ComponentInstance
        postConstructInterceptor = this.postConstruct.create(context);
        // create the pre-destroy interceptors
        preDestroyInterceptor = this.getPreDestroy().create(context);

        final Map<Method, InterceptorFactory> interceptorFactoryMap = this.getInterceptorFactoryMap();
        // This is an identity map.  This means that only certain {@code Method} objects will
        // match - specifically, they must equal the objects provided to the proxy.
        final IdentityHashMap<Method, Interceptor> interceptorMap = new IdentityHashMap<Method, Interceptor>();
        for (Method method : interceptorFactoryMap.keySet()) {
            interceptorMap.put(method, interceptorFactoryMap.get(method).create(context));
        }
        this.interceptorInstanceMap = interceptorMap;
    }

    /**
     * {@inheritDoc}
     */
    public void stop() {
        if (stopping.compareAndSet(false, true)) {
            synchronized (this) {
                gate = false;
                this.interceptorInstanceMap = null;
                this.preDestroyInterceptor = null;
                this.postConstructInterceptor = null;
            }
            //TODO: only run this if there is no instances
            //TODO: trigger destruction of all component instances
            //TODO: this has lots of potential for race conditions unless we are careful
            //TODO: using stopContext.asynchronous() and then executing synchronously is pointless.
            // Use org.jboss.as.server.Services#addServerExecutorDependency to inject an executor to do this async
        }
    }

    Map<Method, InterceptorFactory> getInterceptorFactoryMap() {
        return interceptorFactoryMap;
    }

    InterceptorFactory getPreDestroy() {
        return preDestroy;
    }

    void finishDestroy() {

    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + " " + componentName;
    }

    /**
     * @return The components namespace context selector, or null if it does not have one
     */
    @Override
    public NamespaceContextSelector getNamespaceContextSelector() {
        return namespaceContextSelector;
    }

    public static ServiceName serviceNameOf(final ServiceName deploymentUnitServiceName, final String componentName) {
        return deploymentUnitServiceName.append("component").append(componentName);
    }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, Red Hat Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt 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.ee.component.interceptors;

import org.jboss.invocation.proxy.MethodIdentifier;

/**
 * Post class loading description of an interceptor class.
 *
 * @author Stuart Douglas
 */
public class InterceptorClassDescription {

    public static final InterceptorClassDescription EMPTY_INSTANCE = new Builder().build();
    private final MethodIdentifier aroundInvoke;
    private final MethodIdentifier aroundTimeout;
    private final MethodIdentifier aroundConstruct;
    private final MethodIdentifier preDestroy;
    private final MethodIdentifier postConstruct;
    private final MethodIdentifier prePassivate;
    private final MethodIdentifier postActivate;

    public InterceptorClassDescription(final MethodIdentifier aroundInvoke, final MethodIdentifier aroundTimeout, final MethodIdentifier aroundConstruct, final MethodIdentifier preDestroy, final MethodIdentifier postConstruct, final MethodIdentifier postActivate, final MethodIdentifier prePassivate) {
        this.aroundInvoke = aroundInvoke;
        this.aroundTimeout = aroundTimeout;
        this.aroundConstruct = aroundConstruct;
        this.preDestroy = preDestroy;
        this.postConstruct = postConstruct;
        this.postActivate = postActivate;
        this.prePassivate = prePassivate;
    }

    /**
     * Merges two descriptors, either of the parameters will be null.
     * <p/>
     * this method will never return null;
     *
     * @param existing
     * @param override
     * @return
     */
    public static InterceptorClassDescription merge(InterceptorClassDescription existing, InterceptorClassDescription override) {
        if (existing == null && override == null) {
            return EMPTY_INSTANCE;
        }
        if (override == null) {
            return existing;
        }
        if (existing == null) {
            return override;
        }
        final Builder builder = builder(existing);
        if (override.getAroundInvoke() != null) {
            builder.setAroundInvoke(override.getAroundInvoke());
        }
        if (override.getAroundTimeout() != null) {
            builder.setAroundTimeout(override.getAroundTimeout());
        }
        if (override.getAroundConstruct() != null) {
            builder.setAroundConstruct(override.getAroundConstruct());
        }
        if (override.getPostConstruct() != null) {
            builder.setPostConstruct(override.getPostConstruct());
        }
        if (override.getPreDestroy() != null) {
            builder.setPreDestroy(override.getPreDestroy());
        }
        if (override.getPrePassivate() != null) {
            builder.setPrePassivate(override.getPrePassivate());
        }
        if (override.getPostActivate() != null) {
            builder.setPostActivate(override.getPostActivate());
        }
        return builder.build();
    }

    /**
     * Constructs a new empty builder
     *
     * @return An empty builder
     */
    public static Builder builder() {
        return new Builder();
    }

    /**
     * @param base The existing description, or null for an empty builder
     * @return A builder based on the existing description
     */
    public static Builder builder(InterceptorClassDescription base) {
        if (base == null) {
            return new Builder();
        }
        return new Builder(base);
    }

    public static class Builder {

        private MethodIdentifier aroundInvoke;
        private MethodIdentifier aroundTimeout;
        private MethodIdentifier aroundConstruct;
        private MethodIdentifier preDestroy;
        private MethodIdentifier postConstruct;
        private MethodIdentifier prePassivate;
        private MethodIdentifier postActivate;

        Builder() {

        }

        Builder(InterceptorClassDescription existing) {
            this.aroundInvoke = existing.aroundInvoke;
            this.aroundTimeout = existing.aroundTimeout;
            this.aroundConstruct = existing.aroundConstruct;
            this.preDestroy = existing.preDestroy;
            this.postConstruct = existing.postConstruct;
            this.prePassivate = existing.prePassivate;
            this.postActivate = existing.postActivate;
        }

        public InterceptorClassDescription build() {
            return new InterceptorClassDescription(aroundInvoke, aroundTimeout, aroundConstruct, preDestroy, postConstruct, postActivate, prePassivate);
        }

        public MethodIdentifier getAroundInvoke() {
            return aroundInvoke;
        }

        public void setAroundInvoke(final MethodIdentifier aroundInvoke) {
            this.aroundInvoke = aroundInvoke;
        }

        public MethodIdentifier getAroundTimeout() {
            return aroundTimeout;
        }

        public void setAroundTimeout(final MethodIdentifier aroundTimeout) {
            this.aroundTimeout = aroundTimeout;
        }

        public MethodIdentifier getPostConstruct() {
            return postConstruct;
        }

        public void setPostConstruct(final MethodIdentifier postConstruct) {
            this.postConstruct = postConstruct;
        }

        public MethodIdentifier getPreDestroy() {
            return preDestroy;
        }

        public void setPreDestroy(final MethodIdentifier preDestroy) {
            this.preDestroy = preDestroy;
        }

        public MethodIdentifier getPrePassivate() {
            return prePassivate;
        }

        public void setPrePassivate(final MethodIdentifier prePassivate) {
            this.prePassivate = prePassivate;
        }

        public MethodIdentifier getPostActivate() {
            return postActivate;
        }

        public void setPostActivate(final MethodIdentifier postActivate) {
            this.postActivate = postActivate;
        }

        public MethodIdentifier getAroundConstruct() {
            return aroundConstruct;
        }

        public void setAroundConstruct(final MethodIdentifier aroundConstruct) {
            this.aroundConstruct = aroundConstruct;
        }
    }

    public MethodIdentifier getAroundInvoke() {
        return aroundInvoke;
    }

    public MethodIdentifier getAroundTimeout() {
        return aroundTimeout;
    }

    public MethodIdentifier getPostConstruct() {
        return postConstruct;
    }

    public MethodIdentifier getPreDestroy() {
        return preDestroy;
    }

    public MethodIdentifier getPrePassivate() {
        return prePassivate;
    }

    public MethodIdentifier getPostActivate() {
        return postActivate;
    }

    public MethodIdentifier getAroundConstruct() {
        return aroundConstruct;
    }
}


/*
 * 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.jaxrs.deployment;

import org.jboss.as.jaxrs.logging.JaxrsLogger;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jboss.as.server.deployment.module.ModuleRootMarker;
import org.jboss.as.server.deployment.module.MountHandle;
import org.jboss.as.server.deployment.module.ResourceRoot;
import org.jboss.as.server.deployment.module.TempFileProviderService;
import org.jboss.as.web.common.WarMetaData;
import org.jboss.metadata.javaee.spec.ParamValueMetaData;
import org.jboss.metadata.web.jboss.JBossServletMetaData;
import org.jboss.metadata.web.jboss.JBossWebMetaData;
import org.jboss.metadata.web.spec.ListenerMetaData;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.msc.service.Service;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.ServiceTarget;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StartException;
import org.jboss.msc.service.StopContext;
import org.jboss.vfs.VFS;
import org.jboss.vfs.VFSUtils;
import org.jboss.vfs.VirtualFile;

import java.io.Closeable;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * Recognize Spring deployment and add the JAX-RS integration to it
 */
public class JaxrsSpringProcessor implements DeploymentUnitProcessor {

    private static final String JAR_LOCATION = "resteasy-spring-jar";
    private static final ModuleIdentifier MODULE = ModuleIdentifier.create("org.jboss.resteasy.resteasy-spring");

    public static final String SPRING_LISTENER = "org.jboss.resteasy.plugins.spring.SpringContextLoaderListener";
    public static final String SPRING_SERVLET = "org.springframework.web.servlet.DispatcherServlet";
    @Deprecated
    public static final String DISABLE_PROPERTY = "org.jboss.as.jaxrs.disableSpringIntegration";
    public static final String ENABLE_PROPERTY = "org.jboss.as.jaxrs.enableSpringIntegration";
    public static final String SERVICE_NAME = "resteasy-spring-integration-resource-root";

    private final ServiceTarget serviceTarget;
    private VirtualFile resourceRoot;

    public JaxrsSpringProcessor(ServiceTarget serviceTarget) {
        this.serviceTarget = serviceTarget;
    }

    /**
     * Lookup Seam integration resource loader.
     *
     * @return the Seam integration resource loader
     * @throws DeploymentUnitProcessingException
     *          for any error
     */
    protected synchronized VirtualFile getResteasySpringVirtualFile() throws DeploymentUnitProcessingException {
        if(resourceRoot != null) {
            return resourceRoot;
        }
        try {
            Module module = Module.getBootModuleLoader().loadModule(MODULE);
            URL fileUrl = module.getClassLoader().getResource(JAR_LOCATION);

            if (fileUrl == null) {
                throw JaxrsLogger.JAXRS_LOGGER.noSpringIntegrationJar();
            }
            File dir = new File(fileUrl.toURI());
            File file = null;
            for (String jar : dir.list()) {
                if (jar.endsWith(".jar")) {
                    file = new File(dir, jar);
                    break;
                }
            }
            if (file == null) {
                throw JaxrsLogger.JAXRS_LOGGER.noSpringIntegrationJar();
            }
            VirtualFile vf = VFS.getChild(file.toURI());
            final Closeable mountHandle = VFS.mountZip(file, vf, TempFileProviderService.provider());
            Service<Closeable> mountHandleService = new Service<Closeable>() {
                public void start(StartContext startContext) throws StartException {
                }

                public void stop(StopContext stopContext) {
                    VFSUtils.safeClose(mountHandle);
                }

                public Closeable getValue() throws IllegalStateException, IllegalArgumentException {
                    return mountHandle;
                }
            };
            ServiceBuilder<Closeable> builder = serviceTarget.addService(ServiceName.JBOSS.append(SERVICE_NAME),
                    mountHandleService);
            builder.setInitialMode(ServiceController.Mode.ACTIVE).install();
            resourceRoot = vf;

            return resourceRoot;
        } catch (Exception e) {
            throw new DeploymentUnitProcessingException(e);
        }
    }

    public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
        DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
        if (deploymentUnit.getParent() != null) {
            return;
        }

        final List<DeploymentUnit> deploymentUnits = new ArrayList<DeploymentUnit>();
        deploymentUnits.add(deploymentUnit);
        deploymentUnits.addAll(deploymentUnit.getAttachmentList(Attachments.SUB_DEPLOYMENTS));

        boolean found = false;
        for (DeploymentUnit unit : deploymentUnits) {

            WarMetaData warMetaData = unit.getAttachment(WarMetaData.ATTACHMENT_KEY);
            if (warMetaData == null) {
                continue;
            }
            JBossWebMetaData md = warMetaData.getMergedJBossWebMetaData();
            if (md == null) {
                continue;
            }
            if (md.getContextParams() != null) {
                boolean skip = false;
                for (ParamValueMetaData prop : md.getContextParams()) {
                    if (prop.getParamName().equals(ENABLE_PROPERTY)) {
                        boolean explicitEnable = Boolean.parseBoolean(prop.getParamValue());
                        if (explicitEnable) {
                            found = true;
                        } else {
                            skip = true;
                        }
                        break;
                    } else if (prop.getParamName().equals(DISABLE_PROPERTY) && "true".equals(prop.getParamValue())) {
                        skip = true;
                        JaxrsLogger.JAXRS_LOGGER.disablePropertyDeprecated();
                        break;
                    }
                }
                if (skip) {
                    continue;
                }
            }

            if (md.getListeners() != null) {
                for (ListenerMetaData listener : md.getListeners()) {
                    if (SPRING_LISTENER.equals(listener.getListenerClass())) {
                        found = true;
                        break;
                    }
                }
            }
            if (md.getServlets() != null) {
                for (JBossServletMetaData servlet : md.getServlets()) {
                    if (SPRING_SERVLET.equals(servlet.getServletClass())) {
                        found = true;
                        break;
                    }
                }
            }
            if (found) {
                try {
                    MountHandle mh = new MountHandle(null); // actual close is done by the MSC service above
                    ResourceRoot resourceRoot = new ResourceRoot(getResteasySpringVirtualFile(), mh);
                    ModuleRootMarker.mark(resourceRoot);
                    deploymentUnit.addToAttachmentList(Attachments.RESOURCE_ROOTS, resourceRoot);
                } catch (Exception e) {
                    throw new DeploymentUnitProcessingException(e);
                }
                return;
            }
        }
    }

    public void undeploy(DeploymentUnit context) {
    }
}