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.io.Serializable;
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Locale;
027import java.util.Map;
028import java.util.regex.Pattern;
029
030import org.apache.commons.lang3.StringUtils;
031
032/**
033 * This constraint ensures some object's String representation match a pattern.
034 *
035 * @since 7.1
036 */
037public class PatternConstraint extends AbstractConstraint {
038
039    private static final long serialVersionUID = 1L;
040
041    private static final String NAME = "PatternConstraint";
042
043    private static final String PNAME_PATTERN = "Pattern";
044
045    protected final Pattern pattern;
046
047    public PatternConstraint(String pattern) {
048        this.pattern = Pattern.compile(pattern);
049    }
050
051    @Override
052    public boolean validate(Object object) {
053        if (object == null) {
054            return true;
055        }
056        return pattern.matcher(object.toString()).matches();
057    }
058
059    /**
060     * <p>
061     * Here, value is : <br>
062     * name = {@value #NAME} <br>
063     * parameters =
064     * <ul>
065     * <li>{@value #PNAME_PATTERN} : [0-9]+</li>
066     * </ul>
067     * </p>
068     */
069    @Override
070    public Description getDescription() {
071        Map<String, Serializable> params = new HashMap<String, Serializable>();
072        params.put(PNAME_PATTERN, pattern.pattern());
073        return new Description(PatternConstraint.NAME, params);
074    }
075
076    /**
077     * @return The pattern used by this constraint to validate.
078     * @since 7.1
079     */
080    public String getPattern() {
081        return pattern.pattern();
082    }
083
084    @Override
085    public String getErrorMessage(Object invalidValue, Locale locale) {
086        // test whether there's a custom translation for this field constraint specific translation
087        // the expected key is label.schema.constraint.violation.[ConstraintName]
088        // follow the AbstractConstraint behavior otherwise
089        List<String> pathTokens = new ArrayList<String>();
090        pathTokens.add(MESSAGES_KEY);
091        pathTokens.add(PatternConstraint.NAME);
092        String key = StringUtils.join(pathTokens, '.');
093        Object[] params = new Object[] { getPattern() };
094        Locale computedLocale = locale != null ? locale : Constraint.MESSAGES_DEFAULT_LANG;
095        String message = getMessageString(MESSAGES_BUNDLE, key, params, computedLocale);
096        if (message != null && !message.trim().isEmpty() && !key.equals(message)) {
097            // use a custom constraint message if there's one
098            return message;
099        } else {
100            // follow AbstractConstraint behavior otherwise
101            return super.getErrorMessage(invalidValue, computedLocale);
102        }
103    }
104
105    @Override
106    public int hashCode() {
107        final int prime = 31;
108        int result = 1;
109        result = prime * result + ((pattern == null) ? 0 : pattern.pattern().hashCode());
110        return result;
111    }
112
113    @Override
114    public boolean equals(Object obj) {
115        if (this == obj) {
116            return true;
117        }
118        if (obj == null) {
119            return false;
120        }
121        if (getClass() != obj.getClass()) {
122            return false;
123        }
124        PatternConstraint other = (PatternConstraint) obj;
125        if (pattern == null) {
126            if (other.pattern != null) {
127                return false;
128            }
129        } else if (!pattern.pattern().equals(other.pattern.pattern())) {
130            return false;
131        }
132        return true;
133    }
134
135}