RequiredDataAdviceTest.java

/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.aop;

import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Matchers;
import org.openmrs.BaseOpenmrsData;
import org.openmrs.BaseOpenmrsMetadata;
import org.openmrs.BaseOpenmrsObject;
import org.openmrs.Concept;
import org.openmrs.Location;
import org.openmrs.OpenmrsObject;
import org.openmrs.Person;
import org.openmrs.User;
import org.openmrs.annotation.AllowDirectAccess;
import org.openmrs.annotation.DisableHandlers;
import org.openmrs.api.APIException;
import org.openmrs.api.AdministrationService;
import org.openmrs.api.context.Context;
import org.openmrs.api.handler.BaseVoidHandler;
import org.openmrs.api.handler.OpenmrsObjectSaveHandler;
import org.openmrs.api.handler.RequiredDataHandler;
import org.openmrs.api.handler.RetireHandler;
import org.openmrs.api.handler.SaveHandler;
import org.openmrs.api.handler.UnretireHandler;
import org.openmrs.api.handler.UnvoidHandler;
import org.openmrs.api.handler.VoidHandler;
import org.openmrs.api.impl.ConceptServiceImpl;
import org.openmrs.test.Verifies;
import org.openmrs.util.Reflect;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

/**
 * Tests the {@link RequiredDataAdvice} class.
 */
@SuppressWarnings( { "unchecked" })
@RunWith(PowerMockRunner.class)
@PrepareForTest(Context.class)
public class RequiredDataAdviceTest {
	
	private SaveHandler saveHandler;
	
	private VoidHandler voidHandler;
	
	private RequiredDataAdvice requiredDataAdvice;
	
	@Before
	public void setUp() {
		this.requiredDataAdvice = new RequiredDataAdvice();
		
		PowerMockito.mockStatic(Context.class);
		
		saveHandler = mock(OpenmrsObjectSaveHandler.class);
		voidHandler = mock(BaseVoidHandler.class);
		
		when(Context.getRegisteredComponents(SaveHandler.class)).thenReturn(Arrays.asList(saveHandler));
		when(Context.getRegisteredComponents(VoidHandler.class)).thenReturn(Arrays.asList(voidHandler));
		AdministrationService administrationService = mock(AdministrationService.class);
		when(Context.getAdministrationService()).thenReturn(administrationService);
	}
	
	/**
	 * Class with a private field without getter
	 */
	private class MiniOpenmrsObject extends BaseOpenmrsObject {
		
		@AllowDirectAccess
		private List<Location> locations;
		
		public void setLocations(List<Location> locs) {
			this.locations = locs;
		}
		
		public Integer getId() {
			return null;
		}
		
		public void setId(Integer id) {
		}
	}
	
	/**
	 * @see {@link RequiredDataAdvice#getChildCollection(OpenmrsObject, Field)}
	 */
	@Test
	@Verifies(value = "should get value of given child collection on given field", method = "getChildCollection(OpenmrsObject,Field)")
	public void getChildCollection_shouldGetValueOfGivenChildCollectionOnGivenField() throws Exception {
		MiniOpenmrsObject oo = new MiniOpenmrsObject();
		List<Location> locs = new ArrayList<Location>();
		Location location = new Location(1);
		locs.add(location);
		oo.setLocations(locs);
		Collection<OpenmrsObject> fetchedLocations = RequiredDataAdvice.getChildCollection(oo, MiniOpenmrsObject.class
		        .getDeclaredField("locations"));
		Assert.assertTrue(fetchedLocations.contains(location));
	}
	
	/**
	 * @see RequiredDataAdvice#getChildCollection(OpenmrsObject,Field)
	 * @verifies should be able to get annotated private fields
	 */
	@Test
	public void getChildCollection_shouldShouldBeAbleToGetAnnotatedPrivateFields() throws Exception {
		MiniOpenmrsObject oo = new MiniOpenmrsObject();
		oo.setLocations(new ArrayList<Location>());
		Assert.assertNotNull(RequiredDataAdvice
		        .getChildCollection(oo, MiniOpenmrsObject.class.getDeclaredField("locations")));
	}
	
	/**
	 * Class that has a mismatched getter name instead of the correct getter name
	 */
	private class ClassWithBadGetter extends BaseOpenmrsObject {
		
