/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package flex.messaging.config;

import flex.messaging.config.ThrottleSettings.Policy;
import flex.messaging.util.LocaleUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;

/**
 * Processes DOM representation of a messaging configuration file.
 * <p>
 * Note: Since reference ids are used between elements, certain
 * sections of the document need to be parsed first.
 * </p>
 */
public abstract class ServerConfigurationParser extends AbstractConfigurationParser {
    /**
     * Used to verify that advanced messaging support has been registered if necessary.
     * If other configuration requires it, but it was not registered a ConfigurationException is thrown.
     */
    private boolean verifyAdvancedMessagingSupport = false;
    private boolean advancedMessagingSupportRegistered = false;

    @Override
    protected void parseTopLevelConfig(Document doc) {
        Node root = selectSingleNode(doc, "/" + SERVICES_CONFIG_ELEMENT);

        if (root != null) {
            allowedChildElements(root, SERVICES_CONFIG_CHILDREN);

            securitySection(root); // Parse security before channels.

            serversSection(root);

            channelsSection(root);

            services(root);

            clusters(root);

            logging(root);

            system(root);

            flexClient(root);

            factories(root);

            messageFilters(root);

            validators(root);

            // Validate that any dependencies on advanced messaging support can be satisified at runtime.
            if (verifyAdvancedMessagingSupport && !advancedMessagingSupportRegistered) {
                ConfigurationException e = new ConfigurationException();
                e.setMessage(REQUIRE_ADVANCED_MESSAGING_SUPPORT);
                throw e;
            }
        } else {
            // The services configuration root element must be '{SERVICES_CONFIG_ELEMENT}'.
            ConfigurationException e = new ConfigurationException();
            e.setMessage(INVALID_SERVICES_ROOT, new Object[]{SERVICES_CONFIG_ELEMENT});
            throw e;
        }
    }

    private void clusters(Node root) {
        Node clusteringNode = selectSingleNode(root, CLUSTERS_ELEMENT);
        if (clusteringNode != null) {
            allowedAttributesOrElements(clusteringNode, CLUSTERING_CHILDREN);

            NodeList clusters = selectNodeList(clusteringNode, CLUSTER_DEFINITION_ELEMENT);
            for (int i = 0; i < clusters.getLength(); i++) {
                Node cluster = clusters.item(i);
                requiredAttributesOrElements(cluster, CLUSTER_DEFINITION_CHILDREN);
                String clusterName = getAttributeOrChildElement(cluster, ID_ATTR);
                if (!isValidID(clusterName))
                    continue;

                String propsFileName = getAttributeOrChildElement(cluster, CLUSTER_PROPERTIES_ATTR);
                ClusterSettings clusterSettings = new ClusterSettings();
                clusterSettings.setClusterName(clusterName);
                clusterSettings.setPropsFileName(propsFileName);
                String className = getAttributeOrChildElement(cluster, CLASS_ATTR);
                if (className != null && className.length() > 0)
                    clusterSettings.setImplementationClass(className);

                String defaultValue = getAttributeOrChildElement(cluster, ClusterSettings.DEFAULT_ELEMENT);
                if (defaultValue != null && defaultValue.length() > 0) {
                    if (defaultValue.equalsIgnoreCase(TRUE_STRING))
                        clusterSettings.setDefault(true);
                    else if (!defaultValue.equalsIgnoreCase(FALSE_STRING)) {
                        ConfigurationException e = new ConfigurationException();
                        e.setMessage(10215, new Object[]{clusterName, defaultValue});
                        throw e;
                    }
                }
                String ulb = getAttributeOrChildElement(cluster, ClusterSettings.URL_LOAD_BALANCING);
                if (ulb != null && ulb.length() > 0) {
                    if (ulb.equalsIgnoreCase(FALSE_STRING)) {
                        clusterSettings.setURLLoadBalancing(false);
                    } else if (!ulb.equalsIgnoreCase(TRUE_STRING)) {
                        ConfigurationException e = new ConfigurationException();
                        e.setMessage(10216, new Object[]{clusterName, ulb});
                        throw e;
                    }
                }

                NodeList properties = selectNodeList(cluster, PROPERTIES_ELEMENT + "/*");
                if (properties.getLength() > 0) {
                    ConfigMap map = properties(properties, getSourceFileOf(cluster));
                    clusterSettings.addProperties(map);
                }

                ((MessagingConfiguration) config).addClusterSettings(clusterSettings);

            }
        }
    }


    private void securitySection(Node root) {
        Node security = selectSingleNode(root, SECURITY_ELEMENT);

        if (security == null)
            return;

        allowedChildElements(security, SECURITY_CHILDREN);

        NodeList list = selectNodeList(security, SECURITY_CONSTRAINT_DEFINITION_ELEMENT);
        for (int i = 0; i < list.getLength(); i++) {
            Node constraint = list.item(i);
            securityConstraint(constraint, false);
        }

        list = selectNodeList(security, CONSTRAINT_INCLUDE_ELEMENT);
        for (int i = 0; i < list.getLength(); i++) {
            Node include = list.item(i);
            securityConstraintInclude(include);
        }

        list = selectNodeList(security, LOGIN_COMMAND_ELEMENT);
        for (int i = 0; i < list.getLength(); i++) {
            Node login = list.item(i);
            LoginCommandSettings loginCommandSettings = new LoginCommandSettings();
            requiredAttributesOrElements(login, LOGIN_COMMAND_REQ_CHILDREN);
            allowedAttributesOrElements(login, LOGIN_COMMAND_CHILDREN);

            String server = getAttributeOrChildElement(login, SERVER_ATTR);
            if (server.length() == 0) {
                // Attribute '{SERVER_ATTR}' must be specified for element '{LOGIN_COMMAND_ELEMENT}'
                ConfigurationException e = new ConfigurationException();
                e.setMessage(MISSING_ATTRIBUTE, new Object[]{SERVER_ATTR, LOGIN_COMMAND_ELEMENT});
                throw e;
            }
            loginCommandSettings.setServer(server);

            String loginClass = getAttributeOrChildElement(login, CLASS_ATTR);
            if (loginClass.length() == 0) {
                // Attribute '{CLASS_ATTR}' must be specified for element '{LOGIN_COMMAND_ELEMENT}'
                ConfigurationException e = new ConfigurationException();
                e.setMessage(MISSING_ATTRIBUTE, new Object[]{CLASS_ATTR, LOGIN_COMMAND_ELEMENT});
                throw e;
            }
            loginCommandSettings.setClassName(loginClass);

            boolean isPerClientAuth = Boolean.valueOf(getAttributeOrChildElement(login, PER_CLIENT_AUTH));
            loginCommandSettings.setPerClientAuthentication(isPerClientAuth);

            ((MessagingConfiguration) config).getSecuritySettings().addLoginCommandSettings(loginCommandSettings);
        }

        boolean recreateHttpSessionAfterLogin = Boolean.valueOf(getAttributeOrChildElement(security, RECREATE_HTTPSESSION_AFTER_LOGIN_ELEMENT));
        ((MessagingConfiguration) config).getSecuritySettings().setRecreateHttpSessionAfterLogin(recreateHttpSessionAfterLogin);
    }

    private SecurityConstraint securityConstraint(Node constraint, boolean inline) {
        SecurityConstraint sc;

        // Validation
        allowedAttributesOrElements(constraint, SECURITY_CONSTRAINT_DEFINITION_CHILDREN);

        // Constraint by reference
        String ref = getAttributeOrChildElement(constraint, REF_ATTR);
        if (ref.length() > 0) {
            allowedAttributesOrElements(constraint, new String[]{REF_ATTR});

            sc = ((MessagingConfiguration) config).getSecuritySettings().getConstraint(ref);
            if (sc == null) {
                // {SECURITY_CONSTRAINT_DEFINITION_ELEMENT} not found for reference '{ref}'.
                ConfigurationException e = new ConfigurationException();
                e.setMessage(REF_NOT_FOUND, new Object[]{SECURITY_CONSTRAINT_DEFINITION_ELEMENT, ref});
                throw e;
            }
        } else {
            // New security constraint
            String id = getAttributeOrChildElement(constraint, ID_ATTR);

            // If not inline, we must have a valid id to register the constraint!
            if (inline) {
                sc = new SecurityConstraint("");
            } else if (isValidID(id)) {
                sc = new SecurityConstraint(id);
                ((MessagingConfiguration) config).getSecuritySettings().addConstraint(sc);
            } else {
                //Invalid {SECURITY_CONSTRAINT_DEFINITION_ELEMENT} id '{id}'.
                ConfigurationException ex = new ConfigurationException();
                ex.setMessage(INVALID_ID, new Object[]{SECURITY_CONSTRAINT_DEFINITION_ELEMENT, id});
                ex.setDetails(INVALID_ID);
                throw ex;
            }

            // Authentication Method
            String method = getAttributeOrChildElement(constraint, AUTH_METHOD_ELEMENT);
            sc.setMethod(method);

            // Roles
            Node rolesNode = selectSingleNode(constraint, ROLES_ELEMENT);
            if (rolesNode != null) {
                allowedChildElements(rolesNode, ROLES_CHILDREN);
                NodeList roles = selectNodeList(rolesNode, ROLE_ELEMENT);
                for (int r = 0; r < roles.getLength(); r++) {
                    Node roleNode = roles.item(r);
                    String role = evaluateExpression(roleNode, ".").toString().trim();
                    if (role.length() > 0) {
                        sc.addRole(role);
                    }
                }
            }
        }

        return sc;
    }

