com.mockey.plugin.RequestInspectorDefinedByJson.java Source code

Java tutorial

Introduction

Here is the source code for com.mockey.plugin.RequestInspectorDefinedByJson.java

Source

/*
 * This file is part of Mockey, a tool for testing application 
 * interactions over HTTP, with a focus on testing web services, 
 * specifically web applications that consume XML, JSON, and HTML.
 *  
 * Copyright (C) 2009-2013  Authors:
 * 
 * chad.lafontaine (chad.lafontaine AT gmail DOT com)
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */
/*
 * This file is part of Mockey, a tool for testing application 
 * interactions over HTTP, with a focus on testing web services, 
 * specifically web applications that consume XML, JSON, and HTML.
 *  
 * Copyright (C) 2009-2010  Authors:
 * 
 * chad.lafontaine (chad.lafontaine AT gmail DOT com)
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */
package com.mockey.plugin;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.mockey.model.RequestFromClient;

/**
 * Given a JSON string containing rules to validate the HTTP request, this will
 * provide the logic to inspect and validate HTTP request and build an
 * informative message for results. A few things to note: 
 * 
 * All rules per TYPE will be treated as 'AND'. For example, all key/value 
 * pairs in 'parameters' must exist. 
 * 
 * All rules between TYPEs will be treated as 'OR'. For example, all key/value
 * pair rules must be TRUE in 'parameters' OR all key/value rules must be true
 * for 'headers'. 
 * 
 * 
 * <pre>
 *     {
 *        "parameters": [
 *            {
 *                "key": "ticker",
 *                "desc": "A value must be provided with the 'ticker' parameter, and it must contain the letter 'g'. Providing 'GOOG' is valid, but 'FB' will flag an error.",
 *                "value_rule_arg": "g",
 *                "value_rule_type": "string_required"
 *            },
 *            {
 *                "key": "date",
 *                "desc": "Optional date value, but if provided, must satisfy mm/DD/yyyy format.",
 *                "value_rule_arg": "^(((0[1-9]|[12]\\d|3[01])\\/(0[13578]|1[02])\\/((19|[2-9]\\d)\\d{2}))|((0[1-9]|[12]\\d|30)\\/(0[13456789]|1[012])\\/((19|[2-9]\\d)\\d{2}))|((0[1-9]|1\\d|2[0-8])\\/02\\/((19|[2-9]\\d)\\d{2}))|(29\\/02\\/((1[6-9]|[2-9]\\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$",
 *                "value_rule_type": "regex_optional"
 *            }
 *        ],
 *        "headers": [
 *            {
 *                "key": "page_id",
 *                "desc": "A page_id value MUST be provided, any non-empty string value.",
 *                "value_rule_arg": "",
 *                "value_rule_type": "string_required"
 *            }
 *        ],
 *      "body": [
 *            {
 *                "desc": "The text 'username' is required to be present in the POST body.",
 *                "value_rule_arg": "username",
 *                "value_rule_type": "string_required"
 *            }
 *        ],
 *      "url": [
 *            {
 *                "desc": "The value '123' is required to be present in the RESTful URL.",
 *                "value_rule_arg": "\\b123\\b",
 *                "value_rule_type": "regex_required"
 *            }
 *        ]
 *    }
 * </pre>
 * 
 * @author chadlafontaine
 * 
 */
public class RequestInspectorDefinedByJson implements IRequestInspector {

    private JSONObject json = null;
    private Logger logger = Logger.getLogger(RequestInspectorDefinedByJson.class);
    private Map<RequestRuleType, List<String>> errorMapByKey = new HashMap<RequestRuleType, List<String>>();
    private Map<RequestRuleType, Boolean> rulesDefinedForType = new HashMap<RequestRuleType, Boolean>();
    private int totalRuleCount = 0;
    private int validRuleCount = 0;

    /**
     * 
     * @param json
     *            - request inspection rules
     * @throws JSONException
     */
    public RequestInspectorDefinedByJson(String json) throws JSONException {

        this.json = new JSONObject(json);

    }

    /**
     * 
     * @return the number of rules processed post analysis.
     */
    public int getTotalRuleCount() {
        return this.totalRuleCount;
    }

    /**
     * 
     * @return the number of rules that had a positive outcome. 
     */
    public int getValidRuleCount() {
        return this.validRuleCount;
    }