		private Set<Location> locations;
		
		public Set<Location> getMyLocations() {
			return locations;
		}
		
		public void setMyLocations(Set<Location> locs) {
			this.locations = locs;
		}
		
		public Integer getId() {
			return null;
		}
		
		public void setId(Integer id) {
		}
	}
	
	/**
	 * @see {@link RequiredDataAdvice#getChildCollection(OpenmrsObject, Field)}
	 */
	@Test(expected = APIException.class)
	@Verifies(value = "should throw APIException if getter method not found", method = "getChildCollection(OpenmrsObject,Field)")
	public void getChildCollection_shouldThrowAPIExceptionIfGetterMethodNotFound() throws Exception {
		ClassWithBadGetter oo = new ClassWithBadGetter();
		oo.setMyLocations(new HashSet<Location>());
		RequiredDataAdvice.getChildCollection(oo, ClassWithBadGetter.class.getDeclaredField("locations"));
	}
	
	/**
	 * A class that has normal fields and non{@link OpenmrsObject} on it.
	 */
	@SuppressWarnings( { "UnusedDeclaration" })
	private class ClassWithOtherFields extends BaseOpenmrsObject {
		
		private Set<Locale> locales;
		
		private List<Map<String, String>> nestedGenericProperty;
		
		private Integer id;
		
		public List<Map<String, String>> getNestedGenericProperty() {
			return nestedGenericProperty;
		}
		
		public void setNestedGenericProperty(List<Map<String, String>> nestedGenericProperty) {
			this.nestedGenericProperty = nestedGenericProperty;
		}
		
		public Set<Locale> getLocales() {
			return locales;
		}
		
		public void setLocales(Set<Locale> locs) {
			this.locales = locs;
		}
		
		public Integer getId() {
			return id;
		}
		
		public void setId(Integer id) {
			this.id = id;
		}
	}
	
	/**
	 * @see {@link RequiredDataAdvice#isOpenmrsObjectCollection(Field)}
	 */
	@Test
	@Verifies(value = "should return false if field is collection of other objects", method = "isOpenmrsObjectCollection(Field)")
	public void isOpenmrsObjectCollection_shouldReturnFalseIfFieldIsCollectionOfOtherObjects() throws Exception {
		Assert.assertFalse(RequiredDataAdvice.isOpenmrsObjectCollection(ClassWithOtherFields.class
		        .getDeclaredField("locales")));
		List<String> list = new LinkedList<String>();
		list.add("Test");
		Assert.assertFalse(RequiredDataAdvice.isOpenmrsObjectCollection(list));
		
	}
	
	/**
	 * @see {@link RequiredDataAdvice#isOpenmrsObjectCollection(Field)}
	 */
	@Test
	@Verifies(value = "should return false if field is collection of parameterized type", method = "isOpenmrsObjectCollection(Field)")
	public void isOpenmrsObjectCollection_shouldReturnFalseIfFieldIsCollectionOfParameterizedType() throws Exception {
		Assert.assertFalse(RequiredDataAdvice.isOpenmrsObjectCollection(ClassWithOtherFields.class
		        .getDeclaredField("nestedGenericProperty")));
	}
	
	/**
	 * @see {@link RequiredDataAdvice#isOpenmrsObjectCollection(Field)}
	 */
	@Test
	@Verifies(value = "should return false if field is not a collection", method = "isOpenmrsObjectCollection(Field)")
	public void isOpenmrsObjectCollection_shouldReturnFalseIfFieldIsNotACollection() throws Exception {
		Assert.assertFalse(RequiredDataAdvice.isOpenmrsObjectCollection(ClassWithOtherFields.class.getDeclaredField("id")));
	}
	
	/**
	 * @see RequiredDataAdvice#isOpenmrsObjectCollection(Class<*>,Object)
	 */
	@Test
	@Verifies(value = "should return true if class is openmrsObject list", method = "isOpenmrsObjectCollection(Object)")
	public void isOpenmrsObjectCollection_shouldReturnTrueIfClassIsOpenmrsObjectList() throws Exception {
		List<Location> locations = new ArrayList<Location>();
		Location location = new Location();
		locations.add(location);
		Assert.assertTrue(RequiredDataAdvice.isOpenmrsObjectCollection(locations));
	}
	