    private void securityConstraintInclude(Node constraintInclude) {
        // Validation
        allowedAttributesOrElements(constraintInclude, CONSTRAINT_INCLUDE_CHILDREN);

        String src = getAttributeOrChildElement(constraintInclude, SRC_ATTR);
        String dir = getAttributeOrChildElement(constraintInclude, DIRECTORY_ATTR);
        if (src.length() > 0) {
            constraintIncludeFile(src);
        } else if (dir.length() > 0) {
            constraintIncludeDirectory(dir);
        } else {
            // The include element ''{0}'' must specify either the ''{1}'' or ''{2}'' attribute.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(MISSING_INCLUDE_ATTRIBUTES, new Object[]{constraintInclude.getNodeName(), SRC_ATTR, DIRECTORY_ATTR});
            throw ex;
        }
    }

    private void constraintIncludeFile(String src) {
        Document doc = loadDocument(src, fileResolver.getIncludedFile(src));
        doc.getDocumentElement().normalize();

        // Check for <security-constraints> wrapping more than one definition
        Node servicesNode = selectSingleNode(doc, SECURITY_CONSTRAINTS_ELEMENT);
        if (servicesNode != null) {
            allowedChildElements(servicesNode, SECURITY_CONSTRAINTS_CHILDREN);
            NodeList constraints = selectNodeList(servicesNode, SECURITY_CONSTRAINT_ELEMENT);
            for (int a = 0; a < constraints.getLength(); a++) {
                Node constraint = constraints.item(a);
                securityConstraint(constraint, false);
            }
            fileResolver.popIncludedFile();
        } else // Check for single <security-constraint>
        {
            Node constraint = selectSingleNode(doc, "/" + SECURITY_CONSTRAINT_ELEMENT);
            if (constraint != null) {
                securityConstraint(constraint, false);
                fileResolver.popIncludedFile();
            } else {
                // The {0} root element in file {1} must be ''{2}'' or ''{3}''.
                ConfigurationException ex = new ConfigurationException();
                ex.setMessage(INVALID_INCLUDE_ROOT, new Object[]{CONSTRAINT_INCLUDE_ELEMENT, src, SECURITY_CONSTRAINTS_ELEMENT, SECURITY_CONSTRAINT_ELEMENT});
                throw ex;
            }
        }
    }

    private void constraintIncludeDirectory(String dir) {
        List files = fileResolver.getFiles(dir);
        for (Object file : files) {
            String src = (String) file;
            constraintIncludeFile(src);
        }
    }

    private void serversSection(Node root) {
        // Only MessagingConfiguration supports the servers element configuration.
        // The general ServicesConfiguration interface does not.
        if (!(config instanceof MessagingConfiguration))
            return;

        Node serversNode = selectSingleNode(root, SERVERS_ELEMENT);
        if (serversNode != null) {
            // Validation
            allowedAttributesOrElements(serversNode, SERVERS_CHILDREN);

            NodeList servers = selectNodeList(serversNode, SERVER_ELEMENT);
            for (int i = 0; i < servers.getLength(); i++) {
                Node server = servers.item(i);
                serverDefinition(server);
            }
        }
    }

    private void serverDefinition(Node server) {
        // Validation
        requiredAttributesOrElements(server, SERVER_REQ_CHILDREN);
        allowedAttributesOrElements(server, SERVER_CHILDREN);

        String id = getAttributeOrChildElement(server, ID_ATTR);
        if (isValidID(id)) {
            SharedServerSettings settings = new SharedServerSettings();
            settings.setId(id);
            settings.setSourceFile(getSourceFileOf(server));
            String className = getAttributeOrChildElement(server, CLASS_ATTR);
            if (className.length() > 0) {
                settings.setClassName(className);
                // Custom server properties.
                NodeList properties = selectNodeList(server, PROPERTIES_ELEMENT + "/*");
                if (properties.getLength() > 0) {
                    ConfigMap map = properties(properties, getSourceFileOf(server));
                    settings.addProperties(map);
                }
                ((MessagingConfiguration) config).addSharedServerSettings(settings);
            } else {
                // Class not specified for {MESSAGE_FILTER_ELEMENT} '{id}'.
                ConfigurationException ex = new ConfigurationException();
                ex.setMessage(CLASS_NOT_SPECIFIED, new Object[]{SERVER_ELEMENT, id});
                throw ex;
            }
        }
    }

    private void channelsSection(Node root) {
        Node channelsNode = selectSingleNode(root, CHANNELS_ELEMENT);
        if (channelsNode != null) {
            // Validation
            allowedAttributesOrElements(channelsNode, CHANNELS_CHILDREN);

            NodeList channels = selectNodeList(channelsNode, CHANNEL_DEFINITION_ELEMENT);
            for (int i = 0; i < channels.getLength(); i++) {
                Node channel = channels.item(i);
                channelDefinition(channel);
            }

            NodeList includes = selectNodeList(channelsNode, CHANNEL_INCLUDE_ELEMENT);
            for (int i = 0; i < includes.getLength(); i++) {
                Node include = includes.item(i);
                channelInclude(include);
            }
        }
    }