    /**
     * Will apply request inspection rules as defined in JSON, only looking at
     * parameters and headers, not Body.
     * 
     * @param request
     *            - HTTP request to analyze.
     */
    public void analyze(RequestFromClient request) {

        // Since we apply the same evaluation logic to parameters and headers,
        // we'll move the key-value pairs into a Map, and process the rules
        // accordingly.

        // *************
        // Parameters
        // *************
        analyze(RequestRuleType.RULE_TYPE_FOR_PARAMETERS, request.getParameters());

        // *************
        // Headers
        // *************
        analyze(RequestRuleType.RULE_TYPE_FOR_HEADERS, request.getHeaderInfoAsMap());

        // *************
        // RULE_FOR_BODY ??
        // *************
        Map<String, String[]> postBodyMap = new HashMap<String, String[]>();
        if (request.hasPostBody()) {
            postBodyMap.put(RequestRuleType.RULE_TYPE_FOR_BODY.toString(), new String[] { request.getBodyInfo() });
        }
        analyze(RequestRuleType.RULE_TYPE_FOR_BODY, postBodyMap);

        // *************
        // Url
        // *************
        Map<String, String[]> urlMap = new HashMap<String, String[]>();
        urlMap.put(RequestRuleType.RULE_TYPE_FOR_URL.toString(), new String[] { request.getRequestURL() });
        analyze(RequestRuleType.RULE_TYPE_FOR_URL, urlMap);

    }

    /**
     * Based on type, method will extra validation rules and evaluate the
     * keyValues mapping.
     * 
     * @param type
     *            - Valid values are RULE_FOR_HEADERS or RULE_FOR_PARAMETERS
     * @param keyValues
     *            - An array of possible values associated to a key
     * @see #RULE_FOR_HEADERS
     * @see #RULE_FOR_PARAMETERS
     */
    private void analyze(RequestRuleType ruleType, Map<String, String[]> keyValues) {

        // Validate parameters.
        try {

            // FOR PARAMETERs and HEADERs
            JSONArray parameterArray = this.json.getJSONArray(ruleType.toString());

            for (int i = 0; i < parameterArray.length(); i++) {
                JSONObject jsonRule = parameterArray.getJSONObject(i);

                this.totalRuleCount++;
                try {
                    RequestRule requestRule = new RequestRule(jsonRule, ruleType);
                    this.rulesDefinedForType.put(ruleType, new Boolean(true));
                    if (RequestRuleType.RULE_TYPE_FOR_BODY.equals(ruleType)) {
                        String[] values = keyValues.get(RequestRuleType.RULE_TYPE_FOR_BODY.toString());
                        if (requestRule.evaluate(values)) {
                            addErrorMessage(ruleType, requestRule);
                        } else {
                            this.validRuleCount++;
                        }
                    } else if (RequestRuleType.RULE_TYPE_FOR_URL.equals(ruleType)) {
                        String[] values = keyValues.get(RequestRuleType.RULE_TYPE_FOR_URL.toString());
                        if (requestRule.evaluate(values)) {
                            addErrorMessage(ruleType, requestRule);
                        } else {
                            this.validRuleCount++;
                        }
                    } else {
                        // For HEADERS and PARAMETERS
                        if (requestRule.getKey() != null && requestRule.getKey().contains("*")) {
                            // We treat this as a wild-card.
                            Iterator<String> allKeys = keyValues.keySet().iterator();
                            List<String> allValues = new ArrayList<String>();
                            while (allKeys.hasNext()) {
                                String key = allKeys.next();
                                String[] vals = keyValues.get(key);
                                for (String v : vals) {
                                    allValues.add(v);
                                }
                            }
                            // Get ALL values from all parameters, and evaluate. 
                            if (requestRule.evaluate(allValues.toArray(new String[allValues.size()]))) {
                                addErrorMessage(ruleType, requestRule);
                            } else {
                                this.validRuleCount++;
                            }
                        } else {
                            // We have non-null, and non-empty keys.
                            // Apply specific rules.
                            // Keys in RULES and INCOMING maps should 
                            // be case insensitive!
                            Iterator<String> allKeys = keyValues.keySet().iterator();
                            while (allKeys.hasNext()) {
                                String key = allKeys.next();
                                if (key.equalsIgnoreCase(requestRule.getKey())) {
                                    String[] values = keyValues.get(key);
                                    if (requestRule.evaluate(values)) {
                                        addErrorMessage(ruleType, requestRule);
                                    } else {
                                        this.validRuleCount++;
                                    }
                                }

                            }
                        }
                    }

                } catch (RequestRuleException e) {
                    addErrorMessage(ruleType, "Invalid JSON rule setup. " + e.getMessage());
                }

            }

        } catch (JSONException e) {

            // Not necessarily an error. Could be missing
            logger.debug(
                    "Request Inspection JSON rules does not have rule defined for '" + ruleType.toString() + "'");
        }

    }

