/*
 * $Id:AddressedEndpointDefinitionParser.java 8321 2007-09-10 19:22:52Z acooke $
 * --------------------------------------------------------------------------------------
 * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.com
 *
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */

package org.mule.config.spring.parsers.specific.endpoint.support;

import org.mule.api.config.MuleProperties;
import org.mule.config.spring.parsers.AbstractMuleBeanDefinitionParser;
import org.mule.config.spring.parsers.MuleChildDefinitionParser;
import org.mule.config.spring.parsers.MuleDefinitionParser;
import org.mule.config.spring.parsers.delegate.AbstractSingleParentFamilyDefinitionParser;
import org.mule.config.spring.parsers.generic.AttributePropertiesDefinitionParser;
import org.mule.config.spring.parsers.processors.BlockAttribute;
import org.mule.config.spring.parsers.processors.CheckExclusiveAttributes;
import org.mule.config.spring.parsers.processors.CheckRequiredAttributes;
import org.mule.endpoint.AbstractEndpointBuilder;
import org.mule.endpoint.URIBuilder;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Combine a
 * {@link org.mule.config.spring.parsers.specific.endpoint.support.ChildAddressDefinitionParser} and
 * either a
 * {@link org.mule.config.spring.parsers.specific.endpoint.support.OrphanEndpointDefinitionParser}
 * or a
 * {@link org.mule.config.spring.parsers.specific.endpoint.support.ChildEndpointDefinitionParser}
 * in one parser.  This lets us put the address attributes in the endpoint element.
 */
public class AddressedEndpointDefinitionParser extends AbstractSingleParentFamilyDefinitionParser
{

    protected Log logger = LogFactory.getLog(getClass());
    public static final boolean META = ChildAddressDefinitionParser.META;
    public static final boolean PROTOCOL = ChildAddressDefinitionParser.PROTOCOL;
    public static final String PROPERTIES = "properties";
    public static final String[] RESTRICTED_ENDPOINT_ATTRIBUTES =
            new String[]{MuleProperties.SYNCHRONOUS_PROPERTY, AbstractEndpointBuilder.PROPERTY_REMOTE_SYNC,
                    AbstractEndpointBuilder.PROPERTY_REMOTE_SYNC_TIMEOUT, "encoding",
                    "connector", "createConnector", "transformer", "responseTransformer"};

    // this is an example of parsing a single element with several parsers.  in this case
    // (because we extend AbstractSingleParentFamilyDefinitionParser) the first parser is expected to
    // create the "parent".  then subsequent parsers will be called as children.

    // because all are generated from one element we need to be careful to block attributes
    // that are irrelevant to a particular parser.

    public AddressedEndpointDefinitionParser(String protocol, MuleDefinitionParser endpointParser)
    {
        this(protocol, PROTOCOL, endpointParser);
    }

    public AddressedEndpointDefinitionParser(String metaOrProtocol, boolean isMeta, MuleDefinitionParser endpointParser)
    {
        this(metaOrProtocol, isMeta, endpointParser, new String[]{}, new String[]{});
    }

    public AddressedEndpointDefinitionParser(String metaOrProtocol, boolean isMeta,
                                             MuleDefinitionParser endpointParser,
                                             String[] requiredAddressAttributes,
                                             String[] requiredProperties)
    {
        this(metaOrProtocol, isMeta, endpointParser,
                RESTRICTED_ENDPOINT_ATTRIBUTES, URIBuilder.ALL_ATTRIBUTES,
                new String[][]{requiredAddressAttributes}, new String[][]{requiredProperties});
    }

