# ThrottleFactory.java

    /*
    *  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 org.apache.synapse.commons.throttle.core;

    import org.apache.axiom.om.OMElement;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.apache.neethi.Policy;
    import org.apache.neethi.PolicyEngine;
    import org.apache.neethi.builders.xml.XmlPrimtiveAssertion;
    import org.apache.synapse.commons.resolvers.ResolverFactory;
    import org.apache.synapse.commons.throttle.core.factory.CallerConfigurationFactory;
    import org.apache.synapse.commons.throttle.core.factory.ThrottleConfigurationFactory;
    import org.apache.synapse.commons.throttle.core.factory.ThrottleContextFactory;
    import javax.xml.namespace.QName;
    import java.util.Iterator;
    import java.util.List;

    /**
     * Factory for creating a throttle instance using throttle policy
     */
    public class ThrottleFactory {

        private ThrottleFactory() {
        }

        private static Log log = LogFactory.getLog(ThrottleFactory.class);

        /**
         * Abstraction for processing module policy assertion and create a throttle based on it
         *
         * @param policy Throttle policy
         * @return Throttle instance , if there any module policy assertion , otherwise null
         * @throws ThrottleException
         */
        public static Throttle createModuleThrottle(Policy policy) throws ThrottleException {
            return createThrottle(policy, ThrottleConstants.MODULE_THROTTLE_ASSERTION_QNAME);
        }

        /**
         * Abstraction for processing service policy assertion and create a throttle based on it
         *
         * @param policy Throttle policy
         * @return Throttle instance , if there any service policy assertion , otherwise null
         * @throws ThrottleException
         */
        public static Throttle createServiceThrottle(Policy policy) throws ThrottleException {
            return createThrottle(policy, ThrottleConstants.SERVICE_THROTTLE_ASSERTION_QNAME);
        }

        /**
         * Abstraction for processing operation policy assertion and create a throttle based on it
         *
         * @param policy Throttle policy
         * @return Throttle instance , if there any operation policy assertion , otherwise null
         * @throws ThrottleException
         */
        public static Throttle createOperationThrottle(Policy policy) throws ThrottleException {
            return createThrottle(policy, ThrottleConstants.OPERATION_THROTTLE_ASSERTION_QNAME);
        }

        /**
         * Abstraction for processing mediator policy assertion and create a throttle based on it
         *
         * @param policy Throttle policy
         * @return Throttle instance , if there any mediator policy assertion , otherwise null
         * @throws ThrottleException
         */
        public static Throttle createMediatorThrottle(Policy policy) throws ThrottleException {
            return createThrottle(policy, ThrottleConstants.MEDIATOR_THROTTLE_ASSERTION_QNAME);
        }

        /**
         * Factory method to create a throttle from a given throttle policy
         *
         * @param policy    Throttle policy
         * @param forceRoot Root assertion QName for select correct policy assertion
         * @return Throttle instance
         * @throws ThrottleException
         */
        private static Throttle createThrottle(Policy policy, QName forceRoot)
                throws ThrottleException {

            if (policy == null) {
                if (log.isDebugEnabled()) {
                    log.debug("Policy cannot be found");
                }
                //if policy is not available ,then return null for ThrottleConfiguration
                return null; // no policy is available in the module description
            }

            if (forceRoot == null) {
                if (log.isDebugEnabled()) {
                    log.debug("Given root assertion QName is null");
                }
                return null;
            }

            for (Iterator iterator = policy.getAlternatives();
                 iterator.hasNext(); ) {
                Object object = iterator.next();
                if (object instanceof List) {
                    List list = (List) object;
                    for (Iterator it = list.iterator(); it.hasNext(); ) {
                        Object assertObj = it.next();
                        if (assertObj instanceof XmlPrimtiveAssertion) {
                            XmlPrimtiveAssertion primitiveAssertion = (XmlPrimtiveAssertion)
                                    assertObj;
                            QName qName = primitiveAssertion.getName();

                            if (qName == null) {
                                handleException("Invalid Throttle Policy - QName of the " +
                                        "assertion cannot be null.");
                            }
                            // top policy must contains ThrottleAssertion
                            Policy throttlePolicy = PolicyEngine.
                                    getPolicy(primitiveAssertion.getValue());
                            if (ThrottleConstants.THROTTLE_ASSERTION_QNAME.equals(qName)) {
                                return ThrottlePolicyProcessor.processPolicy(throttlePolicy);
                            } else if (forceRoot.equals(qName)) {
                                return buildThrottle(throttlePolicy);
                            } else {
                                if (log.isDebugEnabled()) {
                                    log.debug("There is no throttle policy " +
                                            "for given QName : " + forceRoot);
                                }
                            }
                        }
                    }
                }
            }
            return null;

        }

        /**
         * Factory method to help to create a throttle
         *
         * @param throtlePolicy Throttle assertion policy
         * @return Throttle instance
         * @throws ThrottleException
         */
        private static Throttle buildThrottle(Policy throtlePolicy) throws ThrottleException {
            Throttle throttle = new Throttle();   // throttle instance
            ThrottleConfiguration configuration = null;  // configuration data
            List list = throtlePolicy.getPolicyComponents();

            if (list == null || (list != null && list.isEmpty())) {
                handleException("Empty the policy components" +
                        " as ThrottleAssertion's children");
            }
            for (Iterator iterator = list.iterator(); iterator.hasNext(); ) {
                Object object = iterator.next();
                if (object instanceof Policy) {

                    // boolean isOtherConfiguration = false;
                    //  // To track default callerConfiguration for all ips
                    CallerConfiguration callerConfiguration = null;
                    Policy policy = null;
                    List assertList = ((Policy) object).getAssertions();
                    if (assertList != null) {
                        for (Iterator assertIterator =
                             assertList.iterator(); assertIterator.hasNext(); ) {
                            Object ca = assertIterator.next();
                            if (ca instanceof XmlPrimtiveAssertion) {
                                XmlPrimtiveAssertion id = (XmlPrimtiveAssertion) ca;
                                configuration = createThrottleConfiguration(id, throttle);
                                if (configuration == null) {
                                    handleException("Invalid throttle - Throttle configuration " +
                                            "cannot be created from given policy");
                                }

                                if (configuration.getType() == ThrottleConstants.IP_BASE) {
                                    //create a caller context for ip based throttle
                                    callerConfiguration = CallerConfigurationFactory.
                                            createCallerConfiguration(
                                                    ThrottleConstants.IP_BASE);
                                } else if (configuration.getType() == ThrottleConstants.DOMAIN_BASE) {
                                    //create a caller context for ip based throttle
                                    callerConfiguration = CallerConfigurationFactory.
                                            createCallerConfiguration(
                                                    ThrottleConstants.DOMAIN_BASE);
                                } else if (configuration.getType() == ThrottleConstants.ROLE_BASE) {
                                    //create a caller context for ip based throttle
                                    callerConfiguration = CallerConfigurationFactory.
                                            createCallerConfiguration(
                                                    ThrottleConstants.ROLE_BASE);
                                } else {
                                    handleException("Invalid throttle type - Only" +
                                            " support IP ,DOMAIN and ROLE as types ");
                                }
                                if (callerConfiguration != null) {
                                    OMElement element = id.getValue();

                                    // Name of the policy assertion
                                    String name = element.getLocalName();
                                    // Value of the policy assertion
                                    String value = element.getText();

                                    // If Value and Name  are null,
                                    // then it is a invalid policy configuration
                                    if (name == null || value == null) {
                                        handleException("Either Value or" +
                                                " Name of the policy cannot be null");
                                    } else if (name.equals(ThrottleConstants.ID_PARAMETER_NAME)) {

                                        if (!value.equals("")) {
                                            callerConfiguration.setID(value);
                                        } else {
                                            handleException("Value of ID cannot find " +
                                                    "- invalid configuration");
                                        }
                                    } else {
                                        handleException("Undefined policy property for" +
                                                " throttle - Expect ID  ");
                                    }
                                }

                            } else if (ca instanceof Policy) {
                                policy = (Policy) ca;
                            }
                        }
                    }
                    if (callerConfiguration != null) {
                        if (policy != null) {
                            fillCallerConfiguration(policy, callerConfiguration);
                            configuration.addCallerConfiguration(callerConfiguration);
                        }

                    } else {
                        if (log.isDebugEnabled()) {
                            log.debug("Couldn't find a callerConfiguration for a throttle" +
                                    " configuration for an one caller  ");
                        }
                    }

                } else if (object instanceof XmlPrimtiveAssertion) {

                    XmlPrimtiveAssertion xmlPrimitiveAssertion = (XmlPrimtiveAssertion) object;
                    OMElement element = xmlPrimitiveAssertion.getValue();
                    // Name of the policy assertion
                    String name = element.getLocalName();
                    //Value of the policy assertion
                    String value = element.getText();

                    //if Value and Name  are null,then
                    // it is a invalid policy configuration
                    if (name == null || value == null) {
                        handleException("Either value or name of the policy cannot be null");
                    } else if (name.equals(
                            ThrottleConstants.MAXIMUM_CONCURRENT_ACCESS_PARAMETER_NAME)) {
                        int intValue = 0;
                        try {
                            intValue = Integer.parseInt(value.trim());
                        } catch (NumberFormatException ignored) {
                            log.error("Error occurred - Invalid number for maximum " +
                                    "concurrent access ", ignored);
                        }
                        if (intValue > 0) {
                            throttle.setConcurrentAccessController(
                                    new ConcurrentAccessController(intValue));
                        }
                    } else {
                        handleException("Invalid throttle policy configuration : unexpected policy " +
                                "element with name " + name);
                    }
                }
            }
            return throttle;
        }

        /**
         * Factory method to create a Throttle Configuration instance
         *
         * @param id       Policy assertion relates to a particular caller
         * @param throttle Throttle instance
         * @return Throttle Configuration instance
         * @throws ThrottleException
         */
        private static ThrottleConfiguration createThrottleConfiguration(XmlPrimtiveAssertion id,
                                                          Throttle throttle) throws ThrottleException {
            OMElement element = id.getValue();
            ThrottleConfiguration configuration = null;

            String type = element.getAttributeValue(
                    ThrottleConstants.THROTTLE_TYPE_ATTRIBUTE_QNAME);
            if (type == null) {
                handleException("Type of Throttle " +
                        "in the policy cannot be null");
            }
            if ("IP".equals(type)) {
                // create a ip based throttle context and configuration
                configuration = throttle.getThrottleConfiguration
                        (ThrottleConstants.IP_BASED_THROTTLE_KEY);
                if (configuration == null) {
                    configuration =
                            ThrottleConfigurationFactory.
                                    createThrottleConfiguration(
                                            ThrottleConstants.IP_BASE);
                    throttle.addThrottleContext(
                            ThrottleConstants.IP_BASED_THROTTLE_KEY,
                            ThrottleContextFactory.createThrottleContext(
                                    ThrottleConstants.IP_BASE, configuration));
                    throttle.addThrottleConfiguration(
                            ThrottleConstants.IP_BASED_THROTTLE_KEY, configuration);
                }

            } else if (("DOMAIN".equals(type))) {
                // create a domain based throttle context and configuration
                configuration = throttle.getThrottleConfiguration(
                        ThrottleConstants.DOMAIN_BASED_THROTTLE_KEY);
                if (configuration == null) {
                    configuration =
                            ThrottleConfigurationFactory.
                                    createThrottleConfiguration(
                                            ThrottleConstants.DOMAIN_BASE);
                    throttle.addThrottleContext(
                            ThrottleConstants.DOMAIN_BASED_THROTTLE_KEY,
                            ThrottleContextFactory.createThrottleContext(
                                    ThrottleConstants.DOMAIN_BASE, configuration));
                    throttle.addThrottleConfiguration(
                            ThrottleConstants.DOMAIN_BASED_THROTTLE_KEY, configuration);
                }
                //create a caller context for domain based throttle

            } else if (("ROLE".equals(type))) {
                // create a domain based throttle context and configuration
                configuration = throttle.getThrottleConfiguration(
                        ThrottleConstants.ROLE_BASED_THROTTLE_KEY);
                if (configuration == null) {
                    configuration =
                            ThrottleConfigurationFactory.
                                    createThrottleConfiguration(
                                            ThrottleConstants.ROLE_BASE);
                    throttle.addThrottleContext(
                            ThrottleConstants.ROLE_BASED_THROTTLE_KEY,
                            ThrottleContextFactory.createThrottleContext(
                                    ThrottleConstants.ROLE_BASE, configuration));
                    throttle.addThrottleConfiguration(
                            ThrottleConstants.ROLE_BASED_THROTTLE_KEY, configuration);
                }
                //create a caller context for domain based throttle

            } else {
                handleException("Unsupported throttle type : " + type);
            }

            return configuration;
        }

        /**
         * Fills the caller configuration information based on given policy
         *
         * @param policy              Policy instance
         * @param callerConfiguration Caller configuration instance
         * @throws ThrottleException
         */
        private static void fillCallerConfiguration(Policy policy,
                                    CallerConfiguration callerConfiguration) throws ThrottleException {
            List list = policy.getPolicyComponents();
            for (Iterator iterator = list.iterator(); iterator.hasNext(); ) {
                Object object = iterator.next();

                XmlPrimtiveAssertion primitiveAssertion = (XmlPrimtiveAssertion) object;
                OMElement element = primitiveAssertion.getValue();
                // Name of the policy assertion
                String name = element.getLocalName();
                if (name.equals(
                        ThrottleConstants.ALLOW_PARAMETER_NAME)) {

                    callerConfiguration.setAccessState(
                            ThrottleConstants.ACCESS_ALLOWED);

                } else if (name.equals(
                        ThrottleConstants.DENY_PARAMETER_NAME)) {

                    callerConfiguration.setAccessState(
                            ThrottleConstants.ACCESS_DENIED);

                } else if (name.equals(
                        ThrottleConstants.CONTROL_PARAMETER_NAME)) {

                    callerConfiguration.setAccessState(
                            ThrottleConstants.ACCESS_CONTROLLED);
                    OMElement controlElement = primitiveAssertion.getValue();
                    if (controlElement == null) {
                        handleException("Invalid throttle configuration - " +
                                "Control assertion cannot be empty");
                    }

                    Policy controlPolicy = PolicyEngine.getPolicy(controlElement);
                    if (controlPolicy != null) {
                        fillControlConfiguration(controlPolicy, callerConfiguration);
                    } else {
                        handleException("Invalid throttle configuration - " +
                                "Cannot create a policy object(Control Assertion ) " +
                                "form given policy file ");
                    }

                } else {
                    handleException("Invalid Throttle" +
                            " Policy configuration");
                }
            }
        }

        /**
         * Helper method to process control assertion
         *
         * @param policy              Policy for Control Assertion
         * @param callerConfiguration Caller to whom control need to be applied.
         * @throws ThrottleException
         */
        private static void fillControlConfiguration(Policy policy,
                                                     CallerConfiguration callerConfiguration)
                throws ThrottleException {
            boolean isFoundMaxCount = false;
            boolean isFoundUnitTime = false;
            List list = policy.getPolicyComponents();
            for (Iterator iterator = list.iterator(); iterator.hasNext(); ) {
                Object obj = iterator.next();
                if (obj instanceof Policy) {
                    List controlList = ((Policy) obj).getPolicyComponents();
                    for (Iterator controlIterator = controlList.iterator();
                                                         controlIterator.hasNext(); ) {
                        Object object = controlIterator.next();
                        if (object instanceof XmlPrimtiveAssertion) {
                            XmlPrimtiveAssertion primitiveAssertion =
                                    (XmlPrimtiveAssertion) object;
                            OMElement element = primitiveAssertion.getValue();
                            // Name of the policy assertion
                            String name = element.getLocalName();
                            //Value of the policy assertion
                            String value = element.getText();

                            //if Value and Name  are null,then it is a
                            // invalid policy configuration
                            if (name == null || value == null) {
                                handleException("Either Value or " +
                                        "Name of the policy cannot be null");
                            }
                            if (!value.equals("")) {
                                String resolvedValue = ResolverFactory.getInstance().getResolver(value.trim()).resolve();

                                if (name.equals(
                                        ThrottleConstants.
                                                MAXIMUM_COUNT_PARAMETER_NAME)) {

                                    isFoundMaxCount = true;
                                    try {
                                        callerConfiguration.setMaximumRequestPerUnitTime(
                                                Integer.parseInt(resolvedValue));
                                    } catch (NumberFormatException ignored) {
                                        log.error("Error occurred - " +
                                                "Invalid number for maximum " +
                                                "request number ", ignored);
                                        if (log.isDebugEnabled()) {
                                            log.debug("Access" +
                                                    " will be fully allowed");
                                        }
                                        callerConfiguration.setAccessState(
                                                ThrottleConstants.ACCESS_ALLOWED);
                                    }
                                } else if (name.equals(
                                        ThrottleConstants.
                                                UNIT_TIME_PARAMETER_NAME)) {
                                    //TODO need to verify that value is in milisecond
                                    long timeInMilliSec = 0;
                                    try {
                                        timeInMilliSec =
                                                Long.parseLong(resolvedValue);
                                    } catch (NumberFormatException ignored) {
                                        log.error("Error occurred " +
                                                        "- Invalid number for unit time",
                                                ignored);
                                    }
                                    if (timeInMilliSec == 0) {
                                        handleException("Unit Time cannot " +
                                                "find - invalid throttle " +
                                                "policy configuration");
                                    }
                                    isFoundUnitTime = true;
                                    callerConfiguration.setUnitTime(timeInMilliSec);

                                } else if (name.equals(
                                        ThrottleConstants.
                                                PROHIBIT_TIME_PERIOD_PARAMETER_NAME)) {
                                    try {
                                        callerConfiguration.setProhibitTimePeriod(
                                                Long.parseLong(resolvedValue));
                                    } catch (NumberFormatException ignored) {
                                        log.error("Error occurred - Invalid" +
                                                        " number for prohibit time ",
                                                ignored);
                                    }
                                } else {
                                    handleException("Undefined Policy" +
                                            " property for Throttle Policy");
                                }
                            } else {
                                if (!name.equals(
                                        ThrottleConstants.
                                                PROHIBIT_TIME_PERIOD_PARAMETER_NAME)) {
                                    handleException("The policy which have " +
                                            " defined as optional " +
                                            "should have value ");
                                }
                            }
                        }
                    }
                } else {
                    handleException("Invalid policy - " +
                            "Control Assertion must contain a wsp:Policy as child ");
                }

            }
            if (!isFoundUnitTime && !isFoundMaxCount) {
                handleException("Maximum Count and UnitTime are " +
                        "Mandatory in Throttle Policy ");

            }
        }

        /**
         * Helper method to handle exception
         *
         * @param message Debug message
         * @throws ThrottleException
         */
        private static void handleException(String message) throws ThrottleException {
            String msg = "Error was occurred during throttle policy processing :  " + message;
            log.error(msg);
            throw new ThrottleException(msg);
        }
    }