001/* 002 * (C) Copyright 2014 Nuxeo SA (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.lang.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 * </p> 091 */ 092 @Override 093 public Description getDescription() { 094 Map<String, Serializable> params = new HashMap<String, Serializable>(); 095 if (min != null) { 096 params.put(PNAME_MIN_LENGTH, min); 097 } 098 if (max != null) { 099 params.put(PNAME_MAX_LENGTH, max); 100 } 101 return new Description(LengthConstraint.NAME, params); 102 } 103 104 /** 105 * @return This constraints minimum length if bounded, null otherwise. 106 * @since 7.1 107 */ 108 public Long getMin() { 109 return min; 110 } 111 112 /** 113 * @return This constraints maximum length if bounded, null otherwise. 114 * @since 7.1 115 */ 116 public Long getMax() { 117 return max; 118 } 119 120 @Override 121 public String getErrorMessage(Object invalidValue, Locale locale) { 122 // test whether there's a custom translation for this field constraint specific translation 123 // the expected key is label.schema.constraint.violation.[ConstraintName].minmax ou 124 // the expected key is label.schema.constraint.violation.[ConstraintName].min ou 125 // the expected key is label.schema.constraint.violation.[ConstraintName].max 126 // follow the AbstractConstraint behavior otherwise 127 Object[] params; 128 String subKey; 129 if (min != null && max != null) { 130 params = new Object[] { min, max }; 131 subKey = "minmax"; 132 } else if (min != null) { 133 params = new Object[] { min }; 134 subKey = "min"; 135 } else { 136 params = new Object[] { max }; 137 subKey = "max"; 138 } 139 List<String> pathTokens = new ArrayList<String>(); 140 pathTokens.add(MESSAGES_KEY); 141 pathTokens.add(LengthConstraint.NAME); 142 pathTokens.add(subKey); 143 String key = StringUtils.join(pathTokens, '.'); 144 Locale computedLocale = locale != null ? locale : Constraint.MESSAGES_DEFAULT_LANG; 145 String message = getMessageString(MESSAGES_BUNDLE, key, params, computedLocale); 146 if (message != null && !message.trim().isEmpty() && !key.equals(message)) { 147 // use a custom constraint message if there's one 148 return message; 149 } else { 150 // follow AbstractConstraint behavior otherwise 151 return super.getErrorMessage(invalidValue, computedLocale); 152 } 153 } 154 155 @Override 156 public int hashCode() { 157 final int prime = 31; 158 int result = 1; 159 result = prime * result + ((max == null) ? 0 : max.hashCode()); 160 result = prime * result + ((min == null) ? 0 : min.hashCode()); 161 return result; 162 } 163 164 @Override 165 public boolean equals(Object obj) { 166 if (this == obj) { 167 return true; 168 } 169 if (obj == null) { 170 return false; 171 } 172 if (getClass() != obj.getClass()) { 173 return false; 174 } 175 LengthConstraint other = (LengthConstraint) obj; 176 if (max == null) { 177 if (other.max != null) { 178 return false; 179 } 180 } else if (!max.equals(other.max)) { 181 return false; 182 } 183 if (min == null) { 184 if (other.min != null) { 185 return false; 186 } 187 } else if (!min.equals(other.min)) { 188 return false; 189 } 190 return true; 191 } 192 193}