    private void channelDefinition(Node channel) {
        // Validation
        requiredAttributesOrElements(channel, CHANNEL_DEFINITION_REQ_CHILDREN);
        allowedAttributesOrElements(channel, CHANNEL_DEFINITION_CHILDREN);

        String id = getAttributeOrChildElement(channel, ID_ATTR);
        if (isValidID(id)) {
            // Don't allow multiple channels with the same id
            if (config.getChannelSettings(id) != null) {
                // Cannot have multiple channels with the same id ''{0}''.
                ConfigurationException e = new ConfigurationException();
                e.setMessage(DUPLICATE_CHANNEL_ERROR, new Object[]{id});
                throw e;
            }

            ChannelSettings channelSettings = new ChannelSettings(id);
            channelSettings.setSourceFile(getSourceFileOf(channel));

            // Note whether the channel-definition was for a remote endpoint
            String remote = getAttributeOrChildElement(channel, REMOTE_ATTR);
            channelSettings.setRemote(Boolean.valueOf(remote));

            // Endpoint
            Node endpoint = selectSingleNode(channel, ENDPOINT_ELEMENT);
            if (endpoint != null) {
                // Endpoint Validation
                allowedAttributesOrElements(endpoint, ENDPOINT_CHILDREN);

                String type = getAttributeOrChildElement(endpoint, CLASS_ATTR);
                channelSettings.setEndpointType(type);

                // The url attribute may also be specified by the deprecated uri attribute
                String uri = getAttributeOrChildElement(endpoint, URL_ATTR);
                if (uri == null || EMPTY_STRING.equals(uri))
                    uri = getAttributeOrChildElement(endpoint, URI_ATTR);
                channelSettings.setUri(uri);

                config.addChannelSettings(id, channelSettings);
            }

            channelServerOnlyAttribute(channel, channelSettings);

            // Server reference
            Node server = selectSingleNode(channel, SERVER_ELEMENT);
            if (server != null) {
                requiredAttributesOrElements(server, CHANNEL_DEFINITION_SERVER_REQ_CHILDREN);

                String serverId = getAttributeOrChildElement(server, REF_ATTR);
                channelSettings.setServerId(serverId);
            }

            // Channel Properties
            NodeList properties = selectNodeList(channel, PROPERTIES_ELEMENT + "/*");
            if (properties.getLength() > 0) {
                ConfigMap map = properties(properties, getSourceFileOf(channel));
                channelSettings.addProperties(map);

                // Sniff for adaptive-frequency under flex-client-queue-processor hich requires advanced messaging support.
                if (!verifyAdvancedMessagingSupport) {
                    ConfigMap outboundQueueProcessor = map.getPropertyAsMap(FLEX_CLIENT_OUTBOUND_QUEUE_PROCESSOR_ELEMENT, null);
                    if (outboundQueueProcessor != null) {
                        // Flex client queue processor properties
                        ConfigMap queueProcessorProperties = outboundQueueProcessor.getPropertyAsMap(PROPERTIES_ELEMENT, null);
                        if (queueProcessorProperties != null) {
                            // Sniff for adaptive-frequency which requires advanced messaging support.
                            boolean adaptiveFrequency = queueProcessorProperties.getPropertyAsBoolean(ADAPTIVE_FREQUENCY, false);
                            if (adaptiveFrequency)
                                verifyAdvancedMessagingSupport = true;
                        }
                    }
                }
            }

            // Channel Security

            // Security-constraint short-cut attribute
            String ref = evaluateExpression(channel, "@" + SECURITY_CONSTRAINT_ATTR).toString().trim();
            if (ref.length() > 0) {
                SecurityConstraint sc = ((MessagingConfiguration) config).getSecuritySettings().getConstraint(ref);
                if (sc != null) {
                    channelSettings.setConstraint(sc);
                } else {
                    // {SECURITY_CONSTRAINT_ELEMENT} not found for reference '{ref}' in channel '{id}'.
                    ConfigurationException ex = new ConfigurationException();
                    ex.setMessage(REF_NOT_FOUND_IN_CHANNEL, new Object[]{SECURITY_CONSTRAINT_ATTR, ref, id});
                    throw ex;
                }
            } else {
                // Inline security element
                Node security = selectSingleNode(channel, SECURITY_ELEMENT);
                if (security != null) {
                    allowedChildElements(security, EMBEDDED_SECURITY_CHILDREN);
                    Node constraint = selectSingleNode(security, SECURITY_CONSTRAINT_ELEMENT);
                    if (constraint != null) {
                        SecurityConstraint sc = securityConstraint(constraint, true);
                        channelSettings.setConstraint(sc);
                    }
                }
            }
        } else {
            // Invalid {CHANNEL_DEFINITION_ELEMENT} id '{id}'.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(INVALID_ID, new Object[]{CHANNEL_DEFINITION_ELEMENT, id});
            ex.setDetails(INVALID_ID);
            throw ex;
        }
    }

    private void channelServerOnlyAttribute(Node channel, ChannelSettings channelSettings) {
        String clientType = getAttributeOrChildElement(channel, CLASS_ATTR);
        clientType = clientType.length() > 0 ? clientType : null;

        String serverOnlyString = getAttributeOrChildElement(channel, SERVER_ONLY_ATTR);
        boolean serverOnly = serverOnlyString.length() > 0 ? Boolean.valueOf(serverOnlyString) : false;

        if (clientType == null && !serverOnly) // None set.
        {
            String url = channelSettings.getUri();
            boolean serverOnlyProtocol = (url.startsWith("samfsocket") || url.startsWith("amfsocket") || url.startsWith("ws"));
            if (!serverOnlyProtocol) {
                // Endpoint ''{0}'' needs to have either class or server-only attribute defined.
                ConfigurationException ce = new ConfigurationException();
                ce.setMessage(CLASS_OR_SERVER_ONLY_ERROR, new Object[]{channelSettings.getId()});
                throw ce;
            }
            channelSettings.setServerOnly(true);
        } else if (clientType != null && serverOnly) // Both set.
        {
            // Endpoint ''{0}'' cannot have both class and server-only attribute defined.
            ConfigurationException ce = new ConfigurationException();
            ce.setMessage(CLASS_AND_SERVER_ONLY_ERROR, new Object[]{channelSettings.getId()});
            throw ce;
        } else // One of them set.
        {
            if (serverOnly)
                channelSettings.setServerOnly(true);
            else
                channelSettings.setClientType(clientType);
        }
    }

    private void channelInclude(Node channelInclude) {
        // Validation
        allowedAttributesOrElements(channelInclude, CHANNEL_INCLUDE_CHILDREN);

        String src = getAttributeOrChildElement(channelInclude, SRC_ATTR);
        String dir = getAttributeOrChildElement(channelInclude, DIRECTORY_ATTR);
        if (src.length() > 0) {
            channelIncludeFile(src);
        } else if (dir.length() > 0) {
            channelIncludeDirectory(dir);
        } else {
            // The include element ''{0}'' must specify either the ''{1}'' or ''{2}'' attribute.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(MISSING_INCLUDE_ATTRIBUTES, new Object[]{channelInclude.getNodeName(), SRC_ATTR, DIRECTORY_ATTR});
            throw ex;
        }
    }

    private void channelIncludeFile(String src) {
        Document doc = loadDocument(src, fileResolver.getIncludedFile(src));
        doc.getDocumentElement().normalize();

        // Check for multiple channels in a single file.
        Node channelsNode = selectSingleNode(doc, CHANNELS_ELEMENT);
        if (channelsNode != null) {
            allowedChildElements(channelsNode, CHANNELS_CHILDREN);
            NodeList channels = selectNodeList(channelsNode, CHANNEL_DEFINITION_ELEMENT);
            for (int a = 0; a < channels.getLength(); a++) {
                Node service = channels.item(a);
                channelDefinition(service);
            }
            fileResolver.popIncludedFile();
        } else // Check for single channel in the file.
        {
            Node channel = selectSingleNode(doc, "/" + CHANNEL_DEFINITION_ELEMENT);
            if (channel != null) {
                channelDefinition(channel);
                fileResolver.popIncludedFile();
            } else {
                // The {0} root element in file {1} must be '{CHANNELS_ELEMENT}' or '{CHANNEL_ELEMENT}'.
                ConfigurationException ex = new ConfigurationException();
                ex.setMessage(INVALID_INCLUDE_ROOT, new Object[]{CHANNEL_INCLUDE_ELEMENT, src, CHANNELS_ELEMENT, CHANNEL_DEFINITION_ELEMENT});
                throw ex;
            }
        }
    }

    private void channelIncludeDirectory(String dir) {
        List files = fileResolver.getFiles(dir);
        for (Object file : files) {
            String src = (String) file;
            channelIncludeFile(src);
        }
    }

    private void services(Node root) {
        Node servicesNode = selectSingleNode(root, SERVICES_ELEMENT);
        if (servicesNode != null) {
            // Validation
            allowedChildElements(servicesNode, SERVICES_CHILDREN);

            // Default Channels for the application
            Node defaultChannels = selectSingleNode(servicesNode, DEFAULT_CHANNELS_ELEMENT);
            if (defaultChannels != null) {
                allowedChildElements(defaultChannels, DEFAULT_CHANNELS_CHILDREN);
                NodeList channels = selectNodeList(defaultChannels, CHANNEL_ELEMENT);
                for (int c = 0; c < channels.getLength(); c++) {
                    Node chan = channels.item(c);
                    allowedAttributes(chan, new String[]{REF_ATTR});
                    defaultChannel(chan);
                }
            }

            // Service Includes
            NodeList services = selectNodeList(servicesNode, SERVICE_INCLUDE_ELEMENT);
            for (int i = 0; i < services.getLength(); i++) {
                Node service = services.item(i);
                serviceInclude(service);
            }

            // Service
            services = selectNodeList(servicesNode, SERVICE_ELEMENT);
            for (int i = 0; i < services.getLength(); i++) {
                Node service = services.item(i);
                service(service);
            }
        }
    }

    private void serviceInclude(Node serviceInclude) {
        // Validation
        allowedAttributesOrElements(serviceInclude, SERVICE_INCLUDE_CHILDREN);

        String src = getAttributeOrChildElement(serviceInclude, SRC_ATTR);
        String dir = getAttributeOrChildElement(serviceInclude, DIRECTORY_ATTR);
        if (src.length() > 0) {
            serviceIncludeFile(src);
        } else if (dir.length() > 0) {
            serviceIncludeDirectory(dir);
        } else {
            // The include element ''{0}'' must specify either the ''{1}'' or ''{2}'' attribute.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(MISSING_INCLUDE_ATTRIBUTES, new Object[]{serviceInclude.getNodeName(), SRC_ATTR, DIRECTORY_ATTR});
            throw ex;
        }
    }

