package rocks.inspectit.agent.java.config.impl; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.mockito.Mockito; import org.slf4j.LoggerFactory; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import rocks.inspectit.agent.java.AbstractLogSupport; import rocks.inspectit.agent.java.config.PropertyAccessException; import rocks.inspectit.shared.all.communication.data.ParameterContentData; import rocks.inspectit.shared.all.communication.data.ParameterContentType; import rocks.inspectit.shared.all.instrumentation.config.impl.PropertyPath; import rocks.inspectit.shared.all.instrumentation.config.impl.PropertyPathStart; @SuppressWarnings("PMD") public class PropertyAccessorTest extends AbstractLogSupport { private PropertyAccessor propertyAccessor; private Object resultValueMock; @BeforeClass public void initTestClass() { propertyAccessor = new PropertyAccessor(); propertyAccessor.log = LoggerFactory.getLogger(PropertyAccessor.class); } @BeforeMethod public void initialize() { resultValueMock = Mockito.mock(Object.class); } @Test public void readFieldPersonObject() throws PropertyAccessException { Person person = new Person(); person.setName("Dirk"); PropertyPathStart start = new PropertyPathStart(); start.setName("this"); start.setContentType(ParameterContentType.FIELD); String result = propertyAccessor.getPropertyContent(start, person, null, resultValueMock); assertThat(result, is("Dirk")); Mockito.verifyZeroInteractions(resultValueMock); } @Test public void readFieldPersonName() throws PropertyAccessException { Person person = new Person(); person.setName("Dirk"); PropertyPathStart start = new PropertyPathStart(); start.setName("this"); start.setContentType(ParameterContentType.FIELD); PropertyPath path = new PropertyPath(); path.setName("name"); start.setPathToContinue(path); String result = propertyAccessor.getPropertyContent(start, person, null, resultValueMock); assertThat(result, is("Dirk")); Mockito.verifyZeroInteractions(resultValueMock); } @Test(expectedExceptions = { PropertyAccessException.class }) public void nullStartPath() throws PropertyAccessException { propertyAccessor.getPropertyContent(null, null, null, null); } @Test(expectedExceptions = { PropertyAccessException.class }) public void nullNeededClassObjectForFieldAccess() throws PropertyAccessException { PropertyPathStart start = new PropertyPathStart(); start.setName("this"); start.setContentType(ParameterContentType.FIELD); propertyAccessor.getPropertyContent(start, null, null, resultValueMock); Mockito.verifyZeroInteractions(resultValueMock); } /* * We no longer throw exceptions in the case that the return value is null as this would * completely remove the property accessor and this is not the desired behaviour. */ @Test public void nullNeededResultObjectForResultAccess() throws PropertyAccessException { PropertyPathStart start = new PropertyPathStart(); start.setName("this"); start.setContentType(ParameterContentType.RETURN); String result = propertyAccessor.getPropertyContent(start, null, null, null); assertThat(result, is(equalTo("null"))); } @Test(expectedExceptions = { PropertyAccessException.class }) public void nullNeededParameterObject() throws PropertyAccessException { PropertyPathStart start = new PropertyPathStart(); start.setName("name"); start.setSignaturePosition(0); start.setContentType(ParameterContentType.PARAM); propertyAccessor.getPropertyContent(start, null, null, resultValueMock); Mockito.verifyZeroInteractions(resultValueMock); } @Test(expectedExceptions = { PropertyAccessException.class }) public void parameterArrayOutOfRange() throws PropertyAccessException { PropertyPathStart start = new PropertyPathStart(); start.setName("name"); start.setSignaturePosition(0); start.setContentType(ParameterContentType.PARAM); propertyAccessor.getPropertyContent(start, null, new Object[0], resultValueMock); Mockito.verifyZeroInteractions(resultValueMock); } @Test(expectedExceptions = { PropertyAccessException.class }) public void missingContentType() throws PropertyAccessException { PropertyPathStart start = new PropertyPathStart(); start.setName("name"); start.setSignaturePosition(0); propertyAccessor.getPropertyContent(start, null, null, null); } @Test(expectedExceptions = { PropertyAccessException.class }) public void analyzePersonAccessException() throws PropertyAccessException { Person person = new Person(); person.setName("Dirk"); PropertyPathStart start = new PropertyPathStart(); start.setName("this"); start.setContentType(ParameterContentType.FIELD); PropertyPath path = new PropertyPath(); path.setName("surname"); start.setPathToContinue(path); // name != surname -> exception propertyAccessor.getPropertyContent(start, person, null, resultValueMock); Mockito.verifyZeroInteractions(resultValueMock); } @Test public void analyzePersonParameter() throws PropertyAccessException { // create initial object relation Person peter = new Person("Peter"); Person juergen = new Person("Juergen"); peter.setChild(juergen); Person hans = new Person("Hans"); juergen.setChild(hans); Person thomas = new Person("Thomas"); hans.setChild(thomas); Person michael = new Person("Michael"); thomas.setChild(michael); // create the test path PropertyPathStart start = new PropertyPathStart(); start.setName("name"); start.setSignaturePosition(1); start.setContentType(ParameterContentType.PARAM); PropertyPath pathOne = new PropertyPath("child"); start.setPathToContinue(pathOne); PropertyPath pathTwo = new PropertyPath("child"); pathOne.setPathToContinue(pathTwo); PropertyPath pathThree = new PropertyPath("child"); pathTwo.setPathToContinue(pathThree); PropertyPath pathFour = new PropertyPath("child"); pathThree.setPathToContinue(pathFour); // set the parameter array Object[] parameters = { null, peter }; String result = propertyAccessor.getPropertyContent(start, new Object(), parameters, resultValueMock); assertThat(result, is("Michael")); Mockito.verifyZeroInteractions(resultValueMock); } @Test public void removePropertyAccessorFromList() { // create initial object relation Person peter = new Person("Peter"); Person juergen = new Person("Hans"); peter.setChild(juergen); // CopyOnWriteArrayList for thread safety List<PropertyPathStart> propertyAccessorList = new CopyOnWriteArrayList<PropertyPathStart>(); // valid PropertyPathStart start = new PropertyPathStart(); start.setName("name"); start.setSignaturePosition(0); start.setContentType(ParameterContentType.PARAM); PropertyPath pathOne = new PropertyPath("child"); start.setPathToContinue(pathOne); propertyAccessorList.add(start); // not valid start = new PropertyPathStart(); start.setName("this"); start.setContentType(ParameterContentType.FIELD); pathOne = new PropertyPath("notValid"); start.setPathToContinue(pathOne); propertyAccessorList.add(start); // not valid as the second parameter will be null start = new PropertyPathStart(); start.setName("name"); start.setSignaturePosition(1); start.setContentType(ParameterContentType.PARAM); pathOne = new PropertyPath("child"); start.setPathToContinue(pathOne); propertyAccessorList.add(start); assertThat(propertyAccessorList, hasSize(3)); List<ParameterContentData> parameterContentList = propertyAccessor.getParameterContentData(propertyAccessorList, peter, new Object[] { peter }, resultValueMock); // size should be reduced to one assertThat(propertyAccessorList, hasSize(1)); // so is the size of the parameter content assertThat(parameterContentList, is(notNullValue())); assertThat(parameterContentList, hasSize(1)); // changed due to xstream, the ' at the beginning will be always removed // if displayed to the end-user. assertThat(parameterContentList.get(0).getContent(), is("Hans")); assertThat(parameterContentList.get(0).getSignaturePosition(), is(0)); assertThat(parameterContentList.get(0).getName(), is("name")); } @Test public void invokeArrayLengthMethod() throws PropertyAccessException { // create initial object relation Person peter = new Person("Peter"); String[] foreNames = new String[] { "Klaus", "Uwe" }; peter.setForeNames(foreNames); PropertyPathStart start = new PropertyPathStart(); start.setName("this"); start.setContentType(ParameterContentType.FIELD); PropertyPath path = new PropertyPath(); path.setName("foreNames"); start.setPathToContinue(path); PropertyPath path2 = new PropertyPath(); path2.setName("length()"); path.setPathToContinue(path2); String result = propertyAccessor.getPropertyContent(start, peter, null, resultValueMock); assertThat(Integer.parseInt(result), is(2)); Mockito.verifyZeroInteractions(resultValueMock); } @Test(expectedExceptions = { PropertyAccessException.class }) public void invokeArrayLengthMethodOnNonArray() throws PropertyAccessException { // create initial object relation Person peter = new Person("Peter"); String[] foreNames = new String[] { "Klaus", "Uwe" }; peter.setForeNames(foreNames); PropertyPathStart start = new PropertyPathStart(); start.setName("this"); start.setContentType(ParameterContentType.FIELD); PropertyPath path = new PropertyPath(); path.setName("name"); start.setPathToContinue(path); PropertyPath path2 = new PropertyPath(); path2.setName("length()"); path.setPathToContinue(path2); // must result in an Exception as name is not an array propertyAccessor.getPropertyContent(start, peter, null, resultValueMock); Mockito.verifyZeroInteractions(resultValueMock); } @Test public void invokeListSizeMethod() throws PropertyAccessException { // create initial object relation Person peter = new Person("Peter"); List<String> foreNames = new ArrayList<String>(); foreNames.add("blub"); foreNames.add("blub2"); foreNames.add("blub3"); peter.setForeNamesAsList(foreNames); PropertyPathStart start = new PropertyPathStart(); start.setName("this"); start.setContentType(ParameterContentType.FIELD); PropertyPath path = new PropertyPath(); path.setName("foreNamesAsList"); start.setPathToContinue(path); PropertyPath path2 = new PropertyPath(); path2.setName("size()"); path.setPathToContinue(path2); String result = propertyAccessor.getPropertyContent(start, peter, null, resultValueMock); assertThat(Integer.parseInt(result), is(3)); Mockito.verifyZeroInteractions(resultValueMock); } @Test public void analyzeReturnValueString() throws PropertyAccessException { // valid PropertyPathStart start = new PropertyPathStart(); start.setName("returnName"); start.setContentType(ParameterContentType.RETURN); String result = propertyAccessor.getPropertyContent(start, null, null, "Peter"); assertThat(result, is("Peter")); } @Test public void analyzeReturnValueVoidMethod() throws PropertyAccessException { // create initial object relation Person peter = new Person("Peter"); // valid PropertyPathStart start = new PropertyPathStart(); start.setName("setName"); start.setContentType(ParameterContentType.RETURN); String result = propertyAccessor.getPropertyContent(start, null, null, peter); assertThat(result, is("Peter")); } @Test public void analyzeReturnValueObject() throws PropertyAccessException { // create initial object relation Person peter = new Person("Peter"); Person juergen = new Person("Hans"); peter.setChild(juergen); List<PropertyPathStart> propertyAccessorList = new ArrayList<PropertyPathStart>(); // valid PropertyPathStart start = new PropertyPathStart(); start.setName("return"); start.setContentType(ParameterContentType.RETURN); PropertyPath pathOne = new PropertyPath("child"); start.setPathToContinue(pathOne); propertyAccessorList.add(start); String result = propertyAccessor.getPropertyContent(start, null, null, peter); assertThat(result, is("Hans")); } @Test(expectedExceptions = { PropertyAccessException.class }) public void invokeForbiddenMethod() throws PropertyAccessException { // create initial object relation Person peter = new Person("Peter"); PropertyPathStart start = new PropertyPathStart(); start.setName("this"); start.setContentType(ParameterContentType.FIELD); PropertyPath path = new PropertyPath(); path.setName("getName()"); start.setPathToContinue(path); propertyAccessor.getPropertyContent(start, peter, null, resultValueMock); Mockito.verifyZeroInteractions(resultValueMock); } @Test public void concurentAccessOnPropertyAccessorList() { // create initial object relation Person peter = new Person("Peter"); Person juergen = new Person("Jurgen"); peter.setChild(juergen); // CopyOnWriteArrayList for thread safety. It's the same as the propertyAccessorList in // AbstractSensorConfig, as its operated on. In normal ArrayList, Fail-fast iterators throw // ConcurrentModificationException on a best-effort basis. // So it's not guaranteed, that there will be an exception on concurrent access, but the // results will be inconsistent. List<PropertyPathStart> propertyAccessorList = new CopyOnWriteArrayList<PropertyPathStart>(); // valid PropertyPathStart start = new PropertyPathStart(); start.setName("name"); start.setSignaturePosition(0); start.setContentType(ParameterContentType.PARAM); PropertyPath pathOne = new PropertyPath("child"); start.setPathToContinue(pathOne); propertyAccessorList.add(start); // not valid start = new PropertyPathStart(); start.setName("this"); start.setContentType(ParameterContentType.FIELD); pathOne = new PropertyPath("notValid"); start.setPathToContinue(pathOne); propertyAccessorList.add(start); // not valid as the second parameter will be null start = new PropertyPathStart(); start.setName("name"); start.setSignaturePosition(1); start.setContentType(ParameterContentType.PARAM); pathOne = new PropertyPath("child"); start.setPathToContinue(pathOne); propertyAccessorList.add(start); assertThat(propertyAccessorList, hasSize(3)); // Creating concurrent access Iterator<PropertyPathStart> i = propertyAccessorList.iterator(); // Access via iterator i.next(); // Direct access List<ParameterContentData> parameterContentList = propertyAccessor.getParameterContentData(propertyAccessorList, peter, new Object[] { peter }, null); // Double check results, in case of missing exception // size should be reduced to one assertThat(propertyAccessorList, hasSize(1)); // so is the size of the parameter content assertThat(parameterContentList, is(notNullValue())); assertThat(parameterContentList, hasSize(1)); // changed due to xstream, the ' at the beginning will be always removed // if displayed to the end-user. assertThat(parameterContentList.get(0).getContent(), is("Jurgen")); assertThat(parameterContentList.get(0).getSignaturePosition(), is(0)); assertThat(parameterContentList.get(0).getName(), is("name")); } @SuppressWarnings("unused") private static class Person { private String name; private Person child; private String[] foreNames; private List<String> foreNamesAsList; public Person() { } public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Person getChild() { return child; } public void setChild(Person child) { this.child = child; } public String[] getForeNames() { return foreNames; } public void setForeNames(String[] foreNames) { this.foreNames = foreNames; } public List<String> getForeNamesAsList() { return foreNamesAsList; } public void setForeNamesAsList(List<String> foreNamesAsList) { this.foreNamesAsList = foreNamesAsList; } @Override public String toString() { return name; } } }