001    /*******************************************************************************
002     * Portions created by Sebastian Thomschke are copyright (c) 2005-2011 Sebastian
003     * Thomschke.
004     * 
005     * All Rights Reserved. This program and the accompanying materials
006     * are made available under the terms of the Eclipse Public License v1.0
007     * which accompanies this distribution, and is available at
008     * http://www.eclipse.org/legal/epl-v10.html
009     * 
010     * Contributors:
011     *     Sebastian Thomschke - initial implementation.
012     *******************************************************************************/
013    package net.sf.oval.configuration.annotation;
014    
015    import static net.sf.oval.Validator.getCollectionFactory;
016    
017    import java.lang.annotation.Annotation;
018    import java.lang.reflect.AccessibleObject;
019    import java.lang.reflect.Field;
020    import java.lang.reflect.Method;
021    import java.util.Collection;
022    import java.util.List;
023    
024    import javax.persistence.Basic;
025    import javax.persistence.Column;
026    import javax.persistence.GeneratedValue;
027    import javax.persistence.Lob;
028    import javax.persistence.ManyToMany;
029    import javax.persistence.ManyToOne;
030    import javax.persistence.OneToMany;
031    import javax.persistence.OneToOne;
032    import javax.persistence.Version;
033    
034    import net.sf.oval.Check;
035    import net.sf.oval.collection.CollectionFactory;
036    import net.sf.oval.configuration.Configurer;
037    import net.sf.oval.configuration.pojo.elements.ClassConfiguration;
038    import net.sf.oval.configuration.pojo.elements.ConstraintSetConfiguration;
039    import net.sf.oval.configuration.pojo.elements.FieldConfiguration;
040    import net.sf.oval.configuration.pojo.elements.MethodConfiguration;
041    import net.sf.oval.configuration.pojo.elements.MethodReturnValueConfiguration;
042    import net.sf.oval.constraint.AssertValidCheck;
043    import net.sf.oval.constraint.LengthCheck;
044    import net.sf.oval.constraint.NotNullCheck;
045    import net.sf.oval.constraint.RangeCheck;
046    import net.sf.oval.internal.util.ReflectionUtils;
047    
048    /**
049     * Constraints configurer that interprets certain EJB3 JPA annotations:
050     * <ul>
051     * <li>javax.persistence.Basic(optional=false)     => net.sf.oval.constraint.NotNullCheck
052     * <li>javax.persistence.OneToOne(optional=false)  => net.sf.oval.constraint.NotNullCheck, net.sf.oval.constraint.AssertValidCheck
053     * <li>javax.persistence.ManyToOne(optional=false) => net.sf.oval.constraint.NotNullCheck, net.sf.oval.constraint.AssertValidCheck
054     * <li>javax.persistence.ManyToMany                => net.sf.oval.constraint.AssertValidCheck
055     * <li>javax.persistence.Column(nullable=false)    => net.sf.oval.constraint.NotNullCheck
056     * <li>javax.persistence.Column(length=5)          => net.sf.oval.constraint.LengthCheck
057     * </ul>
058     * @author Sebastian Thomschke
059     */
060    public class JPAAnnotationsConfigurer implements Configurer
061    {
062            protected Boolean applyFieldConstraintsToSetters;
063            protected Boolean applyFieldConstraintsToConstructors;
064    
065            /**
066             * @return the applyFieldConstraintsToConstructors
067             */
068            public Boolean getApplyFieldConstraintsToConstructors()
069            {
070                    return applyFieldConstraintsToConstructors;
071            }
072    
073            /**
074             * {@inheritDoc}
075             */
076            public ClassConfiguration getClassConfiguration(final Class< ? > clazz)
077            {
078                    final CollectionFactory cf = getCollectionFactory();
079    
080                    final ClassConfiguration config = new ClassConfiguration();
081                    config.type = clazz;
082                    config.applyFieldConstraintsToConstructors = applyFieldConstraintsToConstructors;
083                    config.applyFieldConstraintsToSetters = applyFieldConstraintsToSetters;
084    
085                    List<Check> checks = cf.createList(2);
086    
087                    /*
088                     * determine field checks
089                     */
090                    for (final Field field : config.type.getDeclaredFields())
091                    {
092    
093                            // loop over all annotations of the current field
094                            for (final Annotation annotation : field.getAnnotations())
095                                    if (annotation instanceof Basic)
096                                            initializeChecks((Basic) annotation, checks);
097                                    else if (annotation instanceof Column)
098                                            initializeChecks((Column) annotation, checks, field);
099                                    else if (annotation instanceof OneToOne)
100                                            initializeChecks((OneToOne) annotation, checks);
101                                    else if (annotation instanceof ManyToOne)
102                                            initializeChecks((ManyToOne) annotation, checks);
103                                    else if (annotation instanceof ManyToMany)
104                                            initializeChecks((ManyToMany) annotation, checks);
105                                    else if (annotation instanceof OneToMany) initializeChecks((OneToMany) annotation, checks);
106                            if (checks.size() > 0)
107                            {
108                                    if (config.fieldConfigurations == null) config.fieldConfigurations = cf.createSet(8);
109    
110                                    final FieldConfiguration fc = new FieldConfiguration();
111                                    fc.name = field.getName();
112                                    fc.checks = checks;
113                                    checks = cf.createList(); // create a new list for the next field with checks
114                                    config.fieldConfigurations.add(fc);
115                            }
116                    }
117    
118                    /*
119                     * determine getter checks
120                     */
121                    for (final Method method : config.type.getDeclaredMethods())
122                    {
123                            // consider getters only 
124                            if (!ReflectionUtils.isGetter(method)) continue;
125    
126                            // loop over all annotations
127                            for (final Annotation annotation : method.getAnnotations())
128                                    if (annotation instanceof Basic)
129                                            initializeChecks((Basic) annotation, checks);
130                                    else if (annotation instanceof Column)
131                                            initializeChecks((Column) annotation, checks, method);
132                                    else if (annotation instanceof OneToOne)
133                                            initializeChecks((OneToOne) annotation, checks);
134                                    else if (annotation instanceof ManyToOne)
135                                            initializeChecks((ManyToOne) annotation, checks);
136                                    else if (annotation instanceof ManyToMany)
137                                            initializeChecks((ManyToMany) annotation, checks);
138                                    else if (annotation instanceof OneToMany) initializeChecks((OneToMany) annotation, checks);
139    
140                            // check if anything has been configured for this method at all
141                            if (checks.size() > 0)
142                            {
143                                    if (config.methodConfigurations == null) config.methodConfigurations = cf.createSet(2);
144    
145                                    final MethodConfiguration mc = new MethodConfiguration();
146                                    mc.name = method.getName();
147                                    mc.isInvariant = true;
148                                    mc.returnValueConfiguration = new MethodReturnValueConfiguration();
149                                    mc.returnValueConfiguration.checks = checks;
150                                    checks = cf.createList(); // create a new list for the next method having return value checks
151                                    config.methodConfigurations.add(mc);
152                            }
153                    }
154                    return config;
155            }
156    
157            /**
158             * {@inheritDoc}
159             */
160            public ConstraintSetConfiguration getConstraintSetConfiguration(final String constraintSetId)
161            {
162                    return null;
163            }
164    
165            protected void initializeChecks(final Basic annotation, final Collection<Check> checks)
166            {
167                    assert annotation != null;
168                    assert checks != null;
169    
170                    if (!annotation.optional()) checks.add(new NotNullCheck());
171            }
172    
173            protected void initializeChecks(final Column annotation, final Collection<Check> checks,
174                            final AccessibleObject fieldOrMethod)
175            {
176                    assert annotation != null;
177                    assert checks != null;
178    
179                    /* If the value is generated (annotated with @GeneratedValue) it is allowed to be null 
180                     * before the entity has been persisted, same is true in case of optimistic locking
181                     * when a field is annotated with @Version.
182                     * Therefore and because of the fact that there is no generic way to determine if an entity 
183                     * has been persisted already, a not-null check will not be performed for such fields. 
184                     */
185                    if (!annotation.nullable() && !fieldOrMethod.isAnnotationPresent(GeneratedValue.class)
186                                    && !fieldOrMethod.isAnnotationPresent(Version.class)) checks.add(new NotNullCheck());
187    
188                    // only consider length parameter if @Lob is not present
189                    if (!fieldOrMethod.isAnnotationPresent(Lob.class))
190                    {
191                            final LengthCheck lengthCheck = new LengthCheck();
192                            lengthCheck.setMax(annotation.length());
193                            checks.add(lengthCheck);
194                    }
195    
196                    // only consider precision/scale for numeric fields
197                    if (annotation.precision() > 0
198                                    && Number.class.isAssignableFrom(fieldOrMethod instanceof Field ? ((Field) fieldOrMethod).getType()
199                                                    : ((Method) fieldOrMethod).getReturnType()))
200                    {
201                            /*
202                             * precision = 6, scale = 2  => -9999.99<=x<=9999.99
203                             * precision = 4, scale = 1  =>   -999.9<=x<=999.9
204                             */
205                            final RangeCheck rangeCheck = new RangeCheck();
206                            rangeCheck.setMax(Math.pow(10, annotation.precision() - annotation.scale())
207                                            - Math.pow(0.1, annotation.scale()));
208                            rangeCheck.setMin(-1 * rangeCheck.getMax());
209                            checks.add(rangeCheck);
210                    }
211            }
212    
213            protected void initializeChecks(final ManyToMany annotation, final Collection<Check> checks)
214            {
215                    assert annotation != null;
216                    assert checks != null;
217    
218                    checks.add(new AssertValidCheck());
219            }
220    
221            protected void initializeChecks(final ManyToOne annotation, final Collection<Check> checks)
222            {
223                    assert annotation != null;
224                    assert checks != null;
225    
226                    if (!annotation.optional()) checks.add(new NotNullCheck());
227                    checks.add(new AssertValidCheck());
228            }
229    
230            protected void initializeChecks(final OneToMany annotation, final Collection<Check> checks)
231            {
232                    assert annotation != null;
233                    assert checks != null;
234    
235                    checks.add(new AssertValidCheck());
236            }
237    
238            protected void initializeChecks(final OneToOne annotation, final Collection<Check> checks)
239            {
240                    assert annotation != null;
241                    assert checks != null;
242    
243                    if (!annotation.optional()) checks.add(new NotNullCheck());
244                    checks.add(new AssertValidCheck());
245            }
246    
247            /**
248             * @return the applyFieldConstraintsToSetter
249             */
250            public Boolean isApplyFieldConstraintsToSetter()
251            {
252                    return applyFieldConstraintsToSetters;
253            }
254    
255            /**
256             * @param applyFieldConstraintsToConstructors the applyFieldConstraintsToConstructors to set
257             */
258            public void setApplyFieldConstraintsToConstructors(final Boolean applyFieldConstraintsToConstructors)
259            {
260                    this.applyFieldConstraintsToConstructors = applyFieldConstraintsToConstructors;
261            }
262    
263            /**
264             * @param applyFieldConstraintsToSetters the applyFieldConstraintsToSetter to set
265             */
266            public void setApplyFieldConstraintsToSetters(final Boolean applyFieldConstraintsToSetters)
267            {
268                    this.applyFieldConstraintsToSetters = applyFieldConstraintsToSetters;
269            }
270    
271    }