	/**
	 * @see RequiredDataAdvice#isOpenmrsObjectCollection(Class<*>,Object)
	 */
	@Test
	@Verifies(value = "should return true if class is openmrsObject set", method = "isOpenmrsObjectCollection(Object)")
	public void isOpenmrsObjectCollection_shouldReturnTrueIfClassIsOpenmrsObjectSet() throws Exception {
		Set<Location> locations = new HashSet<Location>();
		Location location = new Location();
		locations.add(location);
		Assert.assertTrue(RequiredDataAdvice.isOpenmrsObjectCollection(locations));
	}
	
	/**
	 * @see RequiredDataAdvice#isOpenmrsObjectCollection(Class<*>,Object)
	 */
	@Test
	@Verifies(value = "should return false if collection is empty regardless of type held", method = "isOpenmrsObjectCollection(Object)")
	public void isOpenmrsObjectCollection_shouldReturnFalseIfCollectionIsEmptyRegardlessOfTypeHeld() throws Exception {
		Set<Location> locations = new HashSet<Location>();
		Assert.assertFalse(RequiredDataAdvice.isOpenmrsObjectCollection(locations));
	}
	
	/**
	 * Some OpenmrsData with a collection annotated with @DisableHandlers
	 */
	private class ClassWithDisableHandlersAnnotation extends BaseOpenmrsData {
		
		@DisableHandlers(handlerTypes = { VoidHandler.class, SaveHandler.class })
		private List<Person> persons;
		
		private List<Person> notAnnotatedPersons;
		
		public List<Person> getPersons() {
			return persons;
		}
		
		public void setPersons(List<Person> persons) {
			this.persons = persons;
		}
		
		public List<Person> getNotAnnotatedPersons() {
			return notAnnotatedPersons;
		}
		
		public void setNotAnnotatedPersons(List<Person> notAnnotatedPersons) {
			this.notAnnotatedPersons = notAnnotatedPersons;
		}
		
		public Integer getId() {
			return null;
		}
		
		public void setId(Integer id) {
		}
	}
	
	/**
	 * @see RequiredDataAdvice#isHandlerMarkedAsDisabled(Class, java.lang.reflect.Field)
	 */
	@Test
	public void isHandlerMarkedAsDisabled_shouldReturnTrueIfHandlerDisabled() {
		
		Field persons = null;
		
		for (Field field : Reflect.getAllFields(ClassWithDisableHandlersAnnotation.class)) {
			if (field.getName().equals("persons")) {
				persons = field;
			}
		}
		
		Assert.assertTrue(RequiredDataAdvice.isHandlerMarkedAsDisabled(SaveHandler.class, persons));
		Assert.assertTrue(RequiredDataAdvice.isHandlerMarkedAsDisabled(VoidHandler.class, persons));
	}
	
	/**
	 * @see RequiredDataAdvice#isHandlerMarkedAsDisabled(Class, java.lang.reflect.Field)
	 */
	@Test
	public void isHandlerMarkedAsDisabled_shouldReturnFalseIfHandlerNotDisabled() {
		
		Field persons = null;
		
		for (Field field : Reflect.getAllFields(ClassWithDisableHandlersAnnotation.class)) {
			if (field.getName().equals("persons")) {
				persons = field;
			}
		}
		
		Assert.assertFalse(RequiredDataAdvice.isHandlerMarkedAsDisabled(RetireHandler.class, persons));
	}
	
	/**
	 * @see RequiredDataAdvice#isHandlerMarkedAsDisabled(Class, java.lang.reflect.Field)
	 */
	@Test
	public void isHandlerMarkedAsDisabled_shouldReturnFalseIfFieldNotAnnotated() {
		
		Field persons = null;
		
		for (Field field : Reflect.getAllFields(ClassWithDisableHandlersAnnotation.class)) {
			if (field.getName().equals("notAnnotatedPersons")) {
				persons = field;
			}
		}
		
		Assert.assertFalse(RequiredDataAdvice.isHandlerMarkedAsDisabled(RetireHandler.class, persons));
	}
	
	/**
	 * Some OpenmrsData with a collection annotated with @DisableHandlers
	 */
	private class ClassWithDisableHandlersAnnotationForSupertype extends BaseOpenmrsData {
		
		// this should disable all handlers
		@DisableHandlers(handlerTypes = { RequiredDataHandler.class })
		private List<Person> persons;
		