    private void serviceIncludeFile(String src) {
        Document doc = loadDocument(src, fileResolver.getIncludedFile(src));
        doc.getDocumentElement().normalize();

        // Check for multiple services defined in a <services> tag
        Node servicesNode = selectSingleNode(doc, SERVICES_ELEMENT);
        if (servicesNode != null) {
            allowedChildElements(servicesNode, SERVICES_CHILDREN);
            NodeList services = selectNodeList(servicesNode, SERVICE_ELEMENT);
            for (int a = 0; a < services.getLength(); a++) {
                Node service = services.item(a);
                service(service);
            }
            fileResolver.popIncludedFile();
        } else // Check for single <service> definition.
        {
            Node service = selectSingleNode(doc, "/" + SERVICE_ELEMENT);
            if (service != null) {
                service(service);
                fileResolver.popIncludedFile();
            } else {
                // The {0} root element in file {1} must be ''{2}'' or ''{3}''.
                ConfigurationException ex = new ConfigurationException();
                ex.setMessage(INVALID_INCLUDE_ROOT, new Object[]{SERVICE_INCLUDE_ELEMENT, src, SERVICES_ELEMENT, SERVICE_ELEMENT});
                throw ex;
            }
        }
    }

    private void serviceIncludeDirectory(String dir) {
        List files = fileResolver.getFiles(dir);
        for (Object file : files) {
            String src = (String) file;
            serviceIncludeFile(src);
        }
    }

    private void service(Node service) {
        // Validation
        requiredAttributesOrElements(service, SERVICE_REQ_CHILDREN);
        allowedAttributesOrElements(service, SERVICE_CHILDREN);

        String id = getAttributeOrChildElement(service, ID_ATTR);
        if (isValidID(id)) {
            ServiceSettings serviceSettings = config.getServiceSettings(id);
            if (serviceSettings == null) {
                serviceSettings = new ServiceSettings(id);
                serviceSettings.setSourceFile(getSourceFileOf(service));
                config.addServiceSettings(serviceSettings);
            } else {
                // Duplicate service definition '{0}'.
                ConfigurationException e = new ConfigurationException();
                e.setMessage(DUPLICATE_SERVICE_ERROR, new Object[]{id});
                throw e;
            }

            // Service Class Name
            String className = getAttributeOrChildElement(service, CLASS_ATTR);
            if (className.length() > 0) {
                serviceSettings.setClassName(className);

                // Sniff for AdvancedMessagingSupport.
                if (className.equals("flex.messaging.services.AdvancedMessagingSupport"))
                    advancedMessagingSupportRegistered = true;
            } else {
                // Class not specified for {SERVICE_ELEMENT} '{id}'.
                ConfigurationException ex = new ConfigurationException();
                ex.setMessage(CLASS_NOT_SPECIFIED, new Object[]{SERVICE_ELEMENT, id});
                throw ex;
            }

            // Service Message Types - deprecated

            // Service Properties
            NodeList properties = selectNodeList(service, PROPERTIES_ELEMENT + "/*");
            if (properties.getLength() > 0) {
                ConfigMap map = properties(properties, getSourceFileOf(service));
                serviceSettings.addProperties(map);
            }

            // Default Channels
            Node defaultChannels = selectSingleNode(service, DEFAULT_CHANNELS_ELEMENT);
            if (defaultChannels != null) {
                allowedChildElements(defaultChannels, DEFAULT_CHANNELS_CHILDREN);
                NodeList channels = selectNodeList(defaultChannels, CHANNEL_ELEMENT);
                for (int c = 0; c < channels.getLength(); c++) {
                    Node chan = channels.item(c);
                    allowedAttributes(chan, new String[]{REF_ATTR});
                    defaultChannel(chan, serviceSettings);
                }
            }
            // Fall back on application's default channels
            else if (config.getDefaultChannels().size() > 0) {
                for (Object o : config.getDefaultChannels()) {
                    String channelId = (String) o;
                    ChannelSettings channel = config.getChannelSettings(channelId);
                    serviceSettings.addDefaultChannel(channel);
                }
            }

            // Default Security Constraint
            Node defaultSecurityConstraint = selectSingleNode(service, DEFAULT_SECURITY_CONSTRAINT_ELEMENT);
            if (defaultSecurityConstraint != null) {
                // Validation
                requiredAttributesOrElements(defaultSecurityConstraint, new String[]{REF_ATTR});
                allowedAttributesOrElements(defaultSecurityConstraint, new String[]{REF_ATTR});

                String ref = getAttributeOrChildElement(defaultSecurityConstraint, REF_ATTR);
                if (ref.length() > 0) {
                    SecurityConstraint sc = ((MessagingConfiguration) config).getSecuritySettings().getConstraint(ref);
                    if (sc == null) {
                        // {SECURITY_CONSTRAINT_DEFINITION_ELEMENT} not found for reference '{ref}'.
                        ConfigurationException e = new ConfigurationException();
                        e.setMessage(REF_NOT_FOUND, new Object[]{SECURITY_CONSTRAINT_DEFINITION_ELEMENT, ref});
                        throw e;
                    }
                    serviceSettings.setConstraint(sc);
                } else {
                    //Invalid default-security-constraint reference ''{0}'' in service ''{1}''.
                    ConfigurationException ex = new ConfigurationException();
                    ex.setMessage(INVALID_SECURITY_CONSTRAINT_REF, new Object[]{ref, id});
                    throw ex;
                }
            }

            // Adapter Definitions
            Node adapters = selectSingleNode(service, ADAPTERS_ELEMENT);
            if (adapters != null) {
                allowedChildElements(adapters, ADAPTERS_CHILDREN);
                NodeList serverAdapters = selectNodeList(adapters, ADAPTER_DEFINITION_ELEMENT);
                for (int a = 0; a < serverAdapters.getLength(); a++) {
                    Node adapter = serverAdapters.item(a);
                    adapterDefinition(adapter, serviceSettings);
                }
                NodeList adapterIncludes = selectNodeList(adapters, ADAPTER_INCLUDE_ELEMENT);
                for (int a = 0; a < adapterIncludes.getLength(); a++) {
                    Node include = adapterIncludes.item(a);
                    adapterInclude(include, serviceSettings);
                }
            }

            // Destinations
            NodeList list = selectNodeList(service, DESTINATION_ELEMENT);
            for (int i = 0; i < list.getLength(); i++) {
                Node dest = list.item(i);
                destination(dest, serviceSettings);
            }

            // Destination Includes
            list = selectNodeList(service, DESTINATION_INCLUDE_ELEMENT);
            for (int i = 0; i < list.getLength(); i++) {
                Node dest = list.item(i);
                destinationInclude(dest, serviceSettings);
            }
        } else {
            //Invalid {SERVICE_ELEMENT} id '{id}'.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(INVALID_ID, new Object[]{SERVICE_ELEMENT, id});
            throw ex;
        }
    }

    /**
     * A Flex application can declare default channels for its services. If a
     * service specifies its own list of channels it overrides these defaults.
     * <p>
     * &lt;default-channels&gt;<br/>
     * &lt;channel ref="channel-id"/&gt;<br/>
     * &lt;default-channels&gt;
     * </p>
     *
     * @param chan the channel node
     */
    private void defaultChannel(Node chan) {
        String ref = getAttributeOrChildElement(chan, REF_ATTR);

        if (ref.length() > 0) {
            ChannelSettings channel = config.getChannelSettings(ref);
            if (channel != null) {
                config.addDefaultChannel(channel.getId());
            } else {
                // {0} not found for reference '{1}'
                ConfigurationException e = new ConfigurationException();
                e.setMessage(REF_NOT_FOUND, new Object[]{CHANNEL_ELEMENT, ref});
                throw e;
            }
        } else {
            //A default channel was specified without a reference for service '{0}'.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(INVALID_DEFAULT_CHANNEL, new Object[]{"MessageBroker"});
            throw ex;
        }
    }

