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

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMException;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.om.OMText;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axiom.om.util.AXIOMUtil;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.SynapseConstants;
import org.apache.synapse.SynapseException;
import org.apache.synapse.aspects.AspectConfiguration;
import org.apache.synapse.aspects.flow.statistics.StatisticIdentityGenerator;
import org.apache.synapse.aspects.flow.statistics.StatisticSynapseConfigurationObserver;
import org.apache.synapse.aspects.flow.statistics.collectors.RuntimeStatisticCollector;
import org.apache.synapse.core.SynapseEnvironment;
import org.apache.synapse.mediators.MediatorProperty;
import org.apache.synapse.mediators.base.SequenceMediator;
import org.apache.synapse.mediators.builtin.DropMediator;
import org.apache.synapse.mediators.builtin.LogMediator;
import org.apache.synapse.util.SynapseBinaryDataSource;
import org.apache.synapse.util.xpath.SynapseXPath;
import org.jaxen.JaxenException;
import org.wso2.securevault.definition.IdentityKeyStoreInformation;
import org.wso2.securevault.definition.KeyStoreInformation;
import org.wso2.securevault.definition.KeyStoreInformationFactory;
import org.wso2.securevault.definition.TrustKeyStoreInformation;
import org.xml.sax.InputSource;

import javax.activation.DataHandler;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.stream.StreamSource;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;


@SuppressWarnings({"UnusedDeclaration"})
public class SynapseConfigUtils {

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


    private static ConcurrentHashMap<String, SynapseConfiguration> lastRegisteredSynapseConfigurationMap =
                                                                 new ConcurrentHashMap<String, SynapseConfiguration>();