    /**
     * 
     * @param type
     * @param error
     */
    private void addErrorMessage(RequestRuleType type, RequestRule requestRule) {
        List<String> errorListByKeyType = this.errorMapByKey.get(type);
        if (errorListByKeyType == null) {
            errorListByKeyType = new ArrayList<String>();
        }

        // Build
        StringBuilder sb = new StringBuilder();
        sb.append("ISSUE: Rule of type '" + type + "'. ");
        if (!RequestRuleType.RULE_TYPE_FOR_BODY.equals(type) && !RequestRuleType.RULE_TYPE_FOR_URL.equals(type)) {
            sb.append(" Belonging to key name of '" + requestRule.getKey() + "'. ");
        }
        for (String issue : requestRule.getIssues()) {
            sb.append(issue + " ");
        }
        errorListByKeyType.add(sb.toString() + " RULE DESC: " + requestRule.getDesc());
        this.errorMapByKey.put(type, errorListByKeyType);
    }

    /**
     * 
     * @param type
     * @param error
     */
    private void addErrorMessage(RequestRuleType type, String msg) {
        List<String> errorListByKeyType = this.errorMapByKey.get(type);
        if (errorListByKeyType == null) {
            errorListByKeyType = new ArrayList<String>();
        }
        errorListByKeyType.add(msg);
        this.errorMapByKey.put(type, errorListByKeyType);
    }

    //   /**
    //    * Method should be called post analysis.
    //    * 
    //    * @return true if one or more errors exist regardless of type.
    //    */
    //   public boolean hasErrors_() {
    //      if (this.errorMapByKey.isEmpty()) {
    //         return false;
    //      } else {
    //         return true;
    //      }
    //   }

    /**
     * 
     * @return true if ALL rules pass for PARAMETERS or BODY or HEADERS or URL
     */
    public boolean hasAnySuccessForAtLeastOneRuleType() {
        boolean success = false;

        Iterator<RequestRuleType> iter = rulesDefinedForType.keySet().iterator();
        while (iter.hasNext()) {
            RequestRuleType type = iter.next();
            List<String> errors = this.errorMapByKey.get(type);
            if (errors == null || errors.size() == 0) {
                success = true;
                break;
            }
        }
        return success;
    }

    /**
     * If errors exists, this method will build 1 long string representation of
     * all broken rules, inserting a counter i.e. 1, 2, 3, etc. in front of each
     * message.
     * 
     * @return the result of the validate rules, can be an empty string, but
     *         never null.
     */
    public String getPostAnalyzeResultMessage() {
        StringBuffer sb = new StringBuffer();
        int i = 1;
        for (RequestRuleType key : this.errorMapByKey.keySet()) {
            for (String value : this.errorMapByKey.get(key)) {
                sb.append(i++ + ") " + value + " \n");
            }
        }
        return sb.toString();
    }

    @Override
    public boolean isGlobal() {
        // TODO Auto-generated method stub
        return false;
    }

    public static void main_(String[] args) {
        // String valueRuleArg =
        // "^((1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])/(?:[0-9]{2})?[0-9]{2})?$";
        // String value = "10/23/1972";
        // try {
        //
        // Pattern pattern = Pattern.compile(valueRuleArg);
        // Matcher matcher = pattern.matcher(value);
        // if (!matcher.find()) {
        // System.out.println("No match");
        // } else {
        // System.out.println("yes, Match");
        // }
        // } catch (Exception e) {
        // e.printStackTrace();
        // }
        //Map<String, String[]> test = new HashMap<String, String[]>();

    }
}