001/** 002 * Copyright 2005-2014 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.datadictionary.validation.constraint; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.core.api.CoreConstants; 020import org.kuali.rice.core.api.config.property.ConfigContext; 021import org.kuali.rice.krad.datadictionary.parse.BeanTag; 022import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; 023import org.kuali.rice.krad.datadictionary.parse.BeanTags; 024 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.Collections; 028import java.util.List; 029 030/** 031 * DatePatternConstraint constrains a field to only allow dates which are part of the formats 032 * defined in the system. Constraining a field all these formats is often not appropriate for 033 * fields, and you may want to constrain the input to a subset of the allowed formats in the system. 034 * This can be done by setting the allowed formats to this subset (see BasicDatePatternConstraint 035 * bean for example) 036 * 037 * @author Kuali Rice Team (rice.collab@kuali.org) 038 */ 039@BeanTags({@BeanTag(name = "datePatternConstraint", parent = "DatePatternConstraint"), 040 @BeanTag(name = "basicDatePatternConstraint", parent = "BasicDatePatternConstraint")}) 041public class DatePatternConstraint extends ValidDataPatternConstraint { 042 043 private List<String> allowedFormats; 044 045 /** 046 * Returns a regex representing all the allowed formats in the system. If allowedFormats is 047 * supplied, returns a regex representing only those formats. 048 * 049 * @see org.kuali.rice.krad.datadictionary.validation.constraint.ValidDataPatternConstraint#getRegexString() 050 */ 051 @Override 052 protected String getRegexString() { 053 List<String> dateFormatParams = loadFormats(CoreConstants.STRING_TO_DATE_FORMATS, CoreConstants.STRING_TO_DATE_FORMATS_DEFAULT); 054 if (allowedFormats != null && !allowedFormats.isEmpty()) { 055 if (dateFormatParams.containsAll(allowedFormats)) { 056 dateFormatParams = allowedFormats; 057 } else { 058 //throw new Exception("Some of these formats do not exist in configured allowed date formats: " + allowedFormats.toString()); 059 } 060 } 061 062 String regex = ""; 063 int i = 0; 064 for (String format : dateFormatParams) { 065 if (i == 0) { 066 regex = "(^" + convertDateFormatToRegex(format.trim()) + "$)"; 067 } else { 068 regex = regex + "|(^" + convertDateFormatToRegex(format.trim()) + "$)"; 069 } 070 i++; 071 } 072 return regex; 073 } 074 075 /** 076 * Converts a date format supplied to the appropriate date format regex equivalent 077 * 078 * @param format date format 079 * @return regex for validating the date format 080 */ 081 private String convertDateFormatToRegex(String format) { 082 format = format.replace("\\", "\\\\").replace(".", "\\.").replace("-", "\\-").replace("+", "\\+").replace("(", 083 "\\(").replace(")", "\\)").replace("[", "\\[").replace("]", "\\]").replace("|", "\\|").replace("yyyy", 084 "((19|2[0-9])[0-9]{2})").replace("yy", "([0-9]{2})").replaceAll("M{4,}", 085 "([@]+)") //"(January|February|March|April|May|June|July|August|September|October|November|December)") 086 .replace("MMM", "([@]{3})") //"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)") 087 .replace("MM", "(0[1-9]|1[012])").replace("M", "(0?[1-9]|1[012])").replace("dd", 088 "(0[1-9]|[12][0-9]|3[01])").replace("d", "(0?[1-9]|[12][0-9]|3[01])").replace("hh", 089 "(1[0-2]|0[1-9])").replace("h", "(1[0-2]|0?[1-9])").replace("HH", "(2[0-3]|1[0-9]|0[0-9])") 090 .replace("H", "(2[0-3]|1[0-9]|0?[0-9])").replace("kk", "(2[0-4]|1[0-9]|0[1-9])").replace("k", 091 "(2[0-4]|1[0-9]|0?[1-9])").replace("KK", "(1[01]|0[0-9])").replace("K", "(1[01]|0?[0-9])") 092 .replace("mm", "([0-5][0-9])").replace("m", "([1-5][0-9]|0?[0-9])").replace("ss", "([0-5][0-9])") 093 .replace("s", "([1-5][0-9]|0?[0-9])").replace("SSS", "([0-9][0-9][0-9])").replace("SS", 094 "([0-9][0-9][0-9]?)").replace("S", "([0-9][0-9]?[0-9]?)").replaceAll("E{4,}", 095 "([@]+)")//"(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)") 096 .replaceAll("E{1,3}", "([@]{3})")//"(Mon|Tue|Wed|Thu|Fri|Sat|Sun)") 097 .replace("DDD", "(3[0-6][0-5]|[1-2][0-9][0-9]|0[0-9][1-9])").replace("DD", 098 "(3[0-6][0-5]|[1-2][0-9][0-9]|0?[0-9][1-9])").replace("D", 099 "(3[0-6][0-5]|[1-2][0-9][0-9]|0?[0-9]?[1-9])").replace("F", "([1-5])").replace("ww", 100 "(5[0-3]|[1-4][0-9]|0[1-9])").replace("w", "(5[0-3]|[1-4][0-9]|[1-9])").replace("W", "([1-5])") 101 .replaceAll("z{4,}", "([@]+)").replaceAll("z{1,3}", "([@]{1,4})").replaceAll("a{1,}", "([aApP][mM])") 102 .replaceAll("G{1,}", "([aA][dD]|[bB][cC])").replace(" ", "\\s").replace("@", "a-zA-Z"); 103 104 return format; 105 106 } 107 108 /** 109 * Loads a list of date formats from the config, using a default for fallback. 110 * 111 * @param property the config property 112 * @param defaultValue the default value 113 * 114 * @return the config or default value 115 */ 116 private List<String> loadFormats(String property, String defaultValue) { 117 return parseConfigValues(loadFormat(property, defaultValue)); 118 } 119 120 /** 121 * Loads a particular date format from the config, using a default for fallback. 122 * 123 * @param property the config property 124 * @param defaultValue the default value 125 * 126 * @return the config value or default value 127 */ 128 private String loadFormat(String property, String defaultValue) { 129 String format = ConfigContext.getCurrentContextConfig().getProperty(property); 130 131 if (StringUtils.isNotBlank(format)) { 132 return format; 133 } 134 135 return defaultValue; 136 } 137 138 /** 139 * The dateTime config vars are ';' seperated. 140 * 141 * @param configValue configuration value 142 * @return list of tokens in the config value, split on ';' 143 */ 144 private List<String> parseConfigValues(String configValue) { 145 if (configValue == null || "".equals(configValue)) { 146 return Collections.emptyList(); 147 } 148 return Arrays.asList(configValue.split(";")); 149 } 150 151 /** 152 * @return the allowedFormats 153 */ 154 @BeanTagAttribute(name = "allowedFormats", type = BeanTagAttribute.AttributeType.LISTVALUE) 155 public List<String> getAllowedFormats() { 156 return this.allowedFormats; 157 } 158 159 /** 160 * Sets the alloweFormats for this constraint, this must be a subset of the system configured 161 * formats for a date - this list should be used for most fields where you are expecting a user 162 * to enter a date in a specific format 163 * 164 * @param allowedFormats the allowedFormats to set 165 */ 166 public void setAllowedFormats(List<String> allowedFormats) { 167 this.allowedFormats = allowedFormats; 168 } 169 170 /** 171 * This overridden method ... 172 * 173 * @see org.kuali.rice.krad.datadictionary.validation.constraint.ValidDataPatternConstraint#getValidationMessageParams() 174 */ 175 @Override 176 public List<String> getValidationMessageParams() { 177 if (validationMessageParams == null) { 178 validationMessageParams = new ArrayList<String>(); 179 if (allowedFormats != null && !allowedFormats.isEmpty()) { 180 validationMessageParams.add(StringUtils.join(allowedFormats, ", ")); 181 } else { 182 List<String> dateFormatParams = loadFormats(CoreConstants.STRING_TO_DATE_FORMATS, CoreConstants.STRING_TO_DATE_FORMATS_DEFAULT); 183 validationMessageParams.add(StringUtils.join(dateFormatParams, ", ")); 184 } 185 } 186 return validationMessageParams; 187 } 188 189}