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}