/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.sa.api.utils; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.List; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import org.apache.http.conn.util.InetAddressUtils; import org.joda.time.DateTime; import org.joda.time.DateTimeConstants; import org.joda.time.DateTimeZone; import com.emc.sa.descriptor.ServiceField; import com.emc.storageos.db.client.model.uimodels.ExecutionWindow; import com.emc.storageos.db.client.model.uimodels.ExecutionWindowLengthType; import com.emc.storageos.svcs.errorhandling.resources.APIException; import com.emc.vipr.model.catalog.ExecutionWindowCommonParam; import com.google.common.collect.Lists; public class ValidationUtils { private static final Pattern EMAIL_PATTERN = Pattern .compile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?"); private static final Pattern NAME_PART_PATTERN = Pattern.compile("[A-Za-z][A-Za-z0-9_\\-]*"); /** The regular expression for numbers. */ private static final String INTEGER_REGEX = "[\\-]?\\b\\d+\\b"; private static final String NUMBER_REGEX = "[-+]?[0-9]*\\.?[0-9]+"; public static final String DAILY = "DAILY"; public static final String MONTHLY = "MONTHLY"; public static final String WEEKLY = "WEEKLY"; public static final String DAYS = "DAYS"; public static final String HOURS = "HOURS"; public static final String MINUTES = "MINUTES"; private static final int MAX_DAYS = 1; private static final int MAX_HOURS = 23; private static final int MIN_MINUTES = 30; private static final int MAX_MINUTES = (23 * 60) + 59; public static final int MAX_EVENTS = 25; public static final int TIME_RANGE_PADDING_IN_HOURS = 2; public static final long MILLIS_IN_SECOND = 1000; public static final long SECONDS_IN_HOUR = 3600; public static final long SECONDS_IN_DAY = 3600 * 24; public static final long SECONDS_IN_MIN = 60; public static boolean isValidEmail(String value) { if (StringUtils.isBlank(value)) { return true; } return EMAIL_PATTERN.matcher(value.toString()).matches(); } public static boolean isValidHostNameOrIp(String value) { if (isValidIp(value)) { return true; } if (isValidHostName(value)) { return true; } return false; } public static boolean isValidIp(String value) { return validateInetAddress(value); } public static boolean validateInetAddress(final String address) { try { InetAddress.getByName(address); } catch (UnknownHostException e) { return false; } return true; } public static boolean isInetAddressFormat(String address) { return InetAddressUtils.isIPv4Address(address) || InetAddressUtils.isIPv6Address(address); } public static boolean isValidHostName(String value) { try { String[] parts = value.split("\\."); for (int i = 0; i < parts.length; i++) { if (!NAME_PART_PATTERN.matcher(parts[i]).matches()) { return false; } } return true; } catch (Exception e) { return false; } } public static boolean hasValidPort(String endpoint) { try { if (endpoint != null && !endpoint.isEmpty()) { if (endpoint.contains("]:")) { String port = StringUtils.substringAfter(endpoint, "]:"); if (!StringUtils.isNumeric(port)) { return false; } } else if (endpoint.contains(":") && StringUtils.countMatches(endpoint, ":") == 1) { String port = StringUtils.substringAfter(endpoint, ":"); if (!StringUtils.isNumeric(port)) { return false; } } } return true; } catch (Exception e) { return false; } } public static String trimPortFromEndpoint(String endpoint) { if (endpoint != null && !endpoint.isEmpty()) { if (endpoint.contains("]:")) { endpoint = StringUtils.substringBefore(endpoint, "]:"); endpoint = StringUtils.substringAfter(endpoint, "["); } else if (endpoint.contains(":") && StringUtils.countMatches(endpoint, ":") == 1) { endpoint = StringUtils.substringBefore(endpoint, ":"); } } return endpoint; } public static void validateField(Integer storageSize, ServiceField field, String value) { if (field.isRequired()) { validateRequiredField(field, value); } if (ServiceField.TYPE_NUMBER.equals(field.getType())) { validateIntegerField(field, value); } else if (ServiceField.TYPE_TEXT.equals(field.getType())) { validateTextField(field, value); } else if (ServiceField.TYPE_STORAGE_SIZE.equals(field.getType())) { validateStorageSizeField(storageSize, field, value); } else if (ServiceField.TYPE_BOOLEAN.equals(field.getType())) { validateBooleanField(field, value); } } private static void validateRequiredField(ServiceField field, String value) { if (value == null || value.isEmpty()) { throw APIException.badRequests.serviceFieldRequired(field.getName()); } } /** * Validates a number field. * * @param service * the catalog service. * @param field * the field to validate. * @param value * the field value. */ private static void validateNumberField(ServiceField field, String value) { if (StringUtils.isNotBlank(value)) { validateNumber(field.getName(), value); if (new Integer(value) < field.getValidation().getMin()) { throw APIException.badRequests.serviceFieldBelowMin(field.getName()); } if (new Integer(value) > field.getValidation().getMax()) { throw APIException.badRequests.serviceFieldAboveMax(field.getName()); } } } /** * Validates an integer field. * * @param service * the catalog service. * @param field * the field to validate. * @param value * the field value. */ private static void validateIntegerField(ServiceField field, String value) { if (StringUtils.isNotBlank(value)) { validateInteger(field.getName(), value); if (field.getValidation().getMin() != null && new Integer(value) < field.getValidation().getMin()) { throw APIException.badRequests.serviceFieldBelowMin(field.getName()); } if (field.getValidation().getMax() != null && new Integer(value) > field.getValidation().getMax()) { throw APIException.badRequests.serviceFieldAboveMax(field.getName()); } } } /** * Validates a text field. * * @param service * the catalog service. * @param field * the field to validate. * @param value * the field value. */ private static void validateTextField(ServiceField field, String value) { if (StringUtils.isNotBlank(value)) { try { validateRegex(field.getName(), value, field.getValidation().getRegEx()); } catch (Exception e) { throw APIException.badRequests.serviceFieldNonText(field.getName()); } if (field.getValidation().getMin() != null && value.length() < field.getValidation().getMin()) { throw APIException.badRequests.serviceFieldBelowMinLength(field.getName()); } if (field.getValidation().getMax() != null && value.length() > field.getValidation().getMax()) { throw APIException.badRequests.serviceFieldBeyondMaxLength(field.getName()); } } } /** * Validates a storage size field. * * @param service * the catalog service. * @param field * the field to validate. * @param value * the field value. */ private static void validateStorageSizeField(Integer storageSize, ServiceField field, String value) { validateNumber(field.getName(), value); int min = Math.max(0, field.getValidation().getMin()); if (Float.valueOf(value) < min) { throw APIException.badRequests.serviceFieldBelowMin(field.getName()); } boolean hasMaxSize = (storageSize != null) && (storageSize >= 1); if (hasMaxSize) { if (Float.valueOf(value) > storageSize) { throw APIException.badRequests.serviceFieldAboveMax(field.getName()); } } } private static void validateBooleanField(ServiceField field, String value) { if (StringUtils.isNotBlank(value)) { try { validateRegex(field.getName(), value, field.getValidation().getRegEx()); } catch (Exception e) { throw APIException.badRequests.serviceFieldNonBoolean(field.getName()); } } } /** * Validates a value as a number. * * @param fieldName * the name of the field. * @param value * the value to validate. */ private static void validateNumber(String fieldName, String value) { try { validateRegex(fieldName, value, NUMBER_REGEX); } catch (Exception e) { throw APIException.badRequests.serviceFieldNonNumeric(fieldName); } } /** * Validates a value as a float. * * @param fieldName * the name of the field. * @param value * the value to validate. */ private static void validateInteger(String fieldName, String value) { try { validateRegex(fieldName, value, INTEGER_REGEX); } catch (Exception e) { throw APIException.badRequests.serviceFieldNonInteger(fieldName); } } /** * Validates a field using a regular expression. * * @param fieldName * the name of the field. * @param value * the value to validate. * @param pattern * the regular expression pattern. * @param errorMessage * the error message to display if the value does not match, if blank a default message is used. */ private static void validateRegex(String fieldName, String value, String pattern) throws Exception { if (!matches(pattern, value)) { throw new Exception(); } } private static boolean matches(String pattern, String value) { // Null value should be considered matching. If it is required, should be picked up by required field if (value == null) { return true; } if (StringUtils.isNotBlank(pattern)) { return Pattern.matches(pattern, value); } else { return true; } } public static void validateExecutionWindow(ExecutionWindowCommonParam input) { if (input.getExecutionWindowLength() != null) { if (MINUTES.equals(input.getExecutionWindowLengthType())) { if (input.getExecutionWindowLength() < MIN_MINUTES) { throw APIException.badRequests.executionWindowLengthBelowMin( input.getExecutionWindowLength().toString()); } if (input.getExecutionWindowLength() > MAX_MINUTES) { throw APIException.badRequests.executionWindowLengthAboveMax( input.getExecutionWindowLength().toString()); } } else if (HOURS.equals(input.getExecutionWindowLengthType())) { if (input.getExecutionWindowLength() > MAX_HOURS) { throw APIException.badRequests.executionWindowLengthAboveMax( input.getExecutionWindowLength().toString()); } } else if (DAYS.equals(input.getExecutionWindowLengthType())) { if (input.getExecutionWindowLength() > MAX_DAYS) { throw APIException.badRequests.executionWindowLengthAboveMax( input.getExecutionWindowLength().toString()); } } } } public static boolean isOverlapping(ExecutionWindow newExecutionWindow, List<ExecutionWindow> existingWindows) { DateTimeZone tz = DateTimeZone.UTC; DateTime startOfWeek = getStartOfWeek(tz); DateTime endDateTime = startOfWeek.plusDays(31); List<Event> events = asEvents(existingWindows, startOfWeek, endDateTime, tz); List<Event> newEvents = asEvents(newExecutionWindow, startOfWeek, endDateTime, tz, 365); for (Event event : events) { for (Event newEvent : newEvents) { if (isOverlapping(event, newEvent)) { return true; } } } return false; } public static DateTime getStartOfWeek(DateTimeZone tz) { DateTime startOfWeek = new DateTime(tz); startOfWeek = startOfWeek.withDayOfWeek(DateTimeConstants.MONDAY); startOfWeek = startOfWeek.withMillisOfDay(0); startOfWeek = startOfWeek.withZone(DateTimeZone.UTC); return startOfWeek; } private static boolean isOverlapping(Event left, Event right) { return (StringUtils.equals(left.id, right.id) == false) && (left.startMillis < right.endMillis) && (right.startMillis < left.endMillis); } public static List<Event> asEvents(List<ExecutionWindow> executionWindows, DateTime start, DateTime end, DateTimeZone tz) { return asEvents(executionWindows, start, end, tz, MAX_EVENTS); } public static List<Event> asEvents(List<ExecutionWindow> executionWindows, DateTime start, DateTime end, DateTimeZone tz, long maxNumberOfEvents) { List<Event> events = Lists.newArrayList(); if (executionWindows != null) { for (ExecutionWindow executionWindow : executionWindows) { events.addAll(asEvents(executionWindow, start, end, tz, maxNumberOfEvents)); } } return events; } public static long toMillis(long duration, String unit) { ExecutionWindowLengthType lengthType = ExecutionWindowLengthType.valueOf(unit); if (ExecutionWindowLengthType.DAYS.equals(lengthType)) { return duration * SECONDS_IN_DAY * MILLIS_IN_SECOND; } else if (ExecutionWindowLengthType.HOURS.equals(lengthType)) { return duration * SECONDS_IN_HOUR * MILLIS_IN_SECOND; } return duration * SECONDS_IN_MIN * MILLIS_IN_SECOND; } private static List<Event> asEvents(ExecutionWindow executionWindow, DateTime start, DateTime end, DateTimeZone tz, long maxNumberOfEvents) { long lengthInMillis = toMillis(executionWindow.getExecutionWindowLength(), executionWindow.getExecutionWindowLengthType()); List<Event> events = Lists.newArrayList(); DateTime indexDate = start.minusHours(TIME_RANGE_PADDING_IN_HOURS); DateTime paddedEnd = end.plusHours(TIME_RANGE_PADDING_IN_HOURS); while (indexDate.isBefore(paddedEnd.getMillis())) { // Potentially some CRON expressions could fire a LOT. Max out // after a certain number of events per job if (events.size() > maxNumberOfEvents) { break; } if (isScheduled(indexDate, executionWindow)) { int hourOfDay = executionWindow.getHourOfDayInUTC(); int minute = 0; if (executionWindow.getMinuteOfHourInUTC() != null) { minute = executionWindow.getMinuteOfHourInUTC(); } DateTime nextDate = indexDate.withHourOfDay(hourOfDay); nextDate = nextDate.withMinuteOfHour(minute); nextDate = nextDate.withSecondOfMinute(0); nextDate = nextDate.withMillisOfSecond(0); DateTime nextEndDate = nextDate.plusMillis((int) lengthInMillis); String id = null; if (executionWindow.getId() != null) { id = executionWindow.getId().toString(); } events.add(new Event(id, executionWindow.getLabel(), nextDate, nextEndDate, tz)); } indexDate = indexDate.plusDays(1); } return events; } private static boolean isScheduled(DateTime indexDate, ExecutionWindow executionWindow) { if (indexDate != null && executionWindow != null) { if (DAILY.equals(executionWindow.getExecutionWindowType())) { return true; } else if (WEEKLY.equals(executionWindow.getExecutionWindowType())) { Integer dayOfWeek = executionWindow.getDayOfWeek(); if (dayOfWeek != null && indexDate.getDayOfWeek() == dayOfWeek) { return true; } } else if (MONTHLY.equals(executionWindow.getExecutionWindowType())) { Integer dayOfMonth = executionWindow.getDayOfMonth(); Boolean lastDayOfMonth = executionWindow.getLastDayOfMonth(); if (lastDayOfMonth != null && lastDayOfMonth.booleanValue() == true) { if (isLastDayOfMonth(indexDate.getDayOfMonth(), indexDate)) { return true; } } else if (dayOfMonth != null && indexDate.getDayOfMonth() == dayOfMonth) { return true; } else if (isLastDayOfMonth(dayOfMonth, indexDate)) { return true; } } } return false; } private static boolean isLastDayOfMonth(Integer dayOfMonth, DateTime date) { if (dayOfMonth != null && date != null) { // Is last day of month? if (date.dayOfMonth().get() == date.dayOfMonth().getMaximumValue()) { // Is value store for day of month greater than or equal to current day if (dayOfMonth >= date.dayOfMonth().getMaximumValue()) { return true; } } } return false; } }