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; 028 029import org.apache.commons.lang3.StringUtils; 030 031/** 032 * This constraints checks whether an object's String representation size is in some interval. 033 * <p> 034 * This constraint's bounds are not strict (i.e. >= and <=). 035 * </p> 036 * 037 * @since 7.1 038 */ 039public class LengthConstraint extends AbstractConstraint { 040 041 private static final long serialVersionUID = 3630463971175189087L; 042 043 private static final String NAME = "LengthConstraint"; 044 045 private static final String PNAME_MIN_LENGTH = "Minimum"; 046 047 private static final String PNAME_MAX_LENGTH = "Maximum"; 048 049 private final Long min; 050 051 private final Long max; 052 053 /** 054 * For a fixed length, use min = max values. 055 * <p> 056 * Bounds could be any object having toString representating an integer. 057 * </p> 058 * 059 * @param min Minimum length for the validated String, use a null value to get unbounded length. 060 * @param max Maximum length for the validated String, use a null value to get unbounded length. 061 */ 062 public LengthConstraint(Object min, Object max) { 063 this.min = ConstraintUtils.objectToPostiveLong(min); 064 this.max = ConstraintUtils.objectToPostiveLong(max); 065 } 066 067 @Override 068 public boolean validate(Object object) { 069 if (object == null) { 070 return true; 071 } 072 int len = object.toString().length(); 073 if (min != null && len < min) { 074 return false; 075 } 076 if (max != null && len > max) { 077 return false; 078 } 079 return true; 080 } 081 082 /** 083 * Here, value is : <br> 084 * name = {@value #NAME} <br> 085 * parameters = 086 * <ul> 087 * <li>{@value #PNAME_MIN_LENGTH} : 5 // only is bounded</li> 088 * <li>{@value #PNAME_MAX_LENGTH} : 10 // only if bounded</li> 089 * </ul> 090 */ 091 @Override 092 public Description getDescription() { 093 Map<String, Serializable> params = new HashMap<>(); 094 if (min != null) { 095 params.put(PNAME_MIN_LENGTH, min); 096 } 097 if (max != null) { 098 params.put(PNAME_MAX_LENGTH, max); 099 } 100 return new Description(LengthConstraint.NAME, params); 101 } 102 103 /** 104 * @return This constraints minimum length if bounded, null otherwise. 105 * @since 7.1 106 */ 107 public Long getMin() { 108 return min; 109 } 110 111 /** 112 * @return This constraints maximum length if bounded, null otherwise. 113 * @since 7.1 114 */ 115 public Long getMax() { 116 return max; 117 } 118 119 @Override 120 public String getErrorMessage(Object invalidValue, Locale locale) { 121 // test whether there's a custom translation for this field constraint specific translation 122 // the expected key is label.schema.constraint.violation.[ConstraintName].minmax ou 123 // the expected key is label.schema.constraint.violation.[ConstraintName].min ou 124 // the expected key is label.schema.constraint.violation.[ConstraintName].max 125 // follow the AbstractConstraint behavior otherwise 126 Object[] params; 127 String subKey; 128 if (min != null && max != null) { 129 params = new Object[] { min, max }; 130 subKey = "minmax"; 131 } else if (min != null) { 132 params = new Object[] { min }; 133 subKey = "min"; 134 } else { 135 params = new Object[] { max }; 136 subKey = "max"; 137 } 138 List<String> pathTokens = new ArrayList<>(); 139 pathTokens.add(MESSAGES_KEY); 140 pathTokens.add(LengthConstraint.NAME); 141 pathTokens.add(subKey); 142 String key = StringUtils.join(pathTokens, '.'); 143 Locale computedLocale = locale != null ? locale : Constraint.MESSAGES_DEFAULT_LANG; 144 String message = getMessageString(MESSAGES_BUNDLE, key, params, computedLocale); 145 if (message != null && !message.trim().isEmpty() && !key.equals(message)) { 146 // use a custom constraint message if there's one 147 return message; 148 } else { 149 // follow AbstractConstraint behavior otherwise 150 return super.getErrorMessage(invalidValue, computedLocale); 151 } 152 } 153 154 @Override 155 public int hashCode() { 156 final int prime = 31; 157 int result = 1; 158 result = prime * result + ((max == null) ? 0 : max.hashCode()); 159 result = prime * result + ((min == null) ? 0 : min.hashCode()); 160 return result; 161 } 162 163 @Override 164 public boolean equals(Object obj) { 165 if (this == obj) { 166 return true; 167 } 168 if (obj == null) { 169 return false; 170 } 171 if (getClass() != obj.getClass()) { 172 return false; 173 } 174 LengthConstraint other = (LengthConstraint) obj; 175 if (max == null) { 176 if (other.max != null) { 177 return false; 178 } 179 } else if (!max.equals(other.max)) { 180 return false; 181 } 182 if (min == null) { 183 if (other.min != null) { 184 return false; 185 } 186 } else if (!min.equals(other.min)) { 187 return false; 188 } 189 return true; 190 } 191 192}