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