src/test/java/ru/indvdum/jpa/tests/AbstractJPAEntityTest.groovy
author indvdum (gotoindvdum[at]gmail[dot]com)
Sun, 23 Dec 2012 03:24:53 +0400
changeset 20 a05948e9458c
parent 15 7d8a7e7635d2
permissions -rw-r--r--
Interface type of fields processing
     1 package ru.indvdum.jpa.tests
     2 ;
     3 
     4 import static org.junit.Assert.*
     5 
     6 import java.lang.reflect.Field
     7 import java.lang.reflect.Method
     8 import java.lang.reflect.Modifier
     9 
    10 import javax.persistence.EmbeddedId
    11 import javax.persistence.Entity
    12 import javax.persistence.GeneratedValue
    13 import javax.persistence.Id
    14 import javax.persistence.IdClass
    15 import javax.persistence.Transient
    16 
    17 import org.junit.Test
    18 
    19 import ru.indvdum.jpa.dao.JPADataAccessObject
    20 import ru.indvdum.jpa.entities.AbstractEntity
    21 
    22 
    23 /**
    24  * JUnit test case for testing of a creating, listing, updating and removing 
    25  * operations with database of the JPA entities.
    26  * 
    27  * @author 	indvdum (gotoindvdum@gmail.com)
    28  * @since 23.12.2011 22:54:26
    29  *
    30  */
    31 abstract class AbstractJPAEntityTest extends AbstractJPATest {
    32 
    33 	protected def uniqueValue = 1
    34 	protected JPADataAccessObject dao = null
    35 	protected Set toRemove = new HashSet()
    36 	protected Map<Class, Integer> existedEntitiesCount = [:]
    37 
    38 	@Test
    39 	public void testEntity() {
    40 		dao = createDAO()
    41 		try {
    42 			testEntity(getEntityClass())
    43 		} catch (Throwable t) {
    44 			dao.rollback()
    45 			throw t
    46 		} finally {
    47 			dao.close()		
    48 		}
    49 	}
    50 	
    51 	/**
    52 	 * @return Your implementation of JPADataAccessObject
    53 	 */
    54 	protected abstract JPADataAccessObject createDAO();
    55 
    56 	/**
    57 	 * Test creating, listing, updating, and removing of an {@code entityClass} object
    58 	 * 
    59 	 * @param entityClass
    60 	 */
    61 	protected void testEntity(Class entityClass) {
    62 		
    63 		assert AbstractEntity.class.isAssignableFrom(entityClass)
    64 
    65 		Map<String, Object> fieldsValues
    66 		def dbEntity
    67 
    68 		// creating
    69 		def entity = createEntity(entityClass)
    70 		testCreatedEntity(entity)
    71 
    72 		// listing
    73 		toRemove.each {
    74 			assert dao.list(it.class).each { it2 ->
    75 				assert it.class.isAssignableFrom(it2.class)
    76 			}.size() == toRemove.findAll { it2 ->
    77 				it.class.isAssignableFrom(it2.class)
    78 			}.size() + existedEntitiesCount[it.class]
    79 		}
    80 
    81 		// updating
    82 		fieldsValues = updateFields(entity)
    83 		assert dao.persist(entity)
    84 		assert dao.contains(entity)
    85 		dbEntity = dao.find(entityClass, (entity as AbstractEntity).getIdentifierValue())
    86 		assert dbEntity != null
    87 		assert checkEntityFieldValues(dbEntity, fieldsValues)
    88 		testUpdatedEntity(entity)
    89 
    90 		// removing
    91 		assert dao.remove(toRemove)
    92 		assert !dao.contains(toRemove)
    93 		testRemovedEntity(entity)
    94 	}
    95 	
    96 	/**
    97 	 * Create and persist to database an {@code entityClass} object
    98 	 * 
    99 	 * @param entityClass
   100 	 * @return created entity object
   101 	 */
   102 	protected Object createEntity(Class entityClass) {
   103 		if (entityClass.isInterface())
   104 			return null;
   105 		assertNotNull entityClass.annotations.find {it instanceof Entity}
   106 		def entity = entityClass.newInstance()
   107 		assert entity.class == entityClass
   108 		toRemove.add(entity)
   109 		if (!existedEntitiesCount.containsKey(entityClass))
   110 			existedEntitiesCount[entityClass] = dao.list(entityClass).size()
   111 		
   112 		Map<String, Object> fieldsValues = updateFields(entity)
   113 		assert dao.persist(entity)
   114 		assert dao.contains(entity)
   115 		def dbEntity = dao.find(entityClass, (entity as AbstractEntity).getIdentifierValue())
   116 		assert dbEntity != null
   117 		assert checkEntityFieldValues(dbEntity, fieldsValues)
   118 		
   119 		return entity
   120 	}
   121 
   122 	/**
   123 	 * Update all fields of an {@code entity} object
   124 	 * @param entity
   125 	 * @return generated field values
   126 	 */
   127 	protected Map<String, Object> updateFields(Object entity) {
   128 		Map<String, Object> fieldsValues = new HashMap<String, Object>()
   129 		getFields(entity).grep {
   130 			it.annotations.find {
   131 				it instanceof Transient || it instanceof GeneratedValue
   132 			} == null
   133 		}.each {
   134 			def newValue = generateFieldValue(entity, it)
   135 			setFieldValueBySetter(entity, it, newValue)
   136 			fieldsValues.put(it.name, newValue)
   137 		}
   138 		return fieldsValues
   139 	}
   140 	
   141 	/**
   142 	 * Collections and arrays will not be processed
   143 	 * 
   144 	 * @param entity
   145 	 * @param field
   146 	 * @return generated field value
   147 	 */
   148 	protected Object generateFieldValue(Object entity, Field field) {
   149 		def type = field.getType()
   150 		def newValue
   151 		if(type.toString() == 'boolean' || type == Boolean.class) {
   152 			newValue = (boolean) (uniqueValue++ % 2i == 0i)
   153 		} else if(type.toString() == 'byte' || type == Byte) {
   154 			newValue = (byte) (uniqueValue++ % Byte.MAX_VALUE + 1i)
   155 		} else if(type.toString() == 'char' || type == Character) {
   156 			newValue = (char) (uniqueValue++ % (int) Character.MAX_VALUE + 1i)
   157 		} else if(type.toString() == 'short' || type == Short) {
   158 			newValue = (short) (uniqueValue++ % Short.MAX_VALUE + 1i)
   159 		} else if(type.toString() == 'int' || type == Integer) {
   160 			newValue = (int) (uniqueValue++ % Integer.MAX_VALUE + 1i)
   161 		} else if(type.toString() == 'long' || type == Long) {
   162 			newValue = (long) uniqueValue++ % Long.MAX_VALUE + 1L
   163 		} else if(type.toString() == 'float' || type == Float) {
   164 			newValue = (float) uniqueValue++ % Float.MAX_VALUE + 1f
   165 		} else if(type.toString() == 'double' || type == Double) {
   166 			newValue = (double) uniqueValue++ % Double.MAX_VALUE + 1d
   167 		} else if(Number.class.isAssignableFrom(type)) {
   168 			// trying to use constructor with int argument
   169 			newValue = type.newInstance(uniqueValue++ % Byte.MAX_VALUE + 1i)
   170 		} else if(type == String.class) {
   171 			newValue = (String) "test${uniqueValue++}"
   172 		} else if(type instanceof Class && ((type as Class).isInterface() || (type as Class).annotations.find {it instanceof Entity} != null)) { // modifying of a primary keys is deprecated
   173 			// an attempt to use already created entities
   174 			def currentValue = getFieldValue(entity, field)
   175 			newValue = toRemove.find {it.class.isAssignableFrom(type) && it != currentValue}
   176 			if(newValue == null)
   177 				newValue = createEntity(type as Class)
   178 		} else if(Enum.class.isAssignableFrom(type)) {
   179 			def values = type.values();
   180 			newValue = values[uniqueValue++ % values.size()];
   181 		} else if(
   182 				type instanceof Class 
   183 				&& !(
   184 					field.clazz.annotations.find {it instanceof IdClass} != null 
   185 					&& field.declaredAnnotations.find {it instanceof Id} != null
   186 				)
   187 				&& field.declaredAnnotations.find {it instanceof EmbeddedId} == null
   188 				&& !Collection.class.isAssignableFrom(type)
   189 				&& !(type as Class).isArray()
   190 			) { // modifying of a primary keys is deprecated
   191 			newValue = (type as Class).newInstance()
   192 		} else {
   193 			newValue = getFieldValue(entity, field)
   194 		}
   195 		return newValue
   196 	}
   197 
   198 	/**
   199 	 * @param entity
   200 	 * @param rightValues
   201 	 * @return {@code true}, if all entity fields is equals to {@code rightValues}
   202 	 */
   203 	protected boolean checkEntityFieldValues(Object entity, Map<String, Object> rightValues) {
   204 		def result = true
   205 		def fields = getFields(entity)
   206 		rightValues.each {  key, value ->
   207 			fields.find { it.name == key }.each {
   208 				if(!getFieldValue(entity, it).equals(value)) {
   209 					result = false
   210 				}
   211 			}
   212 		}
   213 		return result
   214 	}
   215 
   216 	/**
   217 	 * Set a {@code field} to a {@code value} of an entity {@code object}
   218 	 * 
   219 	 * @param object
   220 	 * @param field
   221 	 * @param value
   222 	 */
   223 	protected void setFieldValue(Object object, Field field, Object value) {
   224 		boolean isAccessible = field.accessible
   225 		field.accessible = true
   226 		field.set(object, value)
   227 		field.accessible = isAccessible
   228 	}
   229 	
   230 	/**
   231 	 * Trying set a {@code field} to a {@code value} of an entity {@code object}
   232 	 * by using a setter method. If setter not found, field will be set directly
   233 	 * throw {@code setFieldValue} method.
   234 	 * 
   235 	 * @param object
   236 	 * @param field
   237 	 * @param value
   238 	 */
   239 	protected void setFieldValueBySetter(Object object, Field field, Object value) {
   240 		boolean isSetted = false
   241 		getMethods(object).grep {
   242 			(
   243 				Modifier.isPublic(it.modifiers) 
   244 				&& it.parameterTypes.size() == 1 
   245 				&& (
   246 					value == null 
   247 					|| it.parameterTypes[0].isAssignableFrom(value.class)
   248 					)
   249 				&& it.name =~ /^set(?i:${field.name.charAt(0)})${field.name.replaceFirst("^.{1}", "")}/
   250 			)
   251 		}.each {
   252 			object."${it.name}"(value)
   253 			isSetted = true
   254 		}
   255 		if(isSetted)
   256 			return
   257 		setFieldValue(object, field, value)
   258 	}
   259 
   260 	/**
   261 	 * @param object
   262 	 * @param field
   263 	 * @return the field value of an object
   264 	 */
   265 	protected Object getFieldValue(Object object, Field field) {
   266 		boolean isAccessible = field.accessible
   267 		field.accessible = true
   268 		def value = field.get(object)
   269 		field.accessible = isAccessible
   270 		return value
   271 	}
   272 
   273 	/**
   274 	 * @param obj
   275 	 * @return all object fields, including inherited
   276 	 */
   277 	protected Collection<Field> getFields(obj) {
   278 		Class clazz = obj.getClass()
   279 		Collection<Field> fields = clazz.declaredFields
   280 		while(clazz.superclass != null) {
   281 			clazz = clazz.superclass
   282 			fields.addAll(clazz.declaredFields)
   283 		}
   284 		fields.grep {
   285 			!it.synthetic &&
   286 					!Modifier.isStatic(it.modifiers) &&
   287 					!Modifier.isTransient(it.modifiers)
   288 		}
   289 	}
   290 
   291 	/**
   292 	 * @param obj
   293 	 * @return all object methods, including inherited
   294 	 */
   295 	protected Collection<Method> getMethods(obj) {
   296 		Class clazz = obj.getClass()
   297 		Collection<Method> methods = clazz.declaredMethods
   298 		while(clazz.superclass != null) {
   299 			clazz = clazz.superclass
   300 			methods.addAll(clazz.declaredMethods)
   301 		}
   302 		methods.grep {
   303 			!it.synthetic &&
   304 					!Modifier.isStatic(it.modifiers) &&
   305 					!Modifier.isTransient(it.modifiers)
   306 		}
   307 	}
   308 	
   309 	/**
   310 	 * For implement in successors
   311 	 *
   312 	 * @param entity
   313 	 */
   314 	protected void testCreatedEntity(Object entity) {
   315 		
   316 	}
   317 	
   318 	/**
   319 	 * For implement in successors
   320 	 *
   321 	 * @param entity
   322 	 */
   323 	protected void testUpdatedEntity(Object entity) {
   324 		
   325 	}
   326 	
   327 	/**
   328 	 * For implement in successors
   329 	 *
   330 	 * @param entity
   331 	 */
   332 	protected void testRemovedEntity(Object entity) {
   333 		
   334 	}
   335 
   336 	/**
   337 	 * @return an {@link AbstractEntity} successor
   338 	 */
   339 	abstract protected Class getEntityClass()
   340 }