		private List<Person> notAnnotatedPersons;
		
		public List<Person> getPersons() {
			return persons;
		}
		
		public void setPersons(List<Person> persons) {
			this.persons = persons;
		}
		
		public List<Person> getNotAnnotatedPersons() {
			return notAnnotatedPersons;
		}
		
		public void setNotAnnotatedPersons(List<Person> notAnnotatedPersons) {
			this.notAnnotatedPersons = notAnnotatedPersons;
		}
		
		public Integer getId() {
			return null;
		}
		
		public void setId(Integer id) {
		}
	}
	
	/**
	 * @see RequiredDataAdvice#isHandlerMarkedAsDisabled(Class, java.lang.reflect.Field)
	 */
	@Test
	public void isHandlerMarkedAsDisabled_shouldReturnTrueIfSupertypeHandlerDisabled() {
		
		Field persons = null;
		
		for (Field field : Reflect.getAllFields(ClassWithDisableHandlersAnnotationForSupertype.class)) {
			if (field.getName().equals("persons")) {
				persons = field;
			}
		}
		
		// all the handlers should be marked as disabled, since the supertype (RequiredDataHandler) was specified to be ignored
		Assert.assertTrue(RequiredDataAdvice.isHandlerMarkedAsDisabled(SaveHandler.class, persons));
		Assert.assertTrue(RequiredDataAdvice.isHandlerMarkedAsDisabled(VoidHandler.class, persons));
		Assert.assertTrue(RequiredDataAdvice.isHandlerMarkedAsDisabled(UnvoidHandler.class, persons));
		Assert.assertTrue(RequiredDataAdvice.isHandlerMarkedAsDisabled(RetireHandler.class, persons));
		Assert.assertTrue(RequiredDataAdvice.isHandlerMarkedAsDisabled(UnretireHandler.class, persons));
	}
	
	/**
	 * Some OpenmrsMetadata with a collection annotated with @DisableHandlers
	 */
	private class MetadataClassWithDisableHandlersAnnotation extends BaseOpenmrsMetadata {
		
		@DisableHandlers(handlerTypes = { UnretireHandler.class })
		private List<Concept> concepts;
		
		public List<Concept> getConcepts() {
			return concepts;
		}
		
		public void setConcepts(List<Concept> concepts) {
			this.concepts = concepts;
		}
		
		public Integer getId() {
			return null;
		}
		
		public void setId(Integer id) {
		}
	}
	
	/**
	 * @see RequiredDataAdvice#isHandlerMarkedAsDisabled(Class, java.lang.reflect.Field)
	 */
	@Test
	public void isHandlerMarkedAsDisabled_shouldReturnTrueIfHandlerDisabledOnMetadata() {
		
		Field persons = null;
		
		for (Field field : Reflect.getAllFields(MetadataClassWithDisableHandlersAnnotation.class)) {
			if (field.getName().equals("concepts")) {
				persons = field;
			}
		}
		
		Assert.assertTrue(RequiredDataAdvice.isHandlerMarkedAsDisabled(UnretireHandler.class, persons));
	}
	
	/**
	 * @see RequiredDataAdvice#isHandlerMarkedAsDisabled(Class, java.lang.reflect.Field)
	 */
	@Test
	public void isHandlerMarkedAsDisabled_shouldReturnFalseIfHandlerNotDisabledOnMetatdata() {
		
		Field persons = null;
		
		for (Field field : Reflect.getAllFields(MetadataClassWithDisableHandlersAnnotation.class)) {
			if (field.getName().equals("concepts")) {
				persons = field;
			}
		}
		
		Assert.assertFalse(RequiredDataAdvice.isHandlerMarkedAsDisabled(RetireHandler.class, persons));
	}
	
	/**
	 * @see {@link RequiredDataAdvice#before(Method, null, Object)}
	 */
	@Test
	@Verifies(value = "should not fail on update method with no arguments", method = "before(Method,null,Object)")
	public void before_shouldNotFailOnUpdateMethodWithNoArguments() throws Throwable {
		Method method = ConceptServiceImpl.class.getMethod("updateConceptWords", (Class[]) null);
		requiredDataAdvice.before(method, null, new ConceptServiceImpl());
		requiredDataAdvice.before(method, new Object[] {}, new ConceptServiceImpl());
	}
	
