View Javadoc
1   /*******************************************************************************
2    * Portions created by Sebastian Thomschke are copyright (c) 2005-2014 Sebastian
3    * Thomschke.
4    *
5    * All Rights Reserved. This program and the accompanying materials
6    * are made available under the terms of the Eclipse Public License v1.0
7    * which accompanies this distribution, and is available at
8    * http://www.eclipse.org/legal/epl-v10.html
9    *
10   * Contributors:
11   *     Sebastian Thomschke - initial implementation.
12   *******************************************************************************/
13  package net.sf.oval.configuration.annotation;
14  
15  import static net.sf.oval.Validator.*;
16  
17  import java.lang.annotation.Annotation;
18  import java.lang.reflect.AccessibleObject;
19  import java.lang.reflect.Field;
20  import java.lang.reflect.Method;
21  import java.util.Collection;
22  import java.util.List;
23  
24  import javax.persistence.Basic;
25  import javax.persistence.Column;
26  import javax.persistence.Enumerated;
27  import javax.persistence.GeneratedValue;
28  import javax.persistence.Lob;
29  import javax.persistence.ManyToMany;
30  import javax.persistence.ManyToOne;
31  import javax.persistence.OneToMany;
32  import javax.persistence.OneToOne;
33  import javax.persistence.Version;
34  
35  import net.sf.oval.Check;
36  import net.sf.oval.collection.CollectionFactory;
37  import net.sf.oval.configuration.Configurer;
38  import net.sf.oval.configuration.pojo.elements.ClassConfiguration;
39  import net.sf.oval.configuration.pojo.elements.ConstraintSetConfiguration;
40  import net.sf.oval.configuration.pojo.elements.FieldConfiguration;
41  import net.sf.oval.configuration.pojo.elements.MethodConfiguration;
42  import net.sf.oval.configuration.pojo.elements.MethodReturnValueConfiguration;
43  import net.sf.oval.constraint.AssertValidCheck;
44  import net.sf.oval.constraint.Length;
45  import net.sf.oval.constraint.LengthCheck;
46  import net.sf.oval.constraint.NotNull;
47  import net.sf.oval.constraint.NotNullCheck;
48  import net.sf.oval.constraint.Range;
49  import net.sf.oval.constraint.RangeCheck;
50  import net.sf.oval.internal.util.ReflectionUtils;
51  
52  /**
53   * Constraints configurer that interprets certain EJB3 JPA annotations:
54   * <pre>
55   * * javax.persistence.Basic(optional=false)     => net.sf.oval.constraint.NotNullCheck
56   * * javax.persistence.OneToOne(optional=false)  => net.sf.oval.constraint.NotNullCheck, net.sf.oval.constraint.AssertValidCheck (if addAssertValidConstraints=true)
57   * * javax.persistence.ManyToOne(optional=false) => net.sf.oval.constraint.NotNullCheck, net.sf.oval.constraint.AssertValidCheck (if addAssertValidConstraints=true)
58   * * javax.persistence.ManyToMany                => net.sf.oval.constraint.AssertValidCheck (if addAssertValidConstraints=true)
59   * * javax.persistence.Column(nullable=false)    => net.sf.oval.constraint.NotNullCheck
60   * * javax.persistence.Column(length=5)          => net.sf.oval.constraint.LengthCheck
61   * * javax.persistence.Column(precision>0)       => net.sf.oval.constraint.RangeCheck (for Numbers only)
62   * </pre>
63   *
64   * <b>Important:</b> by default AssertValidChecks are added for n-m relationships. This may be a problem when using lazy loading. Read <a href="http://sourceforge.net/p/oval/discussion/488110/thread/6ec11584/#4ae0">this post</a> for more details.
65   * To avoid this override the method {@link #addAssertValidCheckIfRequired(Annotation, Collection, AccessibleObject)} with an empty method body, for example
66   * <pre>
67   * JPAAnnotationsConfigurer configurer = new JPAAnnotationsConfigurer() {
68   *    protected void addAssertValidCheckIfRequired(Annotation constraintAnnotation, Collection<Check> checks, AccessibleObject fieldOrMethod)
69   *    {
70   *       // do nothing
71   *    }
72   * };
73   * </pre>
74   *
75   * @author Sebastian Thomschke
76   */
77  @SuppressWarnings("javadoc")
78  public class JPAAnnotationsConfigurer implements Configurer
79  {
80  	protected Boolean applyFieldConstraintsToSetters;
81  	protected Boolean applyFieldConstraintsToConstructors;
82  
83  	protected void addAssertValidCheckIfRequired(final Annotation constraintAnnotation, final Collection<Check> checks,
84  			@SuppressWarnings("unused")/*parameter for potential use by subclasses*/final AccessibleObject fieldOrMethod)
85  	{
86  		if (containsCheckOfType(checks, AssertValidCheck.class)) return;
87  
88  		if (constraintAnnotation instanceof OneToOne || constraintAnnotation instanceof OneToMany
89  				|| constraintAnnotation instanceof ManyToOne || constraintAnnotation instanceof ManyToMany)
90  			checks.add(new AssertValidCheck());
91  	}
92  
93  	protected boolean containsCheckOfType(final Collection<Check> checks, final Class< ? extends Check> checkClass)
94  	{
95  		for (final Check check : checks)
96  			if (checkClass.isInstance(check)) return true;
97  		return false;
98  	}
99  
100 	public Boolean getApplyFieldConstraintsToConstructors()
101 	{
102 		return applyFieldConstraintsToConstructors;
103 	}
104 
105 	public ClassConfiguration getClassConfiguration(final Class< ? > clazz)
106 	{
107 		final CollectionFactory cf = getCollectionFactory();
108 
109 		final ClassConfiguration config = new ClassConfiguration();
110 		config.type = clazz;
111 		config.applyFieldConstraintsToConstructors = applyFieldConstraintsToConstructors;
112 		config.applyFieldConstraintsToSetters = applyFieldConstraintsToSetters;
113 
114 		List<Check> checks = cf.createList(2);
115 
116 		/*
117 		 * determine field checks
118 		 */
119 		for (final Field field : config.type.getDeclaredFields())
120 		{
121 
122 			// loop over all annotations of the current field
123 			for (final Annotation annotation : field.getAnnotations())
124 			{
125 				if (annotation instanceof Basic)
126 					initializeChecks((Basic) annotation, checks);
127 				else if (annotation instanceof Column)
128 					initializeChecks((Column) annotation, checks, field);
129 				else if (annotation instanceof OneToOne)
130 					initializeChecks((OneToOne) annotation, checks);
131 				else if (annotation instanceof ManyToOne)
132 					initializeChecks((ManyToOne) annotation, checks);
133 				else if (annotation instanceof ManyToMany)
134 					initializeChecks((ManyToMany) annotation, checks);
135 				else if (annotation instanceof OneToMany) initializeChecks((OneToMany) annotation, checks);
136 
137 				addAssertValidCheckIfRequired(annotation, checks, field);
138 			}
139 
140 			if (checks.size() > 0)
141 			{
142 				if (config.fieldConfigurations == null) config.fieldConfigurations = cf.createSet(8);
143 
144 				final FieldConfiguration fc = new FieldConfiguration();
145 				fc.name = field.getName();
146 				fc.checks = checks;
147 				checks = cf.createList(); // create a new list for the next field with checks
148 				config.fieldConfigurations.add(fc);
149 			}
150 		}
151 
152 		/*
153 		 * determine getter checks
154 		 */
155 		for (final Method method : config.type.getDeclaredMethods())
156 		{
157 			// consider getters only
158 			if (!ReflectionUtils.isGetter(method)) continue;
159 
160 			// loop over all annotations
161 			for (final Annotation annotation : method.getAnnotations())
162 			{
163 				if (annotation instanceof Basic)
164 					initializeChecks((Basic) annotation, checks);
165 				else if (annotation instanceof Column)
166 					initializeChecks((Column) annotation, checks, method);
167 				else if (annotation instanceof OneToOne)
168 					initializeChecks((OneToOne) annotation, checks);
169 				else if (annotation instanceof ManyToOne)
170 					initializeChecks((ManyToOne) annotation, checks);
171 				else if (annotation instanceof ManyToMany)
172 					initializeChecks((ManyToMany) annotation, checks);
173 				else if (annotation instanceof OneToMany) initializeChecks((OneToMany) annotation, checks);
174 
175 				addAssertValidCheckIfRequired(annotation, checks, method);
176 			}
177 
178 			// check if anything has been configured for this method at all
179 			if (checks.size() > 0)
180 			{
181 				if (config.methodConfigurations == null) config.methodConfigurations = cf.createSet(2);
182 
183 				final MethodConfiguration mc = new MethodConfiguration();
184 				mc.name = method.getName();
185 				mc.isInvariant = true;
186 				mc.returnValueConfiguration = new MethodReturnValueConfiguration();
187 				mc.returnValueConfiguration.checks = checks;
188 				checks = cf.createList(); // create a new list for the next method having return value checks
189 				config.methodConfigurations.add(mc);
190 			}
191 		}
192 		return config;
193 	}
194 
195 	public ConstraintSetConfiguration getConstraintSetConfiguration(final String constraintSetId)
196 	{
197 		return null;
198 	}
199 
200 	protected void initializeChecks(final Basic annotation, final Collection<Check> checks)
201 	{
202 		if (!annotation.optional() && !containsCheckOfType(checks, NotNullCheck.class)) checks.add(new NotNullCheck());
203 	}
204 
205 	protected void initializeChecks(final Column annotation, final Collection<Check> checks, final AccessibleObject fieldOrMethod)
206 	{
207 		/* If the value is generated (annotated with @GeneratedValue) it is allowed to be null
208 		 * before the entity has been persisted, same is true in case of optimistic locking
209 		 * when a field is annotated with @Version.
210 		 * Therefore and because of the fact that there is no generic way to determine if an entity
211 		 * has been persisted already, a not-null check will not be performed for such fields.
212 		 */
213 		if (!annotation.nullable() && !fieldOrMethod.isAnnotationPresent(GeneratedValue.class)
214 				&& !fieldOrMethod.isAnnotationPresent(Version.class) && !fieldOrMethod.isAnnotationPresent(NotNull.class))
215 			if (!containsCheckOfType(checks, NotNullCheck.class)) checks.add(new NotNullCheck());
216 
217 		// add Length check based on Column.length parameter, but only:
218 		if (!fieldOrMethod.isAnnotationPresent(Lob.class) && // if @Lob is not present
219 				!fieldOrMethod.isAnnotationPresent(Enumerated.class) && // if @Enumerated is not present
220 				!fieldOrMethod.isAnnotationPresent(Length.class) // if an explicit @Length constraint is not present
221 		)
222 		{
223 			final LengthCheck lengthCheck = new LengthCheck();
224 			lengthCheck.setMax(annotation.length());
225 			checks.add(lengthCheck);
226 		}
227 
228 		// add Range check based on Column.precision/scale parameters, but only:
229 		if (!fieldOrMethod.isAnnotationPresent(Range.class) // if an explicit @Range is not present
230 				&& annotation.precision() > 0 // if precision is > 0
231 				&& Number.class.isAssignableFrom(fieldOrMethod instanceof Field ? ((Field) fieldOrMethod).getType()
232 						: ((Method) fieldOrMethod).getReturnType()) // if numeric field type
233 		)
234 		{
235 			/* precision = 6, scale = 2  => -9999.99<=x<=9999.99
236 			 * precision = 4, scale = 1  =>   -999.9<=x<=999.9
237 			 */
238 			final RangeCheck rangeCheck = new RangeCheck();
239 			rangeCheck.setMax(Math.pow(10, annotation.precision() - annotation.scale()) - Math.pow(0.1, annotation.scale()));
240 			rangeCheck.setMin(-1 * rangeCheck.getMax());
241 			checks.add(rangeCheck);
242 		}
243 	}
244 
245 	@SuppressWarnings("unused")
246 	protected void initializeChecks(final ManyToMany annotation, final Collection<Check> checks)
247 	{
248 		// override if required
249 	}
250 
251 	protected void initializeChecks(final ManyToOne annotation, final Collection<Check> checks)
252 	{
253 		if (!annotation.optional() && !containsCheckOfType(checks, NotNullCheck.class)) checks.add(new NotNullCheck());
254 	}
255 
256 	@SuppressWarnings("unused")
257 	protected void initializeChecks(final OneToMany annotation, final Collection<Check> checks)
258 	{
259 		// override if required
260 	}
261 
262 	protected void initializeChecks(final OneToOne annotation, final Collection<Check> checks)
263 	{
264 		if (!annotation.optional() && !containsCheckOfType(checks, NotNullCheck.class)) checks.add(new NotNullCheck());
265 	}
266 
267 	public Boolean isApplyFieldConstraintsToSetter()
268 	{
269 		return applyFieldConstraintsToSetters;
270 	}
271 
272 	public void setApplyFieldConstraintsToConstructors(final Boolean applyFieldConstraintsToConstructors)
273 	{
274 		this.applyFieldConstraintsToConstructors = applyFieldConstraintsToConstructors;
275 	}
276 
277 	public void setApplyFieldConstraintsToSetters(final Boolean applyFieldConstraintsToSetters)
278 	{
279 		this.applyFieldConstraintsToSetters = applyFieldConstraintsToSetters;
280 	}
281 }