    /**
     * A service can declare default channels for its destinations. If a destination
     * specifies its own list of channels it overrides these defaults.
     * <p>
     * &lt;default-channels&gt;<br/>
     * &lt;channel ref="channel-id"/&gt;<br/>
     * &lt;default-channels&gt;
     * </p>
     *
     * @param chan            the channel node
     * @param serviceSettings service settings
     */
    private void defaultChannel(Node chan, ServiceSettings serviceSettings) {
        String ref = getAttributeOrChildElement(chan, REF_ATTR);

        if (ref.length() > 0) {
            ChannelSettings channel = config.getChannelSettings(ref);
            if (channel != null) {
                serviceSettings.addDefaultChannel(channel);
            } else {
                // {0} not found for reference '{1}'
                ConfigurationException e = new ConfigurationException();
                e.setMessage(REF_NOT_FOUND, new Object[]{CHANNEL_ELEMENT, ref});
                throw e;
            }
        } else {
            //A default channel was specified without a reference for service '{0}'.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(INVALID_DEFAULT_CHANNEL, new Object[]{serviceSettings.getId()});
            throw ex;
        }
    }

    private void adapterDefinition(Node adapter, ServiceSettings serviceSettings) {
        // Validation
        requiredAttributesOrElements(adapter, ADAPTER_DEFINITION_REQ_CHILDREN);
        allowedChildElements(adapter, ADAPTER_DEFINITION_CHILDREN);

        String serviceId = serviceSettings.getId();

        String id = getAttributeOrChildElement(adapter, ID_ATTR);
        if (isValidID(id)) {
            AdapterSettings adapterSettings = new AdapterSettings(id);
            adapterSettings.setSourceFile(getSourceFileOf(adapter));
            String className = getAttributeOrChildElement(adapter, CLASS_ATTR);

            if (className.length() > 0) {
                adapterSettings.setClassName(className);

                // Default Adapter Check
                boolean isDefault = Boolean.valueOf(getAttributeOrChildElement(adapter, DEFAULT_ATTR));
                if (isDefault) {
                    adapterSettings.setDefault(true);

                    AdapterSettings defaultAdapter;
                    defaultAdapter = serviceSettings.getDefaultAdapter();

                    if (defaultAdapter != null) {
                        // Duplicate default adapter '{0}' in service '{1}'. '{2}' has already been selected as the default.
                        ConfigurationException ex = new ConfigurationException();
                        ex.setMessage(DUPLICATE_DEFAULT_ADAPTER, new Object[]{id, serviceId, defaultAdapter.getId()});
                        throw ex;
                    }
                }

                serviceSettings.addAdapterSettings(adapterSettings);

                // Adapter Properties
                NodeList properties = selectNodeList(adapter, PROPERTIES_ELEMENT + "/*");
                if (properties.getLength() > 0) {
                    ConfigMap map = properties(properties, getSourceFileOf(adapter));
                    adapterSettings.addProperties(map);
                }
            } else {
                // Class not specified for {ADAPTER_DEFINITION_ELEMENT} '{id}'.
                ConfigurationException ex = new ConfigurationException();
                ex.setMessage(CLASS_NOT_SPECIFIED, new Object[]{ADAPTER_DEFINITION_ELEMENT, id});
                throw ex;
            }
        } else {
            //Invalid {ADAPTER_DEFINITION_ELEMENT} id '{id}' for service '{serviceId}'.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(INVALID_ID_IN_SERVICE, new Object[]{ADAPTER_DEFINITION_ELEMENT, id, serviceId});
            throw ex;
        }
    }

    private void adapterInclude(Node adapterInclude, ServiceSettings serviceSettings) {
        // Validation
        allowedAttributesOrElements(adapterInclude, ADAPTER_INCLUDE_CHILDREN);

        String src = getAttributeOrChildElement(adapterInclude, SRC_ATTR);
        String dir = getAttributeOrChildElement(adapterInclude, DIRECTORY_ATTR);
        if (src.length() > 0) {
            adapterIncludeFile(serviceSettings, src);
        } else if (dir.length() > 0) {
            adapterIncludeDirectory(serviceSettings, dir);
        } else {
            // Attribute '{0}' must be specified for element '{1}'
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(MISSING_INCLUDE_ATTRIBUTES, new Object[]{adapterInclude.getNodeName(), SRC_ATTR, DIRECTORY_ATTR});
            throw ex;
        }
    }

    private void adapterIncludeDirectory(ServiceSettings serviceSettings, String dir) {
        List files = fileResolver.getFiles(dir);
        for (Object file : files) {
            String src = (String) file;
            adapterIncludeFile(serviceSettings, src);
        }
    }

    private void adapterIncludeFile(ServiceSettings serviceSettings, String src) {
        Document doc = loadDocument(src, fileResolver.getIncludedFile(src));
        doc.getDocumentElement().normalize();

        // Check for multiple adapters defined in file wrapped in an <adapters> element
        Node adaptersNode = selectSingleNode(doc, ADAPTERS_ELEMENT);
        if (adaptersNode != null) {
            allowedChildElements(adaptersNode, ADAPTERS_CHILDREN);
            NodeList adapters = selectNodeList(adaptersNode, ADAPTER_DEFINITION_ELEMENT);
            for (int a = 0; a < adapters.getLength(); a++) {
                Node adapter = adapters.item(a);
                adapterDefinition(adapter, serviceSettings);
            }
            fileResolver.popIncludedFile();
        } else // Check for single adapter
        {
            Node adapter = selectSingleNode(doc, "/" + ADAPTER_DEFINITION_ELEMENT);
            if (adapter != null) {
                adapterDefinition(adapter, serviceSettings);
                fileResolver.popIncludedFile();
            } else {
                // The {0} root element in file {1} must be ''{2}'' or ''{3}''.
                ConfigurationException ex = new ConfigurationException();
                ex.setMessage(INVALID_INCLUDE_ROOT, new Object[]{ADAPTER_INCLUDE_ELEMENT, src, ADAPTERS_ELEMENT, ADAPTER_DEFINITION_ELEMENT});
                throw ex;
            }
        }
    }

    private void destinationInclude(Node destInclude, ServiceSettings serviceSettings) {
        // Validation
        allowedAttributesOrElements(destInclude, DESTINATION_INCLUDE_CHILDREN);

        String src = getAttributeOrChildElement(destInclude, SRC_ATTR);
        String dir = getAttributeOrChildElement(destInclude, DIRECTORY_ATTR);
        if (src.length() > 0) {
            destinationIncludeFile(serviceSettings, src);
        } else if (dir.length() > 0) {
            destinationIncludeDirectory(serviceSettings, dir);
        } else {
            // The include element ''{0}'' must specify either the ''{1}'' or ''{2}'' attribute.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(MISSING_INCLUDE_ATTRIBUTES, new Object[]{destInclude.getNodeName(), SRC_ATTR, DIRECTORY_ATTR});
            throw ex;
        }
    }

    private void destinationIncludeDirectory(ServiceSettings serviceSettings, String dir) {
        List files = fileResolver.getFiles(dir);
        for (Object file : files) {
            String src = (String) file;
            destinationIncludeFile(serviceSettings, src);
        }
    }

    private void destinationIncludeFile(ServiceSettings serviceSettings, String src) {
        Document doc = loadDocument(src, fileResolver.getIncludedFile(src));
        doc.getDocumentElement().normalize();

        // Check for multiple destination defined in file.
        Node destinationsNode = selectSingleNode(doc, DESTINATIONS_ELEMENT);
        if (destinationsNode != null) {
            allowedChildElements(destinationsNode, DESTINATIONS_CHILDREN);
            NodeList destinations = selectNodeList(destinationsNode, DESTINATION_ELEMENT);
            for (int a = 0; a < destinations.getLength(); a++) {
                Node dest = destinations.item(a);
                destination(dest, serviceSettings);
            }
            fileResolver.popIncludedFile();
        } else // Check for single destination definition.
        {
            Node dest = selectSingleNode(doc, "/" + DESTINATION_ELEMENT);
            if (dest != null) {
                destination(dest, serviceSettings);
                fileResolver.popIncludedFile();
            } else {
                // The {0} root element in file {1} must be ''{2}'' or ''{3}''.
                ConfigurationException ex = new ConfigurationException();
                ex.setMessage(INVALID_INCLUDE_ROOT, new Object[]{DESTINATION_INCLUDE_ELEMENT, src, DESTINATIONS_ELEMENT, DESTINATION_ELEMENT});
                throw ex;
            }
        }
    }

