001/* 002 * Stallion Core: A Modern Web Framework 003 * 004 * Copyright (C) 2015 - 2016 Stallion Software LLC. 005 * 006 * This program is free software: you can redistribute it and/or modify it under the terms of the 007 * GNU General Public License as published by the Free Software Foundation, either version 2 of 008 * the License, or (at your option) any later version. This program is distributed in the hope that 009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 011 * License for more details. You should have received a copy of the GNU General Public License 012 * along with this program. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>. 013 * 014 * 015 * 016 */ 017 018package io.stallion.requests.validators; 019 020 021import io.stallion.exceptions.ClientException; 022import io.stallion.reflection.PropertyUtils; 023import io.stallion.utils.GeneralUtils; 024import io.stallion.utils.Sanitize; 025 026import java.util.Collection; 027import java.util.List; 028import java.util.Map; 029import java.util.regex.Pattern; 030 031import static io.stallion.utils.Literals.emptyObject; 032import static io.stallion.utils.Literals.list; 033 034public class SafeMerger { 035 private List<OneParam> params = list(); 036 037 public static SafeMerger with() { 038 return new SafeMerger(); 039 } 040 041 /* 042 public static SafeMerger nonEmpty(String...fieldNames) { 043 SafeMerger merger = new SafeMerger().withNonEmpty(fieldNames); 044 return merger; 045 } 046 047 public static SafeMerger nonNull(String...fieldNames) { 048 SafeMerger merger = new SafeMerger().withNonNull(fieldNames); 049 return merger; 050 } 051 052 public static SafeMerger optional(String...fieldNames) { 053 SafeMerger merger = new SafeMerger().withOptional(fieldNames); 054 return merger; 055 } 056 */ 057 058 059 public SafeMerger nonNull(String...fieldNames) { 060 for (String field: fieldNames) { 061 params.add( 062 new OneParam().setFieldName(field).setRequired(true) 063 ); 064 } 065 return this; 066 } 067 068 public SafeMerger nonEmpty(String...fieldNames) { 069 for (String field: fieldNames) { 070 params.add(new OneParam().setFieldName(field).setRequired(true).setNonEmpty(true)); 071 } 072 return this; 073 } 074 075 public SafeMerger email(String...fieldNames) { 076 for (String field: fieldNames) { 077 params.add(new OneParam().setFieldName(field).setEmail(true)); 078 } 079 return this; 080 } 081 082 public SafeMerger minLength(int minLength, String...fieldNames) { 083 for (String field: fieldNames) { 084 params.add(new OneParam().setFieldName(field).setRequired(true).setMinLength(minLength)); 085 } 086 return this; 087 } 088 089 public SafeMerger patterns(Pattern pattern, String ...fieldNames) { 090 for (String field: fieldNames) { 091 params.add(new OneParam().setFieldName(field).setRequired(true).setPattern(pattern)); 092 } 093 return this; 094 } 095 096 public SafeMerger optional(String...fieldNames) { 097 for (String field: fieldNames) { 098 params.add(new OneParam().setFieldName(field).setRequired(false)); 099 } 100 return this; 101 } 102 103 public SafeMerger optionalEmail(String...fieldNames) { 104 for (String field: fieldNames) { 105 params.add(new OneParam().setFieldName(field).setRequired(false).setNonEmpty(false).setEmail(true)); 106 } 107 return this; 108 } 109 110 public <T> T mergeMap(Map<String, Object> newValues, T dest) { 111 List<String> errors = list(); 112 for (OneParam param : params) { 113 Object val = newValues.getOrDefault(param.getFieldName(), null); 114 mergeForParam(param, val, dest, errors); 115 } 116 if (errors.size() > 0) { 117 throw new ClientException("Errors validating request: " + String.join(".\n", errors)); 118 } 119 return dest; 120 121 } 122 123 public <T> T mergeMap(Map<String, Object> newValues, Class<? extends T> destClass) { 124 T dest = null; 125 try { 126 dest = (T)destClass.newInstance(); 127 } catch (InstantiationException e) { 128 throw new RuntimeException(e); 129 } catch (IllegalAccessException e) { 130 throw new RuntimeException(e); 131 } 132 return mergeMap(newValues, dest); 133 } 134 135 136 public <T> T merge(T newValues, T dest) { 137 List<String> errors = list(); 138 for (OneParam param : params) { 139 Object val = PropertyUtils.getPropertyOrMappedValue(newValues, param.getFieldName()); 140 mergeForParam(param, val, dest, errors); 141 } 142 if (errors.size() > 0) { 143 throw new ClientException("Errors validating request: " + String.join(".\n", errors)); 144 } 145 return dest; 146 } 147 148 private void mergeForParam(OneParam param, Object val, Object dest, List<String> errors) { 149 if (val == null) { 150 if (param.isRequired()) { 151 errors.add("Field " + param.getFieldName() + " is required."); 152 } 153 return; 154 } 155 if (emptyObject(val)) { 156 if (param.isNonEmpty()) { 157 errors.add("Field " + param.getFieldName() + " must not be empty."); 158 return; 159 } else if (!param.isRequired()) { 160 return; 161 } 162 } 163 if (param.getMinLength() > 0) { 164 if (val instanceof String) { 165 if (((String) val).length() < param.getMinLength()) { 166 errors.add("Field " + param.getFieldName() + " must be at least " + param.getMinLength() + " character(s)"); 167 return; 168 } 169 } else if (val instanceof Collection) { 170 if (((Collection) val).size() < param.getMinLength()) { 171 errors.add("Field " + param.getFieldName() + " must have at least " + param.getMinLength() + " entries."); 172 return; 173 } 174 } 175 } 176 if (param.isEmail()) { 177 String valString = (String)val; 178 if (!GeneralUtils.isValidEmailAddress(valString)) { 179 errors.add("Field " + param.getFieldName() + " must be a valid email address. Passed in value - " + Sanitize.stripAll(valString) + " - is not valid."); 180 return; 181 } 182 } 183 PropertyUtils.setProperty(dest, param.getFieldName(), val); 184 } 185 public <T> T merge(T newValues) { 186 T dest = null; 187 try { 188 dest = (T)newValues.getClass().newInstance(); 189 } catch (InstantiationException e) { 190 throw new RuntimeException(e); 191 } catch (IllegalAccessException e) { 192 throw new RuntimeException(e); 193 } 194 return merge(newValues, dest); 195 } 196 197 public List<OneParam> getParams() { 198 return params; 199 } 200 201 public SafeMerger setParams(List<OneParam> params) { 202 this.params = params; 203 return this; 204 } 205 206 207 208 static class OneParam { 209 private String fieldName; 210 private boolean required = false; 211 private boolean nonEmpty = false; 212 private int minLength = 0; 213 private Pattern pattern = null; 214 private boolean email = false; 215 216 public String getFieldName() { 217 return fieldName; 218 } 219 220 public OneParam setFieldName(String fieldName) { 221 this.fieldName = fieldName; 222 return this; 223 } 224 225 public boolean isRequired() { 226 return required; 227 } 228 229 public OneParam setRequired(boolean required) { 230 this.required = required; 231 return this; 232 } 233 234 public boolean isNonEmpty() { 235 return nonEmpty; 236 } 237 238 public OneParam setNonEmpty(boolean nonEmpty) { 239 this.nonEmpty = nonEmpty; 240 return this; 241 } 242 243 public int getMinLength() { 244 return minLength; 245 } 246 247 public OneParam setMinLength(int minLength) { 248 this.minLength = minLength; 249 return this; 250 } 251 252 public Pattern getPattern() { 253 return pattern; 254 } 255 256 public OneParam setPattern(Pattern pattern) { 257 this.pattern = pattern; 258 return this; 259 } 260 261 public boolean isEmail() { 262 return email; 263 } 264 265 public OneParam setEmail(boolean email) { 266 this.email = email; 267 return this; 268 } 269 } 270}