    /**
     * @param metaOrProtocol The transport metaOrProtocol ("tcp" etc)
     * @param isMeta Whether transport is "meta" or not (eg cxf)
     * @param endpointParser The parser for the endpoint
     * @param endpointAttributes A list of attribute names which will be set as properties on the
     * endpoint builder
     * @param addressAttributes A list of attribute names which will be set as properties on the
     * endpoint URI builder
     * @param requiredAddressAttributes A list of attribute names that are required if "address"
     * isn't present
     * @param requiredProperties A list of property names that are required if "address" isn't present
     */
    public AddressedEndpointDefinitionParser(String metaOrProtocol, boolean isMeta,
                                             MuleDefinitionParser endpointParser,
                                             String[] endpointAttributes,
                                             String[] addressAttributes,
                                             String[][] requiredAddressAttributes,
                                             String[][] requiredProperties)
    {
        // the first delegate, the parent, is an endpoint; we block everything except the endpoint attributes
        enableAttributes(endpointParser, endpointAttributes);
        enableAttribute(endpointParser, AbstractMuleBeanDefinitionParser.ATTRIBUTE_NAME);
        addDelegate(endpointParser);

        // we handle the address and properties separately, setting the
        // properties directly on the endpoint (rather than as part of the address)
        MuleChildDefinitionParser addressParser =
                new AddressParser(metaOrProtocol, isMeta, addressAttributes, requiredAddressAttributes);

        // this handles the exception thrown if a ref is found in the address parser
        addHandledException(BlockAttribute.BlockAttributeException.class);
        addChildDelegate(addressParser);

        MuleChildDefinitionParser propertiesParser =
                new PropertiesParser(PROPERTIES, endpointAttributes, requiredAddressAttributes, requiredProperties);
        addChildDelegate(propertiesParser);
    }

    private static class AddressParser extends ChildAddressDefinitionParser
    {

        public AddressParser(String metaOrProtocol, boolean isMeta,
                             String[] addressAttributes, String[][] requiredAddressAttributes)
        {
            super(metaOrProtocol, isMeta);

            // this handles the "ref problem" - we don't want this parsers to be used if a "ref"
            // defines the address so add a preprocessor to check for that and indicate that the
            // exception should be handled internally, rather than shown to the user.
            // we do this before the extra processors below so that this is called last,
            // allowing other processors to check for conflicts between ref and other attributes
            registerPreProcessor(new BlockAttribute(AbstractMuleBeanDefinitionParser.ATTRIBUTE_REF));

            // the address parser sees only the endpoint attributes
            enableAttributes(this, addressAttributes);

            // we require either a reference, an address, or the attributes specified
            // (properties can be used in parallel with "address")
            String[][] addressAttributeSets =
            new String[(null != requiredAddressAttributes ? requiredAddressAttributes.length : 0) + 2][];
            addressAttributeSets[0] = new String[]{URIBuilder.ADDRESS};
            addressAttributeSets[1] = new String[]{AbstractMuleBeanDefinitionParser.ATTRIBUTE_REF};
            if (null != requiredAddressAttributes)
            {
                enableAttributes(this, requiredAddressAttributes);
                System.arraycopy(requiredAddressAttributes, 0, addressAttributeSets, 2, requiredAddressAttributes.length);
            }
            registerPreProcessor(new CheckRequiredAttributes(addressAttributeSets));
            // and they must be exclusive
            registerPreProcessor(new CheckExclusiveAttributes(addressAttributeSets));
        }

    }

    private static class PropertiesParser extends AttributePropertiesDefinitionParser
    {

        public PropertiesParser(String setter,
                String[] endpointAttributes, String[][] requiredAddressAttributes, String[][] requiredProperties)
        {
            super(setter);

            // the properties parser gets to see everything that the other parsers don't - if you
            // don't want something, don't enable it in the schema!
            disableAttributes(this, endpointAttributes);
            disableAttributes(this, URIBuilder.ALL_ATTRIBUTES);
            disableAttributes(this, requiredAddressAttributes);
            disableAttribute(this, AbstractMuleBeanDefinitionParser.ATTRIBUTE_NAME);
            disableAttribute(this, AbstractMuleBeanDefinitionParser.ATTRIBUTE_REF);
            if (null != requiredProperties && requiredProperties.length > 0 &&
                    null != requiredProperties[0] && requiredProperties[0].length > 0)
            {
                // if "ref" is present then we don't complain if required properties are missing, since they
                // must have been provided on the global endpoint
                String[][] requiredPropertiesSets = new String[requiredProperties.length + 1][];
                requiredPropertiesSets[0] = new String[]{AbstractMuleBeanDefinitionParser.ATTRIBUTE_REF};
                System.arraycopy(requiredProperties, 0, requiredPropertiesSets, 1, requiredProperties.length);
                registerPreProcessor(new CheckRequiredAttributes(requiredPropertiesSets));
            }
        }

    }

}