	@Test
	public void before_shouldNotCallHandlerOnSaveWithNullOrNoArguments() throws Throwable {
		
		Method m = WithAppropriatelyNamedMethod.class.getMethod("saveSomeOpenmrsData", SomeOpenmrsData.class);
		SomeOpenmrsData openmrsObject = new SomeOpenmrsData();
		requiredDataAdvice.before(m, null, new WithAppropriatelyNamedMethod());
		requiredDataAdvice.before(m, new Object[] {}, new WithAppropriatelyNamedMethod());
		verify(saveHandler, never()).handle(eq(openmrsObject), Matchers.<User> anyObject(), Matchers.<Date> anyObject(),
		    anyString());
	}
	
	@Test
	public void before_shouldCallHandlerOnSaveWithOpenmrsObjectArgument() throws Throwable {
		
		Method m = WithAppropriatelyNamedMethod.class.getMethod("saveSomeOpenmrsData", SomeOpenmrsData.class);
		SomeOpenmrsData openmrsObject = new SomeOpenmrsData();
		requiredDataAdvice.before(m, new Object[] { openmrsObject }, new WithAppropriatelyNamedMethod());
		verify(saveHandler, times(1)).handle(eq(openmrsObject), Matchers.<User> anyObject(), Matchers.<Date> anyObject(),
		    anyString());
	}
	
	@Test
	public void before_shouldNotCallHandlerOnSaveMethodNameNotMatchingDomainObject() throws Throwable {
		
		Method m = WithAppropriatelyNamedMethod.class.getMethod("saveSomeOpenmrsDataButNotReally", SomeOpenmrsData.class);
		SomeOpenmrsData openmrsObject = new SomeOpenmrsData();
		requiredDataAdvice.before(m, new Object[] { openmrsObject }, new WithAppropriatelyNamedMethod());
		verify(saveHandler, never()).handle(eq(openmrsObject), Matchers.<User> anyObject(), Matchers.<Date> anyObject(),
		    anyString());
	}
	
	@Test
	public void before_shouldCallHandlerOnSaveMethodNameWithCollectionArgument() throws Throwable {
		
		Method m = WithAppropriatelyNamedMethod.class.getMethod("saveSomeOpenmrsDatas", List.class);
		List<SomeOpenmrsData> openmrsObjects = Arrays.asList(new SomeOpenmrsData(), new SomeOpenmrsData());
		requiredDataAdvice.before(m, new Object[] { openmrsObjects }, new WithAppropriatelyNamedMethod());
		verify(saveHandler, times(2)).handle(Matchers.<SomeOpenmrsData> anyObject(), Matchers.<User> anyObject(),
		    Matchers.<Date> anyObject(), anyString());
	}
	
	@Test
	public void before_shouldNotCallHandlerOnVoidWithNullOrNoArguments() throws Throwable {
		
		Method m = WithAppropriatelyNamedMethod.class.getMethod("voidSomeOpenmrsData", SomeOpenmrsData.class);
		SomeOpenmrsData openmrsObject = new SomeOpenmrsData();
		requiredDataAdvice.before(m, null, new WithAppropriatelyNamedMethod());
		requiredDataAdvice.before(m, new Object[] {}, new WithAppropriatelyNamedMethod());
		verify(voidHandler, never()).handle(eq(openmrsObject), Matchers.<User> anyObject(), Matchers.<Date> anyObject(),
		    anyString());
	}
	
	@Test
	public void before_shouldCallHandlerOnVoidMethodNameMatchingDomainObject() throws Throwable {
		
		Method m = WithAppropriatelyNamedMethod.class.getMethod("voidSomeOpenmrsData", SomeOpenmrsData.class);
		SomeOpenmrsData openmrsObject = new SomeOpenmrsData();
		requiredDataAdvice.before(m, new Object[] { openmrsObject, "void reason" }, new WithAppropriatelyNamedMethod());
		verify(voidHandler, times(1)).handle(eq(openmrsObject), Matchers.<User> anyObject(), Matchers.<Date> anyObject(),
		    anyString());
	}
	
