# IncomingMessage.java

    /*
     * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
     *
     * WSO2 Inc. 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.emulator.http.dsl.dto.consumer;

    import io.netty.handler.codec.http.HttpMethod;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.apache.synapse.commons.emulator.http.dsl.dto.Header;
    import org.apache.synapse.commons.emulator.http.consumer.HttpRequestContext;
    import org.apache.synapse.commons.emulator.http.dsl.dto.QueryParameter;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.regex.Pattern;

    public class IncomingMessage {

        private static final Log logger = LogFactory.getLog(IncomingMessage.class.getName());

        private static IncomingMessage incoming;
        private HttpMethod method;
        private String path;
        private String body;
        private String context;
        private Pattern pathRegex;
        private Header header;
        private List<QueryParameter> queryParameters;

        private static IncomingMessage getInstance() {
            incoming = new IncomingMessage();
            return incoming;
        }

        public static IncomingMessage request() {
            return getInstance();
        }

        public IncomingMessage withMethod(HttpMethod method) {
            this.method = method;
            return this;
        }

        public IncomingMessage withPath(String path) {
            this.path = path;
            return this;
        }

        public IncomingMessage withBody(String body) {
            this.body = body;
            return this;
        }

        public IncomingMessage withHeader(String name, String value) {
            header = new Header(name, value);
            return this;
        }

        public IncomingMessage withQueryParameter(String name, String value) {
            if (queryParameters == null) {
                this.queryParameters = new ArrayList<>();
            }
            this.queryParameters.add(new QueryParameter(name, value));
            return this;
        }

        public boolean isMatch(HttpRequestContext requestContext) {
            if (isContextMatch(requestContext) && isHttpMethodMatch(requestContext) &&
                    isRequestContentMatch(requestContext) &&
                    isHeadersMatch(requestContext) && isQueryParameterMatch(requestContext)) {
                return true;
            }
            return false;
        }

        public void buildPathRegex(String context) {
            this.context = context;
            String regex = buildRegex(context, path);
            this.pathRegex = Pattern.compile(regex);
        }

        private boolean isContextMatch(HttpRequestContext requestContext) {
            this.context = extractContext(requestContext.getUri());
            return pathRegex.matcher(context).find();
        }

        private boolean isHttpMethodMatch(HttpRequestContext requestContext) {
            if (method == null) {
                return true;
            }

            if (method.equals(requestContext.getHttpMethod())) {
                return true;
            }

            logger.error("Incoming request's service method '" + requestContext.getHttpMethod()
                    + "' is not matched with the expected service method '" + method + "' for the mock service path "
                    + context.substring(0, context.length() - 1) + path);
            return false;
        }

        private boolean isRequestContentMatch(HttpRequestContext requestContext) {
            if (body == null || body.isEmpty()) {
                return true;
            }

            if (body.equalsIgnoreCase(requestContext.getRequestBody())) {
                return true;
            }

            String servicePath = context.substring(0, context.length() - 1) + path;
            logger.error("Incoming request and expected request payloads are not matched");
            logger.error("Incoming request payload for the mock service path "
                    + servicePath + " is - " + requestContext.getRequestBody());
            logger.error("Expected request payload for the mock service path " + servicePath + " is - " + body);

            return false;
        }

        private boolean isHeadersMatch(HttpRequestContext requestContext) {
            if (header == null) {
                return true;
            }

            Map<String, List<String>> headerParameters = requestContext.getHeaderParameters();
            List<String> headerValues = headerParameters.get(header.getName());

            if (headerParameters == null || headerValues == null || headerValues.isEmpty()) {
                return false;
            }

            for (String value : headerValues) {
                if (value.equalsIgnoreCase(header.getValue())) {
                    return true;
                }
            }

            logger.error("Could not find expected request header "  + header.getName() + ":" + header.getValue() +
                    " in incoming header list for the mock service path "
                    + context.substring(0, context.length() - 1) + path);
            return false;
        }

        private boolean isQueryParameterMatch(HttpRequestContext requestContext) {

            Map<String, List<String>> requestQueryParameters = requestContext.getQueryParameters();
            if ((queryParameters == null || queryParameters.isEmpty())
                    && (requestQueryParameters == null || requestQueryParameters.isEmpty())) {
                return true;
            } else if (queryParameters == null || queryParameters.isEmpty()
                    || queryParameters.size() != requestQueryParameters.size()) {
                return false;
            }

            for (QueryParameter definedQueryParams : queryParameters) {
                if (!requestQueryParameters.containsKey(definedQueryParams.getName())) {
                    return false;
                }

                List<String> queryValues = requestQueryParameters.get(definedQueryParams.getName());
                if (queryValues == null || queryValues.isEmpty()) {
                    return false;
                }
                for (String value : queryValues) {
                    if (!value.equalsIgnoreCase(definedQueryParams.getValue())) {
                        return false;
                    }
                }
            }

            return true;
        }

        private String buildRegex(String context, String path) {
            String fullPath = "";

            if ((context == null || context.isEmpty()) && (path == null || path.isEmpty())) {
                return ".*";
            }

            if (context != null && !context.isEmpty()) {
                fullPath = context;

                if (!fullPath.startsWith("/")) {
                    fullPath = "/" + fullPath;
                }

                if (!fullPath.endsWith("/")) {
                    fullPath = fullPath + "/";
                }
            } else {
                fullPath = ".*";
            }

            if (path != null && !path.isEmpty()) {
                if (fullPath.endsWith("/") && path.startsWith("/")) {
                    fullPath = fullPath + path.substring(1);
                } else if (fullPath.endsWith("/") && !path.startsWith("/")) {
                    fullPath = fullPath + path;
                } else if (!fullPath.endsWith("/") && path.startsWith("/")) {
                    fullPath = fullPath + path;
                } else {
                    fullPath = fullPath + "/" + path;
                }
            } else {
                fullPath = fullPath + ".*";
            }

            if (fullPath.endsWith("/")) {
                fullPath = fullPath.substring(0, fullPath.length() - 1);
            }

            return "^" + fullPath + "/$";
        }

        private String extractContext(String uri) {
            if (uri == null || uri.isEmpty()) {
                return null;
            }
            if (!uri.contains("?")) {
                if (!uri.endsWith("/")) {
                    uri = uri + "/";
                }
                return uri;
            }
            uri = uri.split("\\?")[0];
            if (!uri.endsWith("/")) {
                uri = uri + "/";
            }
            return uri;
        }
    }