    private void destination(Node dest, ServiceSettings serviceSettings) {
        // Validation
        requiredAttributesOrElements(dest, DESTINATION_REQ_CHILDREN);
        allowedAttributes(dest, DESTINATION_ATTR);
        allowedChildElements(dest, DESTINATION_CHILDREN);

        String serviceId = serviceSettings.getId();

        DestinationSettings destinationSettings;
        String id = getAttributeOrChildElement(dest, ID_ATTR);
        if (isValidID(id)) {
            destinationSettings = (DestinationSettings) serviceSettings.getDestinationSettings().get(id);
            if (destinationSettings != null) {
                // Duplicate destination definition '{id}' in service '{serviceId}'.
                ConfigurationException e = new ConfigurationException();
                e.setMessage(DUPLICATE_DESTINATION_ERROR, new Object[]{id, serviceId});
                throw e;
            }

            destinationSettings = new DestinationSettings(id);
            destinationSettings.setSourceFile(getSourceFileOf(dest));
            serviceSettings.addDestinationSettings(destinationSettings);
        } else {
            //Invalid {DESTINATION_ELEMENT} id '{id}' for service '{serviceId}'.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(INVALID_ID_IN_SERVICE, new Object[]{DESTINATION_ELEMENT, id, serviceId});
            throw ex;
        }

        // Destination Properties
        NodeList properties = selectNodeList(dest, PROPERTIES_ELEMENT + "/*");
        if (properties.getLength() > 0) {
            ConfigMap map = properties(properties, getSourceFileOf(dest));
            destinationSettings.addProperties(map);

            // Sniff for <network><reliable>true|false</reliable></network> setting.
            // Also sniff for inbound and outbound throttle policies of buffer and conflate.
            // All these features are only supported when advanced messaging support is enabled.
            if (!verifyAdvancedMessagingSupport) {
                ConfigMap networkSettings = map.getPropertyAsMap(NetworkSettings.NETWORK_ELEMENT, null);
                if (networkSettings != null) {
                    String reliable = networkSettings.getPropertyAsString(NetworkSettings.RELIABLE_ELEMENT, null);
                    if (reliable != null && Boolean.valueOf(reliable)) {
                        verifyAdvancedMessagingSupport = true;
                    } else {
                        ConfigMap inbound = networkSettings.getPropertyAsMap(ThrottleSettings.ELEMENT_INBOUND, null);
                        if (inbound != null) {
                            String policy = inbound.getPropertyAsString(ThrottleSettings.ELEMENT_POLICY, null);
                            if (policy != null && (Policy.BUFFER.toString().equalsIgnoreCase(policy)
                                    || Policy.CONFLATE.toString().equalsIgnoreCase(policy)))
                                verifyAdvancedMessagingSupport = true;
                        }
                        if (!verifyAdvancedMessagingSupport) {
                            ConfigMap outbound = networkSettings.getPropertyAsMap(ThrottleSettings.ELEMENT_OUTBOUND, null);
                            if (outbound != null) {
                                String policy = outbound.getPropertyAsString(ThrottleSettings.ELEMENT_POLICY, null);
                                if (policy != null && (Policy.BUFFER.toString().equalsIgnoreCase(policy)
                                        || Policy.CONFLATE.toString().equalsIgnoreCase(policy)))
                                    verifyAdvancedMessagingSupport = true;
                            }
                        }
                    }
                }
            }
        }

        // Channels
        destinationChannels(dest, destinationSettings, serviceSettings);

        // Security
        destinationSecurity(dest, destinationSettings, serviceSettings);

        // Service Adapter
        destinationAdapter(dest, destinationSettings, serviceSettings);
    }

    private void destinationChannels(Node dest, DestinationSettings destinationSettings, ServiceSettings serviceSettings) {
        String destId = destinationSettings.getId();

        // Channels attribute
        String channelsList = evaluateExpression(dest, "@" + CHANNELS_ATTR).toString().trim();
        if (channelsList.length() > 0) {
            StringTokenizer st = new StringTokenizer(channelsList, LIST_DELIMITERS);
            while (st.hasMoreTokens()) {
                String ref = st.nextToken().trim();
                ChannelSettings channel = config.getChannelSettings(ref);
                if (channel != null) {
                    destinationSettings.addChannelSettings(channel);
                } else {
                    // {CHANNEL_ELEMENT} not found for reference '{ref}' in destination '{destId}'.
                    ConfigurationException ex = new ConfigurationException();
                    ex.setMessage(REF_NOT_FOUND_IN_DEST, new Object[]{CHANNEL_ELEMENT, ref, destId});
                    throw ex;
                }
            }
        } else {
            // Channels element
            Node channelsNode = selectSingleNode(dest, CHANNELS_ELEMENT);
            if (channelsNode != null) {
                allowedChildElements(channelsNode, DESTINATION_CHANNELS_CHILDREN);
                NodeList channels = selectNodeList(channelsNode, CHANNEL_ELEMENT);
                for (int c = 0; c < channels.getLength(); c++) {
                    Node chan = channels.item(c);

                    // Validation
                    requiredAttributesOrElements(chan, DESTINATION_CHANNEL_REQ_CHILDREN);

                    String ref = getAttributeOrChildElement(chan, REF_ATTR);
                    if (ref.length() > 0) {
                        ChannelSettings channel = config.getChannelSettings(ref);
                        if (channel != null) {
                            destinationSettings.addChannelSettings(channel);
                        } else {
                            // {CHANNEL_ELEMENT} not found for reference '{ref}' in destination '{destId}'.
                            ConfigurationException ex = new ConfigurationException();
                            ex.setMessage(REF_NOT_FOUND_IN_DEST, new Object[]{CHANNEL_ELEMENT, ref, destId});
                            throw ex;
                        }
                    } else {
                        //Invalid {0} ref '{1}' in destination '{2}'.
                        ConfigurationException ex = new ConfigurationException();
                        ex.setMessage(INVALID_REF_IN_DEST, new Object[]{CHANNEL_ELEMENT, ref, destId});
                        throw ex;
                    }
                }
            } else {
                // Finally, we fall back to the service's default channels
                List defaultChannels = serviceSettings.getDefaultChannels();
                for (Object defaultChannel : defaultChannels) {
                    ChannelSettings channel = (ChannelSettings) defaultChannel;
                    destinationSettings.addChannelSettings(channel);
                }
            }
        }

        if (destinationSettings.getChannelSettings().size() <= 0) {
            // Destination '{id}' must specify at least one channel.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(DEST_NEEDS_CHANNEL, new Object[]{destId});
            throw ex;
        }
    }

    private void destinationSecurity(Node dest, DestinationSettings destinationSettings, ServiceSettings serviceSettings) {
        String destId = destinationSettings.getId();

        // Security-constraint short-cut attribute
        String ref = evaluateExpression(dest, "@" + SECURITY_CONSTRAINT_ATTR).toString().trim();
        if (ref.length() > 0) {
            SecurityConstraint sc = ((MessagingConfiguration) config).getSecuritySettings().getConstraint(ref);
            if (sc != null) {
                destinationSettings.setConstraint(sc);
            } else {
                // {SECURITY_CONSTRAINT_ELEMENT} not found for reference '{ref}' in destination '{destId}'.
                ConfigurationException ex = new ConfigurationException();
                ex.setMessage(REF_NOT_FOUND_IN_DEST, new Object[]{SECURITY_CONSTRAINT_ATTR, ref, destId});
                throw ex;
            }
        } else {
            // Inline security element
            Node security = selectSingleNode(dest, SECURITY_ELEMENT);
            if (security != null) {
                allowedChildElements(security, EMBEDDED_SECURITY_CHILDREN);
                Node constraint = selectSingleNode(security, SECURITY_CONSTRAINT_ELEMENT);
                if (constraint != null) {
                    SecurityConstraint sc = securityConstraint(constraint, true);
                    destinationSettings.setConstraint(sc);
                }
            } else {
                // Finally, we fall back to the service's default security constraint
                SecurityConstraint sc = serviceSettings.getConstraint();
                if (sc != null) {
                    destinationSettings.setConstraint(sc);
                }
            }
        }
    }

