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}