001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nicolas Chapurlat <nchapurlat@nuxeo.com> 016 */ 017 018package org.nuxeo.ecm.core.schema.types.constraints; 019 020import java.io.Serializable; 021import java.util.ArrayList; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Locale; 025import java.util.Map; 026 027import org.apache.commons.lang.StringUtils; 028 029/** 030 * This constraints checks whether an object's String representation size is in some interval. 031 * <p> 032 * This constraint's bounds are not strict (i.e. >= and <=). 033 * </p> 034 * 035 * @since 7.1 036 */ 037public class LengthConstraint extends AbstractConstraint { 038 039 private static final long serialVersionUID = 3630463971175189087L; 040 041 private static final String NAME = "LengthConstraint"; 042 043 private static final String PNAME_MIN_LENGTH = "Minimum"; 044 045 private static final String PNAME_MAX_LENGTH = "Maximum"; 046 047 private final Long min; 048 049 private final Long max; 050 051 /** 052 * For a fixed length, use min = max values. 053 * <p> 054 * Bounds could be any object having toString representating an integer. 055 * </p> 056 * 057 * @param min Minimum length for the validated String, use a null value to get unbounded length. 058 * @param max Maximum length for the validated String, use a null value to get unbounded length. 059 */ 060 public LengthConstraint(Object min, Object max) { 061 this.min = ConstraintUtils.objectToPostiveLong(min); 062 this.max = ConstraintUtils.objectToPostiveLong(max); 063 } 064 065 @Override 066 public boolean validate(Object object) { 067 if (object == null) { 068 return true; 069 } 070 int len = object.toString().length(); 071 if (min != null && len < min) { 072 return false; 073 } 074 if (max != null && len > max) { 075 return false; 076 } 077 return true; 078 } 079 080 /** 081 * Here, value is : <br> 082 * name = {@value #NAME} <br> 083 * parameters = 084 * <ul> 085 * <li>{@value #PNAME_MIN_LENGTH} : 5 // only is bounded</li> 086 * <li>{@value #PNAME_MAX_LENGTH} : 10 // only if bounded</li> 087 * </ul> 088 * </p> 089 */ 090 @Override 091 public Description getDescription() { 092 Map<String, Serializable> params = new HashMap<String, Serializable>(); 093 if (min != null) { 094 params.put(PNAME_MIN_LENGTH, min); 095 } 096 if (max != null) { 097 params.put(PNAME_MAX_LENGTH, max); 098 } 099 return new Description(LengthConstraint.NAME, params); 100 } 101 102 /** 103 * @return This constraints minimum length if bounded, null otherwise. 104 * @since 7.1 105 */ 106 public Long getMin() { 107 return min; 108 } 109 110 /** 111 * @return This constraints maximum length if bounded, null otherwise. 112 * @since 7.1 113 */ 114 public Long getMax() { 115 return max; 116 } 117 118 @Override 119 public String getErrorMessage(Object invalidValue, Locale locale) { 120 // test whether there's a custom translation for this field constraint specific translation 121 // the expected key is label.schema.constraint.violation.[ConstraintName].minmax ou 122 // the expected key is label.schema.constraint.violation.[ConstraintName].min ou 123 // the expected key is label.schema.constraint.violation.[ConstraintName].max 124 // follow the AbstractConstraint behavior otherwise 125 Object[] params; 126 String subKey; 127 if (min != null && max != null) { 128 params = new Object[] { min, max }; 129 subKey = "minmax"; 130 } else if (min != null) { 131 params = new Object[] { min }; 132 subKey = "min"; 133 } else { 134 params = new Object[] { max }; 135 subKey = "max"; 136 } 137 List<String> pathTokens = new ArrayList<String>(); 138 pathTokens.add(MESSAGES_KEY); 139 pathTokens.add(LengthConstraint.NAME); 140 pathTokens.add(subKey); 141 String key = StringUtils.join(pathTokens, '.'); 142 Locale computedLocale = locale != null ? locale : Constraint.MESSAGES_DEFAULT_LANG; 143 String message = getMessageString(MESSAGES_BUNDLE, key, params, computedLocale); 144 if (message != null && !message.trim().isEmpty() && !key.equals(message)) { 145 // use a custom constraint message if there's one 146 return message; 147 } else { 148 // follow AbstractConstraint behavior otherwise 149 return super.getErrorMessage(invalidValue, computedLocale); 150 } 151 } 152 153 @Override 154 public int hashCode() { 155 final int prime = 31; 156 int result = 1; 157 result = prime * result + ((max == null) ? 0 : max.hashCode()); 158 result = prime * result + ((min == null) ? 0 : min.hashCode()); 159 return result; 160 } 161 162 @Override 163 public boolean equals(Object obj) { 164 if (this == obj) { 165 return true; 166 } 167 if (obj == null) { 168 return false; 169 } 170 if (getClass() != obj.getClass()) { 171 return false; 172 } 173 LengthConstraint other = (LengthConstraint) obj; 174 if (max == null) { 175 if (other.max != null) { 176 return false; 177 } 178 } else if (!max.equals(other.max)) { 179 return false; 180 } 181 if (min == null) { 182 if (other.min != null) { 183 return false; 184 } 185 } else if (!min.equals(other.min)) { 186 return false; 187 } 188 return true; 189 } 190 191}