	@Test
	public void before_shouldCallHandlerOnVoidMethodWhenDomainObjectIsAssignableFromMethodNameObject() throws Throwable {
		
		Method m = WithAppropriatelyNamedMethod.class.getMethod("voidSomeOpenmrsData", SomeOpenmrsData.class);
		SomeOpenmrsData openmrsObjectSubClass = new SomeOpenmrsDataSubClass();
		requiredDataAdvice.before(m, new Object[] { openmrsObjectSubClass, "void reason" },
		    new WithAppropriatelyNamedMethod());
		verify(voidHandler, times(1)).handle(eq(openmrsObjectSubClass), Matchers.<User> anyObject(),
		    Matchers.<Date> anyObject(), anyString());
	}
	
	@Test
	public void before_shouldNotCallHandlerOnVoidMethodNameNotMatchingDomainObject() throws Throwable {
		
		Method m = WithAppropriatelyNamedMethod.class.getMethod("voidSomeOpenmrsDataButNotReally", SomeOpenmrsData.class);
		SomeOpenmrsData openmrsObject = new SomeOpenmrsData();
		requiredDataAdvice.before(m, new Object[] { openmrsObject }, new WithAppropriatelyNamedMethod());
		verify(voidHandler, never()).handle(eq(openmrsObject), Matchers.<User> anyObject(), Matchers.<Date> anyObject(),
		    anyString());
	}
	
	@Test
	public void before_shouldNotCallHandlersAnnotatedAsDisabled() throws Throwable {
		
		Method m = WithAppropriatelyNamedMethod.class.getMethod("voidClassWithDisableHandlersAnnotation",
		    ClassWithDisableHandlersAnnotation.class);
		
		ClassWithDisableHandlersAnnotation openmrsObject = new ClassWithDisableHandlersAnnotation();
		
		// create a couple locations and associate them with this openmrsObject
		List<Person> persons = new ArrayList<Person>();
		Person person = new Person();
		persons.add(person);
		openmrsObject.setPersons(persons);
		
		requiredDataAdvice.before(m, new Object[] { openmrsObject, "void reason" }, new WithAppropriatelyNamedMethod());
		
		// verify that the handle method was never called on this object
		verify(voidHandler, never()).handle(eq(person), Matchers.<User> anyObject(), Matchers.<Date> anyObject(),
		    anyString());
		
	}
	
	@Test
	public void before_shouldCallHandlersNotAnnotatedAsDisabled() throws Throwable {
		
		Method m = WithAppropriatelyNamedMethod.class.getMethod("voidClassWithDisableHandlersAnnotation",
		    ClassWithDisableHandlersAnnotation.class);
		
		ClassWithDisableHandlersAnnotation openmrsObject = new ClassWithDisableHandlersAnnotation();
		
		// create a couple locations and associate them with this openmrsObject
		List<Person> persons = new ArrayList<Person>();
		Person person = new Person();
		persons.add(person);
		openmrsObject.setNotAnnotatedPersons(persons);
		
		requiredDataAdvice.before(m, new Object[] { openmrsObject, "void reason" }, new WithAppropriatelyNamedMethod());
		
		// verify that the handle method was called on this object
		verify(voidHandler, times(1)).handle(eq(person), Matchers.<User> anyObject(), Matchers.<Date> anyObject(),
		    anyString());
		
	}
	
	class SomeOpenmrsData extends BaseOpenmrsData {
		
		@Override
		public Integer getId() {
			return null;
		}
		
		@Override
		public void setId(Integer id) {
		}
	}
	
	public class SomeOpenmrsDataSubClass extends SomeOpenmrsData {

	}
	
	@SuppressWarnings( { "UnusedDeclaration" })
	public class WithAppropriatelyNamedMethod {
		
		public void saveSomeOpenmrsData(SomeOpenmrsData oo) {
		}
		
		public void saveSomeOpenmrsData(SomeOpenmrsData oo, String reason) {
		}
		
		public void saveSomeOpenmrsDatas(List<SomeOpenmrsData> list) {
		}
		
		public void saveSomeOpenmrsDataButNotReally(SomeOpenmrsData oo) {
		}
		
		public void voidSomeOpenmrsData(SomeOpenmrsData oo) {
		}
		
		public void voidSomeOpenmrsDataButNotReally(SomeOpenmrsData oo) {
		}
		
		public void voidClassWithDisableHandlersAnnotation(ClassWithDisableHandlersAnnotation oo) {
		}
	}
	
}