/* * -----------------------------------------------------------------------\ * PerfCake *   * Copyright (C) 2010 - 2016 the original author or authors. *   * Licensed 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.perfcake.validation; import org.perfcake.message.Message; import org.perfcake.util.Utils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; /** * Utilities used for messages validation. Used by the rules for {@link org.perfcake.validation.RulesValidator}. * * @author <a href="mailto:pavel.macik@gmail.com">Pavel Macík</a> * @author <a href="mailto:lucie.fabrikova@gmail.com">Lucie Fabriková</a> */ public final class ValidatorUtil { /** * Log4j logger. */ private static final Logger log = LogManager.getLogger(ValidatorUtil.class); /** * Non-public constructor to avoid instantiation. */ private ValidatorUtil() { super(); } /** * Validation operator applied to message part and validated value. */ public enum Operator { /** * Uses {@link java.lang.String#equals(Object)}. */ EQUALS, /** * Uses {@link java.lang.String#matches(String)}. */ MATCHES, /** * Uses {@link java.lang.String#startsWith(String)}. */ STARTS_WITH, /** * Uses {@link java.lang.String#contains(CharSequence)}. */ CONTAINS, /** * Uses {@link java.lang.String#endsWith(String)} )}. */ ENDS_WITH, /** * A part exists. */ EXISTS } /** * Message part that is validated. */ public enum MessagePart { /** * Message body as a whole. */ BODY, /** * Message property. */ PROPERTY, /** * Message header. */ HEADER } /** * Message occurrence operator. */ public enum Occurrence { /** * None such message exists. */ NONE, /** * At least the specified number of messages exists. */ AT_LEAST, /** * At most the specified number of messages exists. */ AT_MOST, /** * Exactly the specified number of messages exists. */ EXACTLY } /** * Validates messages in the <code>list</code> in the interval between <code>from</code> and <code>to</code> borders included. It applies <code>operator</code> on the messages <code>part</code> and * the valid <code>value</code>. * * @param list * Message list. * @param from * Begin of the interval. * @param to * End of the interval. * @param part * Validated message part. * @param partValue * Actual value of the parametrized message part (e.g. when <code>part</code> is a body part, this value specifies which * one). If it is not provided, make it <code>null</code>. * @param operator * Operator for validation. * @param value * Valid value of validated message part. * @return A boolean value indicating, if validation of all messages passed (<code>true</code>) or not (<code>false</code>). */ public static boolean validateMessages(final List<Message> list, final int from, final int to, final ValidatorUtil.MessagePart part, final String partValue, final ValidatorUtil.Operator operator, final String value) { final int count = list.size(); if (to <= from) { if (log.isErrorEnabled()) { log.error("Arguments <from>=" + from + " and <to>=" + to + " are not valid interval borders! <from> should be less than <to>."); } return false; } else if (from < 0 || count < to) { if (log.isErrorEnabled()) { log.error("Arguments <from>=" + from + " and <to>=" + to + " are out of list's bounds. The list contains " + count + " messages."); } return false; } boolean retVal = true; for (int i = from; i < to; i++) { retVal = retVal && validateMessage(list, i, part, partValue, operator, value); } return retVal; } /** * Validates message in the <code>list</code> on specified position. It * applies <code>operator</code> on the message <code>part</code> and the * valid <code>value</code>. * * @param list * Message list. * @param number * Message position in the list. * @param part * Validated message part. * @param partName * Name of the message part. * @param operator * Operator for validation. * @param value * Valid value of validated message part. * @return A boolean value indicating, if validation of the message passed (<code>true</code>) or not (<code>false</code>). */ public static boolean validateMessage(final List<Message> list, final int number, final ValidatorUtil.MessagePart part, final String partName, final ValidatorUtil.Operator operator, final String value) { final int count = list.size(); if (number < 0 || number >= count) { if (log.isErrorEnabled()) { log.error("Invalid message position: " + number + " (the list contains " + count + " messages)"); } return false; } else { return validateMessage((list.get(number)), part, partName, operator, value); } } /** * Validates message's part using the specified operator and operand. * * @param message * The message to validate. * @param part * The part of the message to validate. * @param partName * The name of the message part. * @param operator * The operator to use. * @param value * The right hand operand of the operation. * @return True if and only if the message part meets given criteria. */ public static boolean validateMessage(final Message message, final ValidatorUtil.MessagePart part, final String partName, final ValidatorUtil.Operator operator, final String value) { return validateData(getMessagePart(message, part, partName), operator, value); } /** * Gets a particular message part. This is a utility method to use in the rules. * * @param message * The message to get the part from. * @param part * The part to obtain. * @param partName * The name of the part to obtain. * @return The part content or null if it does not exist. */ public static Object getMessagePart(final org.perfcake.message.Message message, final ValidatorUtil.MessagePart part, final String partName) { switch (part) { case BODY: return message.getPayload(); case PROPERTY: return message.getProperty(partName); case HEADER: return message.getHeader(partName); } if (log.isErrorEnabled()) { log.error("This argument values combination (" + part.name() + " + " + partName + ") is not valid for message validation. Please consult with documentation for more information."); } return null; } /** * Validates the given part of the message using specified operator and operand. * * @param str * The part to validate. * @param operator * The operator to use. * @param value * The right hand operand of the operation. * @return True if and only if the part meets given criteria. */ private static boolean validatePart(final String str, final ValidatorUtil.Operator operator, final String value) { if (str == null) { return false; } switch (operator) { case EQUALS: return str.equals(value); case MATCHES: return str.matches(value); case STARTS_WITH: return str.startsWith(value); case CONTAINS: return str.contains(value); case ENDS_WITH: return str.endsWith(value); case EXISTS: return true; // now it is evident that the part exists } if (log.isErrorEnabled()) { log.error("This argument values combination (" + operator.name() + " + " + value + ") is not valid for message validation. Please consult with documentation for more information."); } return false; } /** * Obtains string data from an object in the best possible way. * * @param data * The data to convert to a string. * @return The string representation of the data. */ private static String getStringData(final Object data) { if (data == null) { return null; } else { if (data instanceof byte[]) { try { return new String((byte[]) data, Utils.getDefaultEncoding()); } catch (final UnsupportedEncodingException e) { return new String((byte[]) data, Charset.defaultCharset()); } } else { return data.toString(); } } } /** * Validates the given data from the message using specified operator and operand. * * @param data * The part to validate. * @param operator * The operator to use. * @param value * The right hand operand of the operation. * @return True if and only if the data meet given criteria. */ private static boolean validateData(final Object data, final ValidatorUtil.Operator operator, final String value) { final String str = getStringData(data); return validatePart(str, operator, value); } /** * Validate that the <code>list</code> contains specified number of valid messages. * * @param list * Message list. * @param part * Validated message part. * @param partName * Actual value of the parametrized message part (e.g. when <code>part</code> is a body part, this value specifies which * one). If it is not provided, make it <code>null</code>. * @param operator * Operator for validation. * @param value * Valid value of validated message part. * @param occurrence * Type of message occurrence in the <code>list</code>. * @param treshold * Treshold for the <code>occurrence</code> metrics. * @return A boolean value indicating, if validation of the message passed (<code>true</code>) or not (<code>false</code>) with actual <code>occurrence</code>. */ public static boolean validateMessageOccurance(final List<Message> list, final ValidatorUtil.MessagePart part, final String partName, final ValidatorUtil.Operator operator, final String value, final Occurrence occurrence, final int treshold) { switch (occurrence) { case NONE: for (int i = 0; i < list.size(); i++) { if (validateMessage(list, i, part, partName, operator, value)) { return false; } } return true; case AT_LEAST: return countMessages(list, part, partName, operator, value) >= treshold; case AT_MOST: return countMessages(list, part, partName, operator, value) <= treshold; case EXACTLY: return countMessages(list, part, partName, operator, value) == treshold; } return false; } /** * Validate that the sublist of the <code>list</code> (between <code>from</code> and <code>to</code> (borders included)) contains specified number of valid messages. * * @param list * Message list. * @param from * Begin of the interval. * @param to * End of the interval. * @param part * Validated message part. * @param partName * Actual value of the parametrized message part (e.g. when <code>part</code> is a body part, this value specifies which * one). If it is not provided, make it <code>null</code>. * @param operator * Operator for validation. * @param value * Valid value of validated message part. * @param occurrence * Type of message occurrence in the <code>list</code>. * @param treshold * Treshold for the <code>occurrence</code> metrics. * @return A boolean value indicating, if validation of the message passed (<code>true</code>) or not (<code>false</code>) with actual <code>occurrence</code>. */ public static boolean validateMessageOccuranceOnInterval(final List<Message> list, final int from, final int to, final ValidatorUtil.MessagePart part, final String partName, final ValidatorUtil.Operator operator, final String value, final Occurrence occurrence, final int treshold) { final int count = list.size(); if (to <= from) { if (log.isErrorEnabled()) { log.error("Arguments <from>=" + from + " and <to>=" + to + " are not valid interval borders! <from> should be less than <to>."); } return false; } else if (from < 0 || count < to) { if (log.isErrorEnabled()) { log.error("Arguments <from>=" + from + " and <to>=" + to + " are out of list's bounds. The list contains " + count + " messages."); } return false; } final List<Message> subList = new ArrayList<Message>(); for (int i = from; i <= to; i++) { subList.add(list.get(i)); } return validateMessageOccurance(subList, part, partName, operator, value, occurrence, treshold); } /** * Count messages in the list that match the criteria (pass the validation).. * * @param list * Message list. * @param part * Validated message part. * @param partName * Actual value of the parametrized message part (e.g. when <code>part</code> is a body part, this value specifies which * one). If it is not provided, make it <code>null</code>. * @param operator * Operator for validation. * @param value * Valid value of validated message part. * @return Number of messages in the list that match the criteria (pass the validation). * @see #validateMessage(java.util.List, int, org.perfcake.validation.ValidatorUtil.MessagePart, java.lang.String, org.perfcake.validation.ValidatorUtil.Operator, * java.lang.String) */ private static int countMessages(final List<Message> list, final ValidatorUtil.MessagePart part, final String partName, final ValidatorUtil.Operator operator, final String value) { int messageCount = 0; for (int i = 0; i < list.size(); i++) { if (validateMessage(list, i, part, partName, operator, value)) { messageCount++; } } return messageCount; } }