001/*
002 * (C) Copyright 2014-2018 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
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 *
016 * Contributors:
017 *     Nicolas Chapurlat <nchapurlat@nuxeo.com>
018 */
019
020package org.nuxeo.ecm.core.schema.types.constraints;
021
022import java.util.ArrayList;
023import java.util.Calendar;
024import java.util.Date;
025import java.util.List;
026import java.util.Locale;
027import java.util.MissingResourceException;
028
029import org.apache.commons.lang3.StringUtils;
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.nuxeo.common.utils.i18n.I18NUtils;
033
034/**
035 * <p>
036 * This constraint ensures some date representation is in an enumeration.
037 * </p>
038 * <p>
039 * This constraint can validate any {@link Date} or {@link Calendar}. This constraint also support {@link Number} types
040 * whose long value is recognised as number of milliseconds since January 1, 1970, 00:00:00 GMT.
041 * </p>
042 *
043 * @since 7.1
044 */
045public abstract class AbstractConstraint implements Constraint {
046
047    private static final long serialVersionUID = 1L;
048
049    private static final Log log = LogFactory.getLog(AbstractConstraint.class);
050
051    private static final String HARD_CODED_CONTRAINT_ERROR_MESSAGE = "The constraint '%s' failed for value %s";
052
053    @Override
054    public final String toString() {
055        return getDescription().toString();
056    }
057
058    @Override
059    public String getMessageKey() {
060        // test whether there's a constraint specific translation
061        // the expected key is label.schema.constraint.violation.[TheConstraintName]
062        // if there's none, replies to a generic message
063        // the expected key is label.schema.constraint.violation
064        // if there's none, replies to a hard coded message
065        List<String> pathTokens = new ArrayList<>();
066        pathTokens.add(MESSAGES_KEY);
067        pathTokens.add(getDescription().getName());
068        return StringUtils.join(pathTokens, '.');
069    }
070
071    @Override
072    public String getErrorMessage(Object invalidValue, Locale locale) {
073        String keyConstraint = getMessageKey();
074        String computedInvalidValue = "null";
075        if (invalidValue != null) {
076            String invalidValueString = invalidValue.toString();
077            if (invalidValueString.length() > 20) {
078                computedInvalidValue = invalidValueString.substring(0, 15) + "...";
079            } else {
080                computedInvalidValue = invalidValueString;
081            }
082        }
083        Object[] params = new Object[] { computedInvalidValue };
084        Locale computedLocale = locale != null ? locale : Constraint.MESSAGES_DEFAULT_LANG;
085        String message = getMessageString(MESSAGES_BUNDLE, keyConstraint, params, computedLocale);
086
087        if (message != null && !message.trim().isEmpty() && !keyConstraint.equals(message)) {
088            // use a constraint specific message if there's one
089            return message;
090        } else {
091            params = new Object[] { computedInvalidValue, toString() };
092            message = getMessageString(MESSAGES_BUNDLE, MESSAGES_KEY, params, computedLocale);
093            if (message != null && !message.trim().isEmpty() && !keyConstraint.equals(message)) {
094                // use a generic message if there's one
095                return message;
096            } else {
097                // use a hard coded message
098                return String.format(HARD_CODED_CONTRAINT_ERROR_MESSAGE, toString(), computedInvalidValue);
099            }
100        }
101    }
102
103    /**
104     * Try to get the message from the given message bundle. If the bundle is not found or the key is not found, return
105     * null.
106     *
107     * @since 7.2
108     */
109    public static String getMessageString(String bundleName, String key, Object[] params, Locale locale) {
110        try {
111            return I18NUtils.getMessageString(MESSAGES_BUNDLE, key, params, locale);
112        } catch (MissingResourceException e) {
113            log.trace("No bundle found", e);
114            return null;
115        }
116    }
117
118}