001/*
002 * (C) Copyright 2006-2008 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 *     bstefanescu
018 */
019package org.nuxeo.ecm.webengine.forms.validation;
020
021import java.lang.reflect.Method;
022import java.lang.reflect.Modifier;
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Map;
028
029import org.nuxeo.ecm.webengine.forms.validation.annotations.Enumeration;
030import org.nuxeo.ecm.webengine.forms.validation.annotations.Length;
031import org.nuxeo.ecm.webengine.forms.validation.annotations.NotNull;
032import org.nuxeo.ecm.webengine.forms.validation.annotations.Range;
033import org.nuxeo.ecm.webengine.forms.validation.annotations.Regex;
034import org.nuxeo.ecm.webengine.forms.validation.annotations.Required;
035
036/**
037 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
038 */
039public class FormDescriptor {
040
041    protected FormValidator validator;
042
043    protected Map<String, Field> fields = new HashMap<>();
044
045    protected HashSet<String> requiredFields = new HashSet<>();
046
047    public FormDescriptor(Class<?> type) throws ReflectiveOperationException {
048        Method[] methods = type.getMethods(); // get all inherited public methods
049        int mod = type.getModifiers();
050        if (!Modifier.isInterface(mod)) {
051            throw new IllegalArgumentException("Form type is not an interface");
052        }
053        for (Method m : methods) {
054            String name = m.getName();
055            if (!name.startsWith("get")) {
056                continue;
057            }
058            // get the field name
059            name = getFieldName(name, name.length());
060            Field field = new Field(m, name);
061            if (field.required) {
062                requiredFields.add(field.name);
063            }
064            fields.put(name, field);
065        }
066        org.nuxeo.ecm.webengine.forms.validation.annotations.FormValidator fv = type.getAnnotation(org.nuxeo.ecm.webengine.forms.validation.annotations.FormValidator.class);
067        if (fv != null) {
068            validator = fv.value().getDeclaredConstructor().newInstance();
069        }
070    }
071
072    static class Field {
073        CompositeValidator validator;
074
075        String name;
076
077        Method m;
078
079        boolean isArray;
080
081        boolean required;
082
083        boolean notnull;
084
085        String defaultValue;
086
087        TypeConvertor<?> convertor;
088
089        Field(Method m, String name) throws ReflectiveOperationException {
090            validator = new CompositeValidator();
091            // not null
092            NotNull nn = m.getAnnotation(NotNull.class);
093            if (nn != null) {
094                String dv = nn.value();
095                if (dv.length() > 0) {
096                    defaultValue = dv;
097                } else {
098                    notnull = true;
099                }
100            }
101            // required
102            required = m.isAnnotationPresent(Required.class);
103            // enum
104            Enumeration aenum = m.getAnnotation(Enumeration.class);
105            if (aenum != null) {
106                validator.add(new EnumerationValidator(aenum.value()));
107            }
108            // regex
109            Regex regex = m.getAnnotation(Regex.class);
110            if (regex != null) {
111                validator.add(new RegexValidator(regex.value()));
112            }
113            // length
114            Length length = m.getAnnotation(Length.class);
115            if (length != null) {
116                if (length.value() > -1) {
117                    validator.add(new ExactLengthValidator(length.value()));
118                } else {
119                    validator.add(new LengthValidator(length.min(), length.max()));
120                }
121            }
122            // range
123            Range range = m.getAnnotation(Range.class);
124            if (range != null) {
125                validator.add(new RangeValidator(range.min(), range.max(), range.negate()));
126            }
127            // custom validator
128            org.nuxeo.ecm.webengine.forms.validation.annotations.FieldValidator custom = m.getAnnotation(org.nuxeo.ecm.webengine.forms.validation.annotations.FieldValidator.class);
129            if (custom != null) {
130                validator.add((FieldValidator) custom.value().getDeclaredConstructor().newInstance());
131            }
132            // type convertor
133            Class<?> rtype = m.getReturnType();
134            isArray = rtype.isArray();
135            if (isArray) {
136                rtype = rtype.getComponentType();
137            }
138            convertor = TypeConvertor.getConvertor(rtype);
139
140            this.m = m;
141            this.name = name;
142        }
143
144        Object validate(String value) throws ValidationException {
145            if (value == null || value.length() == 0) {
146                value = null; // "" empty strings are treated as null values
147                if (notnull) {
148                    throw new ValidationException();
149                } else if (defaultValue != null) {
150                    value = defaultValue;
151                }
152            }
153            Object obj = value;
154            if (convertor != null) {
155                obj = convertor.convert(value);
156            }
157            if (validator != null) {
158                validator.validate(value, obj);
159            }
160            return obj;
161        }
162
163        Object[] validateArray(String[] values) throws ValidationException {
164            List<Object> result = new ArrayList<>();
165            for (String value : values) {
166                result.add(validate(value));
167            }
168            if (convertor != null) {
169                return result.toArray(convertor.newArray(values.length));
170            } else {
171                return result.toArray(new String[values.length]);
172            }
173        }
174    }
175
176    static String getFieldName(String key, int len) {
177        if (len == 4) {
178            return "" + Character.toLowerCase(key.charAt(3));
179        } else {
180            return Character.toLowerCase(key.charAt(3)) + key.substring(4);
181        }
182    }
183
184}