    /**
     * Return a StreamSource for the given Object
     *
     * @param o the object
     * @return the StreamSource
     */
    public static StreamSource getStreamSource(Object o) {

        if (o == null) {
            handleException("Cannot convert null to a StreamSource");

        } else if (o instanceof OMElement) {
            OMElement omElement = (OMElement) o;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                omElement.serialize(baos);
                return new StreamSource(new ByteArrayInputStream(baos.toByteArray()));
            } catch (XMLStreamException e) {
                handleException("Error converting to a StreamSource", e);
            }

        } else if (o instanceof OMText) {
            DataHandler dataHandler = (DataHandler) ((OMText) o).getDataHandler();
            if (dataHandler != null) {
                try {
                    return new StreamSource(dataHandler.getInputStream());
                } catch (IOException e) {
                    handleException("Error in reading content as a stream ");
                }
            }
        } else {

            handleException("Cannot convert object to a StreamSource");
        }
        return null;
    }

    public static InputStream getInputStream(Object o) {

        if (o == null) {
            handleException("Cannot convert null to a StreamSource");

        } else if (o instanceof OMElement) {
            OMElement omElement = (OMElement) o;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                omElement.serialize(baos);
                return new ByteArrayInputStream(baos.toByteArray());
            } catch (XMLStreamException e) {
                handleException("Error converting to a StreamSource", e);
            }

        } else if (o instanceof OMText) {
            DataHandler dataHandler = (DataHandler) ((OMText) o).getDataHandler();
            if (dataHandler != null) {
                try {
                    return dataHandler.getInputStream();
                } catch (IOException e) {
                    handleException("Error in reading content as a stream ");
                }
            }
        } else if (o instanceof URI) {
            try {
                return ((URI) (o)).toURL().openStream();
            } catch (IOException e) {
                handleException("Error opening stream form URI", e);
            }
        } else {
            handleException("Cannot convert object to a StreamSource");
        }
        return null;
    }

    /**
     * Get an object from a given URL. Will first fetch the content from the
     * URL and depending on the content-type, a suitable XMLToObjectMapper
     * (if available) would be used to transform this content into an Object.
     * If a suitable XMLToObjectMapper cannot be found, the content would be
     * treated as XML and an OMNode would be returned
     *
     * @param url the URL to the resource
     * @param properties bag of properties to pass in any information to the factory
     * @return an Object created from the given URL
     */
    public static Object getObject(URL url, Properties properties) {
        try {
            if (url != null && "file".equals(url.getProtocol())) {
                try {
                    url.openStream();
                } catch (IOException ignored) {
                    String path = url.getPath();
                    if (log.isDebugEnabled()) {
                        log.debug("Can not open a connection to the URL with a path :" +
                                path);
                    }
                    String synapseHome = (String) properties.get(SynapseConstants.SYNAPSE_HOME);
                    if (synapseHome != null) {
                        if (log.isDebugEnabled()) {
                            log.debug("Trying  to resolve an absolute path of the " +
                                    " URL using the synapse.home : " + synapseHome);
                        }
                        if (synapseHome.endsWith("/")) {
                            synapseHome = synapseHome.substring(0, synapseHome.lastIndexOf("/"));
                        }
                        url = new URL(url.getProtocol() + ":" + synapseHome + "/" + path);
                        try {
                            url.openStream();
                        } catch (IOException e) {
                            if (log.isDebugEnabled()) {
                                log.debug("Failed to resolve an absolute path of the " +
                                        " URL using the synapse.home : " + synapseHome);
                            }
                            log.warn("IO Error reading from URL " + url.getPath() + e);
                        }
                    }
                }
            }
            if (url == null) {
                return null;
            }
            URLConnection connection = getURLConnection(url);
            if (connection == null) {
                if (log.isDebugEnabled()) {
                    log.debug("Cannot create a URLConnection for given URL : " + url);
                }
                return null;
            }
            XMLToObjectMapper xmlToObject =
                    getXmlToObjectMapper(connection.getContentType());
            InputStream inputStream = connection.getInputStream();
            try {
                XMLStreamReader parser = XMLInputFactory.newInstance().
                        createXMLStreamReader(inputStream);
                StAXOMBuilder builder = new StAXOMBuilder(parser);
                OMElement omElem = builder.getDocumentElement();

                // detach from URL connection and keep in memory
                // TODO remove this 
                omElem.build();

                if (xmlToObject != null) {
                    return xmlToObject.getObjectFromOMNode(omElem, properties);
                } else {
                    return omElem;
                }

            } catch (XMLStreamException e) {
                if (log.isDebugEnabled()) {
                    log.debug("Content at URL : " + url + " is non XML..");
                }
                return readNonXML(url);
            } catch (OMException e) {
                if (log.isDebugEnabled()) {
                    log.debug("Content at URL : " + url + " is non XML..");
                }
                return readNonXML(url);
            } finally {
                inputStream.close();
            }

        } catch (IOException e) {
            handleException("Error connecting to URL : " + url, e);
        }
        return null;
    }

    /**
     * Helper method to handle non-XMl resources
     *
     * @param url The resource url
     * @return The content as an OMNode
     */
    public static OMNode readNonXML(URL url) {

        try {
            // Open a new connection
            URLConnection newConnection = getURLConnection(url);
            if (newConnection == null) {
                if (log.isDebugEnabled()) {
                    log.debug("Cannot create a URLConnection for given URL : " + url);
                }
                return null;
            }

            BufferedInputStream newInputStream = new BufferedInputStream(
                    newConnection.getInputStream());

            OMFactory omFactory = OMAbstractFactory.getOMFactory();
            return omFactory.createOMText(
                    new DataHandler(new SynapseBinaryDataSource(newInputStream,
                            newConnection.getContentType())), true);

        } catch (IOException e) {
            handleException("Error when getting a stream from resource's content", e);
        }
        return null;
    }

    /**
     * Return an OMElement from a URL source
     *
     * @param urlStr a URL string
     * @param synapseHome synapse home parameter to be used
     * @return an OMElement of the resource
     * @throws IOException for invalid URL's or IO errors
     */
    public static OMNode getOMElementFromURL(String urlStr, String synapseHome) throws IOException {

        URL url = getURLFromPath(urlStr, synapseHome);
        if (url == null) {
            return null;
        }
        URLConnection connection = null;

        //If url contains http basic authentication parameters.
        if(url.getUserInfo() != null){

            String protocol = url.getProtocol();

            if("http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol)){

                //Create new url excluding user info
                URL newUrl = new URL(protocol, url.getHost(), url.getPort(), url.getFile());

                connection = getURLConnection(newUrl);

                String encoding = StringUtils.removeEnd(new String(new Base64().encode(URLDecoder.decode(url
                        .getUserInfo(), "UTF8").getBytes())), "\r\n");

                connection.setRequestProperty("Authorization", "Basic " + encoding);
            }
            else{
                handleException("Unsuported protocol [" + protocol + "]. Supports only http and https with " +
                        "basic authentication");
            }
        }
        else{
            connection = getURLConnection(url);
        }

        if (connection == null) {
            if (log.isDebugEnabled()) {
                log.debug("Cannot create a URLConnection for given URL : " + urlStr);
            }
            return null;
        }
        InputStream inStream = connection.getInputStream();
        try {
            StAXOMBuilder builder = new StAXOMBuilder(inStream);
            OMElement doc = builder.getDocumentElement();
            doc.build();
            return doc;
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.info("Content at URL : " + url + " is non XML..");
            }
            return readNonXML(url);
        } finally {
            try {
                inStream.close();
            } catch (IOException e) {
                log.warn("Error while closing the input stream to: " + url, e);
            }
        }
    }

    public static InputSource getInputSourceFormURI(URI uri) {

        if (uri == null) {
            if (log.isDebugEnabled()) {
                log.debug("Can not create a URL from 'null' ");
            }
            return null;
        }
        try {
            URL url = uri.toURL();
            String protocol = url.getProtocol();
            String path = url.getPath();
            if (protocol == null || "".equals(protocol)) {
                url = new URL("file:" + path);
            }
            URLConnection connection = getURLConnection(url);
            if (connection == null) {
                if (log.isDebugEnabled()) {
                    log.debug("Cannot create a URLConnection for given URL : " + uri);
                }
                return null;
            }
            BufferedInputStream urlInStream = new BufferedInputStream(connection.getInputStream());
            return new InputSource(urlInStream);
        } catch (MalformedURLException e) {
            handleException("Invalid URL ' " + uri + " '", e);
        } catch (IOException e) {
            handleException("IOError when getting a stream from given url : " + uri, e);
        }
        return null;
    }

    private static void handleException(String msg, Exception e) {
        log.warn(msg, e);
        throw new SynapseException(msg, e);
    }

    /**
     * Helper method to create a HttpSURLConnection with provided KeyStores
     *
     * @param url Https URL
     * @param synapseProperties properties for extracting info
     * @param proxy if there is a proxy
     * @return gives out the connection created
     */
    private static HttpsURLConnection getHttpsURLConnection(
            URL url, Properties synapseProperties, Proxy proxy) {

       if (log.isDebugEnabled()) {
            log.debug("Creating a HttpsURL Connection from given URL : " + url);
        }

        KeyManager[] keyManagers = null;
        TrustManager[] trustManagers = null;

        IdentityKeyStoreInformation identityInformation =
                KeyStoreInformationFactory.createIdentityKeyStoreInformation(synapseProperties);

        if (identityInformation != null) {
            KeyManagerFactory keyManagerFactory =
                    identityInformation.getIdentityKeyManagerFactoryInstance();
            if (keyManagerFactory != null) {
                keyManagers = keyManagerFactory.getKeyManagers();
            }

        } else {
            if (log.isDebugEnabled()) {
                log.debug("There is no private key entry store configuration." +
                        " Will use JDK's default one");
            }
        }

        TrustKeyStoreInformation trustInformation =
                KeyStoreInformationFactory.createTrustKeyStoreInformation(synapseProperties);

        if (trustInformation != null) {
            TrustManagerFactory trustManagerFactory =
                    trustInformation.getTrustManagerFactoryInstance();
            if (trustManagerFactory != null) {
                trustManagers = trustManagerFactory.getTrustManagers();
            }
        } else {
            if (log.isDebugEnabled()) {
                log.debug("There is no trusted certificate store configuration." +
                        " Will use JDK's default one");
            }
        }

        try {
            HttpsURLConnection connection;
            if (proxy != null) {
                connection = (HttpsURLConnection) url.openConnection(proxy);
            } else {
                connection = (HttpsURLConnection) url.openConnection();
            }
            //Create a SSLContext
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagers,
                    trustManagers, null);
            connection.setSSLSocketFactory(sslContext.getSocketFactory());

            if (trustInformation != null) {
                // Determine is it need to overwrite default Host Name verifier
                boolean enableHostnameVerifier = true;
                String value =
                        trustInformation.getParameter(
                                KeyStoreInformation.ENABLE_HOST_NAME_VERIFIER);
                if (value != null) {
                    enableHostnameVerifier = Boolean.parseBoolean(value);
                }

                if (!enableHostnameVerifier) {

                    if (log.isDebugEnabled()) {
                        log.debug("Overriding default HostName Verifier." +
                                "HostName verification disabled");
                    }

                    connection.setHostnameVerifier(new javax.net.ssl.HostnameVerifier() {
                        public boolean verify(String hostname, javax.net.ssl.SSLSession session) {
                            if (log.isTraceEnabled()) {
                                log.trace("HostName verification disabled");
                                log.trace("Host:   " + hostname);
                                log.trace("Peer Host:  " + session.getPeerHost());
                            }
                            return true;
                        }
                    });
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("Using default HostName verifier...");
                    }
                }
            }
            return connection;

        } catch (NoSuchAlgorithmException e) {
            handleException("Error loading SSLContext ", e);
        } catch (KeyManagementException e) {
            handleException("Error initiation SSLContext with KeyManagers", e);
        } catch (IOException e) {
            handleException("Error opening a https connection from URL : " + url, e);
        }
        return null;
    }

    /**
     * Returns a URLCOnnection for given URL. If the URL is https one , then URLConnectin is a
     * HttpsURLCOnnection and it is configured with KeyStores given in the synapse.properties file
     *
     * @param url URL
     * @return URLConnection for given URL
     */
    public static URLConnection getURLConnection(URL url) {

         try {
            if (url == null) {
                if (log.isDebugEnabled()) {
                    log.debug("Provided URL is null");
                }
                return null;
            }

            URLConnection connection;
            if (url.getProtocol().equalsIgnoreCase("http") ||
                    url.getProtocol().equalsIgnoreCase("https")) {

                Properties synapseProperties = SynapsePropertiesLoader.loadSynapseProperties();

                String proxyHost = synapseProperties.getProperty(
                        SynapseConstants.SYNPASE_HTTP_PROXY_HOST);
                String proxyPort = synapseProperties.getProperty(
                        SynapseConstants.SYNPASE_HTTP_PROXY_PORT);

                // get the list of excluded hosts for proxy
                List<String> excludedHosts = getExcludedHostsForProxy(synapseProperties);

                if (proxyHost != null && proxyPort != null && !excludedHosts.contains(proxyHost)) {
                    SocketAddress sockaddr = new InetSocketAddress(
                            proxyHost, Integer.parseInt(proxyPort));
                    Proxy proxy = new Proxy(Proxy.Type.HTTP, sockaddr);

                    if (url.getProtocol().equalsIgnoreCase("https")) {
                        connection = getHttpsURLConnection(url, synapseProperties, proxy);
                    } else {
                        connection = url.openConnection(proxy);
                    }
                } else {
                    if (url.getProtocol().equalsIgnoreCase("https")) {
                        connection = getHttpsURLConnection(url, synapseProperties, null);
                    } else {
                        connection = url.openConnection();
                    }
                }

                // try to see weather authentication is required
                String userName = synapseProperties.getProperty(
                        SynapseConstants.SYNPASE_HTTP_PROXY_USER);
                String password = synapseProperties.getProperty(
                        SynapseConstants.SYNPASE_HTTP_PROXY_PASSWORD);
                if (userName != null && password != null) {
                    String header = userName + ":" + password;
                    byte[] encodedHeaderBytes = new Base64().encode(header.getBytes());
                    String encodedHeader = new String(encodedHeaderBytes);

                    connection.setRequestProperty("Proxy-Authorization", "Basic " + encodedHeader);
                }
            } else {
                connection = url.openConnection();
            }

            connection.setReadTimeout(getReadTimeout());
            connection.setConnectTimeout(getConnectTimeout());
            connection.setRequestProperty("Connection", "close"); // if http is being used
            return connection;
        } catch (IOException e) {
            handleException("Error reading at URI ' " + url + " ' ", e);
        }
        return null;
    }

     /**
     * Get the exclued host list for proxy server. When a connection is made for these hosts it will
     * not go through the proxy server
     * @param synapseProperties properties from the synapse.properties file
     * @return list of excluded hosts
     */
    private static List<String> getExcludedHostsForProxy(Properties synapseProperties) {
        List<String> excludedHosts = new ArrayList<String>();
        String excludedHostsConfig = synapseProperties.
                getProperty(SynapseConstants.SYNAPSE_HTTP_PROXY_EXCLUDED_HOSTS);
        if (excludedHostsConfig != null) {
            String [] list = excludedHostsConfig.split(",");

            for (String host : list) {
                excludedHosts.add(host.trim());
            }
        }

        return excludedHosts;
    }

    private static void handleException(String msg) {
        log.warn(msg);
        throw new SynapseException(msg);
    }

    /**
     * Return a suitable XMLToObjectMapper for the given content type if one
     * is available, else return null;
     *
     * @param contentType the content type for which a mapper is required
     * @return a suitable XMLToObjectMapper or null if none can be found
     */
    public static XMLToObjectMapper getXmlToObjectMapper(String contentType) {
        return null;
    }

    /**
     * Utility method to resolve url(only If need) path using synapse home system property
     *
     * @param path Path to the URL
     * @param synapseHome synapse home parameter value to be used
     * @return Valid URL instance or null(if it is invalid or can not open a connection to it )
     */
    public static URL getURLFromPath(String path, String synapseHome) {
        if (path == null || "null".equals(path)) {
            if (log.isDebugEnabled()) {
                log.debug("Can not create a URL from 'null' ");
            }
            return null;
        }
        URL url = null;
        try {
            url = new URL(path);
            if ("file".equals(url.getProtocol())) {
                try {
                    url.openStream();
                } catch (MalformedURLException e) {
                    handleException("Invalid URL reference : " + path, e);
                } catch (IOException ignored) {
                    if (log.isDebugEnabled()) {
                        log.debug("Can not open a connection to the URL with a path :" + path);
                    }
                    if (synapseHome != null) {
                        if (synapseHome.endsWith("/")) {
                            synapseHome = synapseHome.substring(0, synapseHome.lastIndexOf("/"));
                        }
                        if (log.isDebugEnabled()) {
                            log.debug("Trying  to resolve an absolute path of the " +
                                    " URL using the synapse.home : " + synapseHome);
                        }
                        try {
                            url = new URL(url.getProtocol() + ":" + synapseHome + "/" +
                                    url.getPath());
                            url.openStream();
                        } catch (MalformedURLException e) {
                            handleException("Invalid URL reference " + url.getPath() + e);
                        } catch (IOException e) {
                            if (log.isDebugEnabled()) {
                                log.debug("Failed to resolve an absolute path of the " +
                                        " URL using the synapse.home : " + synapseHome);
                            }
                            log.warn("IO Error reading from URL : " + url.getPath() + e);
                        }
                    }
                }
            }
        } catch (MalformedURLException e) {
            handleException("Invalid URL reference :  " + path, e);
        } catch (IOException e) {
            handleException("IO Error reading from URL : " + path, e);
        }
        return url;
    }

    public static String resolveRelativeURI(String parentLocation, String relativeLocation) {

        if (relativeLocation == null) {
            throw new IllegalArgumentException("Import URI cannot be null");
        }

        if (log.isDebugEnabled()) {
            log.debug("Resolving import URI ' " + parentLocation + " '  " +
                    "against base URI ' " + relativeLocation + " '  ");
        }

        URI importUri = null;
        try {
            importUri = new URI(relativeLocation);
            if (importUri.isAbsolute()) {
                return importUri.toString();
            }
        } catch (URISyntaxException e) {
            handleException("Invalid URI : " + relativeLocation, e);
        }

        if (parentLocation == null) {
            assert importUri != null;
            return importUri.toString();
        } else {
            // if the import-uri is absolute
            if (relativeLocation.startsWith("/") || relativeLocation.startsWith("\\")) {
                if (importUri != null && !importUri.isAbsolute()) {
                    try {
                        importUri = new URI("file:" + relativeLocation);
                        return importUri.toString();
                    } catch (URISyntaxException e) {
                        handleException("Invalid URI ' " + importUri.getPath() + " '", e);
                    }
                }
            } else {
                int index = parentLocation.lastIndexOf("/");
                if (index == -1) {
                    index = parentLocation.lastIndexOf("\\");
                }
                if (index != -1) {
                    String basepath = parentLocation.substring(0, index + 1);
                    String resolvedPath = basepath + relativeLocation;
                    try {
                        URI resolvedUri = new URI(resolvedPath);
                        if (!resolvedUri.isAbsolute()) {
                            resolvedUri = new URI("file:" + resolvedPath);
                        }
                        return resolvedUri.toString();
                    } catch (URISyntaxException e) {
                        handleException("Invalid URI ' " + resolvedPath + " '", e);
                    }
                } else {
                    assert importUri != null;
                    return importUri.toString();
                }
            }
        }
        return null;
    }

    public static int getConnectTimeout() {
        return Integer.parseInt(SynapsePropertiesLoader.getPropertyValue(
                SynapseConstants.CONNECTTIMEOUT,
                String.valueOf(SynapseConstants.DEFAULT_CONNECTTIMEOUT)));

    }

    public static int getReadTimeout() {
        return Integer.parseInt(SynapsePropertiesLoader.getPropertyValue(
                SynapseConstants.READTIMEOUT,
                String.valueOf(SynapseConstants.DEFAULT_READTIMEOUT)));

    }

    public static long getTimeoutHandlerInterval() {
        return Long.parseLong(SynapsePropertiesLoader.getPropertyValue(
                SynapseConstants.TIMEOUT_HANDLER_INTERVAL,
                String.valueOf(SynapseConstants.DEFAULT_TIMEOUT_HANDLER_INTERVAL)));

    }

    public static long getGlobalTimeoutInterval() {
        return Long.parseLong(SynapsePropertiesLoader.getPropertyValue(
                SynapseConstants.GLOBAL_TIMEOUT_INTERVAL,
                String.valueOf(SynapseConstants.DEFAULT_GLOBAL_TIMEOUT)));

    }

    /**
     * Get the global endpoint suspend duration from synapse properties.
     *
     * @return the global suspend duration in milliseconds, or -1 if not configured
     */
    public static long getGlobalSuspendDuration() {
        try {
            return Long.parseLong(SynapsePropertiesLoader.getPropertyValue(
                    SynapseConstants.GLOBAL_ENDPOINT_SUSPEND_DURATION, null));
        } catch (NumberFormatException e) {
            return Long.parseLong(SynapseConstants.DEFAULT_GLOBAL_ENDPOINT_SUSPEND_DURATION);
        }

    }

    /**
     * Get the global endpoint suspend progression factor from synapse properties.
     * The progression factor is used to calculate exponential backoff for endpoint suspension.
     * @return the global suspend progression factor (geometric series multiplier), defaults to 1.0
     */
    public static float getGlobalSuspendProgressFactor() {
        try {
            return Float.parseFloat(SynapsePropertiesLoader.getPropertyValue(
                    SynapseConstants.GLOBAL_ENDPOINT_SUSPEND_PROGRESSION_FACTOR,
                    SynapseConstants.DEFAULT_GLOBAL_ENDPOINT_SUSPEND_PROGRESSION_FACTOR));
        } catch (NumberFormatException e) {
            return Float.parseFloat(SynapseConstants.DEFAULT_GLOBAL_ENDPOINT_SUSPEND_PROGRESSION_FACTOR);
        }
    }

    /**
     * Checks whether endpoint suspend configurations should be overridden with global values.
     * When true, per-endpoint configuration settings are ignored in favor of the global configuration.
     *
     * @return true if global values should override per-endpoint settings, false otherwise (default)
     */
    public static boolean shouldEndpointSuspendConfigsOverrideWithGlobalValues() {
        return SynapsePropertiesLoader.getBooleanProperty(
                SynapseConstants.OVERRIDE_ENDPOINT_SUSPEND_CONFIG_WITH_GLOBAL_VALUES,
                false);

    }

    public static SynapseEnvironment getSynapseEnvironment(AxisConfiguration axisCfg) {
        return axisCfg != null && axisCfg.getParameter(SynapseConstants.SYNAPSE_HOME) != null ? (SynapseEnvironment)
                axisCfg.getParameterValue(SynapseConstants.SYNAPSE_CONFIG) : null;
    }

    public static OMElement stringToOM(String xml) {
        try {
            return AXIOMUtil.stringToOM(xml);  // Just wrap to add logging for any errors
        } catch (XMLStreamException e) {
            handleException("Unable to convert a string to OM Node as the string " +
                    "is malformed , String : " + xml, e);
        }
        return null;
    }

    /**
     * Construct a fresh SynapseConfiguration instance and registers the observers
     * with it as specified in the synapse.properties file. Use the initial.observers
     * property in the synapse.properties file to specify observers as a comma separated
     * list.
     *
     * @return a SynapseConfiguration instance
     */
    public static SynapseConfiguration newConfiguration() {
        SynapseConfiguration synConfig = new SynapseConfiguration();
        Properties synapseProps = SynapsePropertiesLoader.loadSynapseProperties();
        String propValue = synapseProps.getProperty("synapse.observers");
        if (propValue != null) {
            String[] observerNames = propValue.split(",");
            for (String observer : observerNames) {
                try {
                    Class clazz = SynapseConfigUtils.class.getClassLoader().
                            loadClass(observer.trim());
                    SynapseObserver o = (SynapseObserver) clazz.newInstance();
                    synConfig.registerObserver(o);
                } catch (Exception e) {
                    handleException("Error while initializing Synapse observers", e);
                }
            }
        }
        if (RuntimeStatisticCollector.isStatisticsEnabled()) {
            synConfig.registerObserver(new StatisticSynapseConfigurationObserver());
            StatisticIdentityGenerator.setSynapseConfiguration(synConfig);
        }
        return synConfig;
    }

    /**
     * Return the main sequence if one is not defined. This implementation defaults to
     * a simple sequence with a <send/>
     *
     * @param config the configuration to be updated
     */
    public static void setDefaultMainSequence(SynapseConfiguration config) {
        SequenceMediator main = new SequenceMediator();
        main.setName(SynapseConstants.MAIN_SEQUENCE_KEY);
        main.addChild(new LogMediator());
        main.addChild(new DropMediator());
        config.addSequence(SynapseConstants.MAIN_SEQUENCE_KEY, main);
        // set the aspect configuration
        AspectConfiguration configuration = new AspectConfiguration(main.getName());
        main.configure(configuration);
    }

    /**
     * Return the fault sequence if one is not defined. This implementation defaults to
     * a simple sequence :
     * <log level="full">
     *   <property name="MESSAGE" value="Executing default "fault" sequence"/>
     *   <property name="ERROR_CODE" expression="get-property('ERROR_CODE')"/>
     *   <property name="ERROR_MESSAGE" expression="get-property('ERROR_MESSAGE')"/>
     * </log>
     * <drop/>
     *
     * @param config the configuration to be updated
     */
    public static void setDefaultFaultSequence(SynapseConfiguration config) {
        SequenceMediator fault = new SequenceMediator();
        fault.setName(org.apache.synapse.SynapseConstants.FAULT_SEQUENCE_KEY);
        LogMediator log = new LogMediator();
        log.setLogLevel(LogMediator.FULL);

        MediatorProperty mp = new MediatorProperty();
        mp.setName("MESSAGE");
        mp.setValue("Executing default \"fault\" sequence");
        log.addProperty(mp);

        mp = new MediatorProperty();
        mp.setName("ERROR_CODE");
        try {
            mp.setExpression(new SynapseXPath("get-property('ERROR_CODE')"));
        } catch (JaxenException ignore) {}
        log.addProperty(mp);

        mp = new MediatorProperty();
        mp.setName("ERROR_MESSAGE");
        try {
            mp.setExpression(new SynapseXPath("get-property('ERROR_MESSAGE')"));
        } catch (JaxenException ignore) {}
        log.addProperty(mp);

        fault.addChild(log);
        fault.addChild(new DropMediator());

        // set aspect configuration
        AspectConfiguration configuration = new AspectConfiguration(fault.getName());
        fault.configure(configuration);

        config.addSequence(org.apache.synapse.SynapseConstants.FAULT_SEQUENCE_KEY, fault);
    }

    public static boolean isFailSafeEnabled(String componentName) {
        Properties synapseProps = SynapsePropertiesLoader.loadSynapseProperties();
        String failSafeStr = synapseProps.getProperty(SynapseConstants.FAIL_SAFE_MODE_STATUS);
        if (failSafeStr != null) {
            String[] failSafeComponents = failSafeStr.split(",");
            List<String> componentList = Arrays.<String>asList(failSafeComponents);
            if (componentList.indexOf(SynapseConstants.FAIL_SAFE_MODE_ALL) >= 0
                    || componentList.indexOf(componentName) >= 0) {
                return true;
            }
        } else {
            return true; // Enabled by default
        }
        return false;
    }


    public static SynapseConfiguration getSynapseConfiguration(String tenantDomain){
         return  lastRegisteredSynapseConfigurationMap.get(tenantDomain);
    }

    public static void addSynapseConfiguration(String tenantDomain , SynapseConfiguration synapseConfiguration){
        lastRegisteredSynapseConfigurationMap.put(tenantDomain,synapseConfiguration);
    }
}