    private void destinationAdapter(Node dest, DestinationSettings destinationSettings, ServiceSettings serviceSettings) {
        String destId = destinationSettings.getId();

        // Adapter attribute
        String ref = evaluateExpression(dest, "@" + ADAPTER_ATTR).toString().trim();
        if (ref.length() > 0) {
            adapterReference(ref, destinationSettings, serviceSettings);
        } else {
            Node adapter = selectSingleNode(dest, ADAPTER_ELEMENT);

            // Adapter element
            if (adapter != null) {
                allowedAttributesOrElements(adapter, DESTINATION_ADAPTER_CHILDREN);
                ref = getAttributeOrChildElement(adapter, REF_ATTR);
                adapterReference(ref, destinationSettings, serviceSettings);
            } else {
                // Default Adapter (optionally set at the service level)
                AdapterSettings adapterSettings = serviceSettings.getDefaultAdapter();
                if (adapterSettings != null) {
                    destinationSettings.setAdapterSettings(adapterSettings);
                }
            }
        }

        if (destinationSettings.getAdapterSettings() == null) {
            // Destination '{id}' must specify at least one adapter.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(DEST_NEEDS_ADAPTER, new Object[]{destId});
            throw ex;
        }
    }

    private void adapterReference(String ref, DestinationSettings destinationSettings, ServiceSettings serviceSettings) {
        String destId = destinationSettings.getId();
        if (ref.length() > 0) {
            AdapterSettings adapterSettings = serviceSettings.getAdapterSettings(ref);
            if (adapterSettings != null) {
                destinationSettings.setAdapterSettings(adapterSettings);
            } else {
                // {ADAPTER_ELEMENT} not found for reference '{ref}' in destination '{destId}'.
                ConfigurationException ex = new ConfigurationException();
                ex.setMessage(REF_NOT_FOUND_IN_DEST, new Object[]{ADAPTER_ELEMENT, ref, destId});
                throw ex;
            }
        } else {
            //Invalid {ADAPTER_ELEMENT} ref '{ref}' in destination '{destId}'.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(INVALID_REF_IN_DEST, new Object[]{ADAPTER_ELEMENT, ref, destId});
            throw ex;
        }
    }

    private void logging(Node root) {
        Node logging = selectSingleNode(root, LOGGING_ELEMENT);
        if (logging != null) {
            // Validation
            allowedAttributesOrElements(logging, LOGGING_CHILDREN);

            LoggingSettings settings = new LoggingSettings();

            // Log Properties
            NodeList properties = selectNodeList(logging, PROPERTIES_ELEMENT + "/*");
            if (properties.getLength() > 0) {
                ConfigMap map = properties(properties, getSourceFileOf(logging));
                settings.addProperties(map);
            }

            NodeList targets = selectNodeList(logging, TARGET_ELEMENT);
            for (int i = 0; i < targets.getLength(); i++) {
                Node targetNode = targets.item(i);

                // Target Validation
                requiredAttributesOrElements(targetNode, TARGET_REQ_CHILDREN);
                allowedAttributesOrElements(targetNode, TARGET_CHILDREN);

                String className = getAttributeOrChildElement(targetNode, CLASS_ATTR);

                if (className.length() > 0) {
                    TargetSettings targetSettings = new TargetSettings(className);
                    String targetLevel = getAttributeOrChildElement(targetNode, LEVEL_ATTR);

                    if (targetLevel.length() > 0)
                        targetSettings.setLevel(targetLevel);

                    // Filters
                    Node filtersNode = selectSingleNode(targetNode, FILTERS_ELEMENT);
                    if (filtersNode != null) {
                        allowedChildElements(filtersNode, FILTERS_CHILDREN);
                        NodeList filters = selectNodeList(filtersNode, PATTERN_ELEMENT);
                        for (int f = 0; f < filters.getLength(); f++) {
                            Node pattern = filters.item(f);
                            String filter = evaluateExpression(pattern, ".").toString().trim();
                            targetSettings.addFilter(filter);
                        }
                    }

                    // Target Properties
                    properties = selectNodeList(targetNode, PROPERTIES_ELEMENT + "/*");
                    if (properties.getLength() > 0) {
                        ConfigMap map = properties(properties, getSourceFileOf(targetNode));
                        targetSettings.addProperties(map);
                    }

                    settings.addTarget(targetSettings);
                }
            }

            config.setLoggingSettings(settings);
        }
    }

    private void system(Node root) {
        Node system = selectSingleNode(root, SYSTEM_ELEMENT);
        if (system == null) {
            // Create a default instance of SystemSettings which by default has setManagable as true
            // and has setRedeployEnabled as false.
            ((MessagingConfiguration) config).setSystemSettings(new SystemSettings());
            return;
        }

        allowedAttributesOrElements(system, SYSTEM_CHILDREN);

        SystemSettings settings = new SystemSettings();

        settings.setEnforceEndpointValidation(getAttributeOrChildElement(system, ENFORCE_ENDOINT_VALIDATION));
        locale(system, settings);
        settings.setManageable(getAttributeOrChildElement(system, MANAGEABLE_ELEMENT));
        settings.setDotNetFrameworkVersion(getAttributeOrChildElement(system, DOTNET_FRAMEWORK_VERSION));
        redeploy(system, settings);
        uuidGenerator(system, settings);

        ((MessagingConfiguration) config).setSystemSettings(settings);
    }

    private void redeploy(Node system, SystemSettings settings) {
        Node redeployNode = selectSingleNode(system, REDEPLOY_ELEMENT);
        if (redeployNode == null)
            return;

        allowedAttributesOrElements(redeployNode, REDEPLOY_CHILDREN);

        String enabled = getAttributeOrChildElement(redeployNode, ENABLED_ELEMENT);
        settings.setRedeployEnabled(enabled);

        String interval = getAttributeOrChildElement(redeployNode, WATCH_INTERVAL_ELEMENT);
        if (interval.length() > 0) {
            settings.setWatchInterval(interval);
        }

        NodeList watches = selectNodeList(redeployNode, WATCH_FILE_ELEMENT);
        for (int i = 0; i < watches.getLength(); i++) {
            Node watchNode = watches.item(i);
            String watch = evaluateExpression(watchNode, ".").toString().trim();
            if (watch.length() > 0) {
                settings.addWatchFile(watch);
            }
        }

        NodeList touches = selectNodeList(redeployNode, TOUCH_FILE_ELEMENT);
        for (int i = 0; i < touches.getLength(); i++) {
            Node touchNode = touches.item(i);
            String touch = evaluateExpression(touchNode, ".").toString().trim();
            if (touch.length() > 0) {
                settings.addTouchFile(touch);
            }
        }
    }

    private void locale(Node system, SystemSettings settings) {
        Node localeNode = selectSingleNode(system, LOCALE_ELEMENT);
        if (localeNode == null)
            return;

        allowedAttributesOrElements(localeNode, LOCALE_CHILDREN);

        String defaultLocaleString = getAttributeOrChildElement(localeNode, DEFAULT_LOCALE_ELEMENT);
        Locale defaultLocale = defaultLocaleString.length() > 0 ? LocaleUtils.buildLocale(defaultLocaleString) : LocaleUtils.buildLocale(null);
        settings.setDefaultLocale(defaultLocale);
    }

    private void uuidGenerator(Node system, SystemSettings settings) {
        Node uuidGenerator = selectSingleNode(system, UUID_GENERATOR_ELEMENT);
        if (uuidGenerator == null)
            return;

        requiredAttributesOrElements(uuidGenerator, UUID_GENERATOR_REQ_CHILDREN);

        String className = getAttributeOrChildElement(uuidGenerator, CLASS_ATTR);
        if (className.length() == 0) {
            // Class not specified for {UUID_GENERATOR_ELEMENT} '{id}'.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(CLASS_NOT_SPECIFIED, new Object[]{UUID_GENERATOR_ELEMENT, ""});
            throw ex;
        }

        settings.setUUIDGeneratorClassName(className);
    }

    private void flexClient(Node root) {
        Node flexClient = selectSingleNode(root, FLEX_CLIENT_ELEMENT);
        if (flexClient != null) {
            // Validation
            allowedChildElements(flexClient, FLEX_CLIENT_CHILDREN);

            FlexClientSettings flexClientSettings = new FlexClientSettings();

            // Timeout
            String timeout = getAttributeOrChildElement(flexClient, FLEX_CLIENT_TIMEOUT_MINUTES_ELEMENT);
            if (timeout.length() > 0) {
                try {
                    long timeoutMinutes = Long.parseLong(timeout);
                    if (timeoutMinutes < 0) {
                        // Invalid timeout minutes value ''{0}'' in the <flex-client> configuration section. Please specify a positive value or leave the element undefined in which case flex client instances on the server will be timed out when all associated sessions/connections have shut down.
                        ConfigurationException e = new ConfigurationException();
                        e.setMessage(INVALID_FLEX_CLIENT_TIMEOUT, new Object[]{timeout});
                        throw e;
                    }
                    flexClientSettings.setTimeoutMinutes(timeoutMinutes);
                } catch (NumberFormatException nfe) {
                    // Invalid timeout minutes value ''{0}'' in the <flex-client> configuration section. Please specify a positive value or leave the element undefined in which case flex client instances on the server will be timed out when all associated sessions/connections have shut down.
                    ConfigurationException e = new ConfigurationException();
                    e.setMessage(INVALID_FLEX_CLIENT_TIMEOUT, new Object[]{timeout});
                    throw e;
                }
            } else {
                flexClientSettings.setTimeoutMinutes(0); // Default to 0; in this case FlexClients are invalidated when all associated sessions have been invalidated.
            }

            // Flex client queue processor
            Node outboundQueueProcessor = selectSingleNode(flexClient, FLEX_CLIENT_OUTBOUND_QUEUE_PROCESSOR_ELEMENT);
            if (outboundQueueProcessor != null) {
                // Validation
                requiredAttributesOrElements(outboundQueueProcessor, FLEX_CLIENT_OUTBOUND_QUEUE_PROCESSOR_REQ_CHILDREN);

                // Flex client queue processor class
                String outboundQueueProcessClass = getAttributeOrChildElement(outboundQueueProcessor, CLASS_ATTR);
                if (outboundQueueProcessClass.length() > 0) {
                    flexClientSettings.setFlexClientOutboundQueueProcessorClassName(outboundQueueProcessClass);
                } else {
                    // Class not specified for {FLEX_CLIENT_OUTBOUND_QUEUE_PROCESSOR_ELEMENT} '{id}'.
                    ConfigurationException ex = new ConfigurationException();
                    ex.setMessage(CLASS_NOT_SPECIFIED, new Object[]{FLEX_CLIENT_OUTBOUND_QUEUE_PROCESSOR_ELEMENT, ""});
                    throw ex;
                }

                // Flex client queue processor properties
                NodeList properties = selectNodeList(outboundQueueProcessor, PROPERTIES_ELEMENT + "/*");
                if (properties.getLength() > 0) {
                    ConfigMap map = properties(properties, getSourceFileOf(outboundQueueProcessor));
                    flexClientSettings.setFlexClientOutboundQueueProcessorProperties(map);
                    // Sniff for adaptive-frequency which requires advanced messaging support.
                    boolean adaptiveFrequency = map.getPropertyAsBoolean(ADAPTIVE_FREQUENCY, false);
                    if (adaptiveFrequency)
                        verifyAdvancedMessagingSupport = true;
                }
            }
            ((MessagingConfiguration) config).setFlexClientSettings(flexClientSettings);
        }
    }

    private void factories(Node root) {
        Node factories = selectSingleNode(root, FACTORIES_ELEMENT);
        if (factories != null) {
            // Validation
            allowedAttributesOrElements(factories, FACTORIES_CHILDREN);

            NodeList factoryList = selectNodeList(factories, FACTORY_ELEMENT);
            for (int i = 0; i < factoryList.getLength(); i++) {
                Node factory = factoryList.item(i);
                factory(factory);
            }
        }
    }

    private void factory(Node factory) {
        // Validation
        requiredAttributesOrElements(factory, FACTORY_REQ_CHILDREN);

        String id = getAttributeOrChildElement(factory, ID_ATTR);
        String className = getAttributeOrChildElement(factory, CLASS_ATTR);
        if (isValidID(id)) {
            FactorySettings factorySettings = new FactorySettings(id, className);

            // Factory Properties
            NodeList properties = selectNodeList(factory, PROPERTIES_ELEMENT + "/*");
            if (properties.getLength() > 0) {
                ConfigMap map = properties(properties, getSourceFileOf(factory));
                factorySettings.addProperties(map);
            }
            ((MessagingConfiguration) config).addFactorySettings(id, factorySettings);
        } else {
            // Invalid {FACTORY_ELEMENT} id '{id}'.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(INVALID_ID, new Object[]{FACTORY_ELEMENT, id});
            ex.setDetails(INVALID_ID);
            throw ex;
        }
    }

    private void messageFilters(Node root) {
        typedMessageFilters(root, ASYNC_MESSAGE_FILTERS_ELEMENT, ASYNC_MESSAGE_FILTERS_ELEMENT_CHILDREN);
        typedMessageFilters(root, SYNC_MESSAGE_FILTERS_ELEMENT, SYNC_MESSAGE_FILTERS_ELEMENT_CHILDREN);
    }

    private void typedMessageFilters(Node root, String filterTypeElement, String[] childrenElements) {
        Node messageFiltersNode = selectSingleNode(root, filterTypeElement);
        if (messageFiltersNode == null)
            return;

        // Validation
        allowedChildElements(messageFiltersNode, childrenElements);

        // Message filter
        NodeList messageFilters = selectNodeList(messageFiltersNode, FILTER_ELEMENT);
        for (int i = 0; i < messageFilters.getLength(); i++) {
            Node messageFilter = messageFilters.item(i);
            messageFilter(messageFilter, filterTypeElement);
        }
    }

    private void messageFilter(Node messageFilter, String filterType) {
        // Validation
        requiredAttributesOrElements(messageFilter, FILTER_REQ_CHILDREN);
        allowedAttributesOrElements(messageFilter, FILTER_CHILDREN);

        String id = getAttributeOrChildElement(messageFilter, ID_ATTR);
        if (isValidID(id)) {
            // Message filter class name
            String className = getAttributeOrChildElement(messageFilter, CLASS_ATTR);
            if (className.length() > 0) {
                MessageFilterSettings messageFilterSettings = new MessageFilterSettings();
                messageFilterSettings.setId(id);
                messageFilterSettings.setClassName(className);
                // Record type of filter.
                MessageFilterSettings.FilterType type = filterType.equals(ASYNC_MESSAGE_FILTERS_ELEMENT) ?
                        MessageFilterSettings.FilterType.ASYNC :
                        MessageFilterSettings.FilterType.SYNC;
                messageFilterSettings.setFilterType(type);
                // Custom server properties.
                NodeList properties = selectNodeList(messageFilter, PROPERTIES_ELEMENT + "/*");
                if (properties.getLength() > 0) {
                    ConfigMap map = properties(properties, getSourceFileOf(messageFilter));
                    messageFilterSettings.addProperties(map);
                }
                ((MessagingConfiguration) config).addMessageFilterSettings(messageFilterSettings);
            } else {
                // Class not specified for {FILTER_ELEMENT} '{id}'.
                ConfigurationException ex = new ConfigurationException();
                ex.setMessage(CLASS_NOT_SPECIFIED, new Object[]{FILTER_ELEMENT, id});
                throw ex;
            }
        }
    }

    private void validators(Node root) {
        Node validatorsNode = selectSingleNode(root, VALIDATORS_ELEMENT);
        if (validatorsNode == null) {
            return;
        }

        // Validation
        allowedChildElements(validatorsNode, VALIDATORS_CHILDREN);

        // Validator
        NodeList validators = selectNodeList(validatorsNode, VALIDATOR_ELEMENT);
        if (validators.getLength() > 0) {
            for (int i = 0; i < validators.getLength(); i++) {
                Node validator = validators.item(i);
                validator(validator);
            }
        }
    }

    private void validator(Node validator) {
        // Validation
        requiredAttributesOrElements(validator, VALIDATOR_REQ_CHILDREN);
        allowedAttributesOrElements(validator, VALIDATOR_CHILDREN);

        ValidatorSettings validatorSettings = new ValidatorSettings();

        // Validator class name
        String className = getAttributeOrChildElement(validator, CLASS_ATTR);
        if (className.length() > 0) {
            validatorSettings.setClassName(className);
        } else {
            // Class not specified for {VALIDATOR_ELEMENT} '{id}'.
            ConfigurationException ex = new ConfigurationException();
            ex.setMessage(CLASS_NOT_SPECIFIED, new Object[]{VALIDATOR_ELEMENT, ""});
            throw ex;
        }

        // Validator type
        String type = getAttributeOrChildElement(validator, TYPE_ATTR);
        if (type.length() > 0)
            validatorSettings.setType(type);

        // Validator properties
        NodeList properties = selectNodeList(validator, PROPERTIES_ELEMENT + "/*");
        if (properties.getLength() > 0) {
            ConfigMap map = properties(properties, getSourceFileOf(validator));
            validatorSettings.addProperties(map);
        }

        ((MessagingConfiguration) config).addValidatorSettings(validatorSettings);
    }
}
