Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -537,23 +537,32 @@ public PropertyPath getPropertyPath(Class<?> klass, String path) {
}

public static <T> T safeInvoke(Object object, Method method) {
// Resolve the fallback up front: ReflectionUtils.invokeMethod logs a type mismatch at ERROR.
if (object != null && method != null && !method.getDeclaringClass().isInstance(object)) {
Method fallback = getInterfaceMethod(method);
if (fallback != null && fallback.getDeclaringClass().isInstance(object)) {
return ReflectionUtils.invokeMethod(object, fallback);
}
}
try {
return ReflectionUtils.invokeMethod(object, method);
} catch (Exception e) {
Class<?> interfaceClass = INTERFACE_TYPE_BY_BASE_TYPE.get(method.getDeclaringClass());
if (interfaceClass != null) {
try {
Method fallback = interfaceClass.getMethod(method.getName());
return ReflectionUtils.invokeMethod(object, fallback);
} catch (Exception ex) {
throw new RuntimeException(
"Failed to invoke fallback method for " + method.getName(), ex);
}
}
throw new RuntimeException("Failed to invoke method " + method.getName(), e);
}
}

private static Method getInterfaceMethod(Method method) {
Class<?> interfaceClass = INTERFACE_TYPE_BY_BASE_TYPE.get(method.getDeclaringClass());
if (interfaceClass == null) {
return null;
}
try {
return interfaceClass.getMethod(method.getName());
} catch (NoSuchMethodException e) {
return null;
}
}

private boolean isFilterByAttributeId(Property curProperty, String propertyName) {
return curProperty == null && CodeGenerator.isValidUid(propertyName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,50 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;

import java.lang.reflect.Method;
import org.hisp.dhis.category.CategoryOption;
import org.hisp.dhis.common.BaseDimensionalItemObject;
import org.hisp.dhis.common.BaseDimensionalObject;
import org.hisp.dhis.common.BaseNameableObject;
import org.hisp.dhis.common.DimensionalObject;
import org.hisp.dhis.dataelement.DataElement;
import org.hisp.dhis.legend.Legend;
import org.hisp.dhis.system.util.ReflectionUtils;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;

class DefaultSchemaServiceSafeInvokeTest {

@Test
void testInterfaceFallbackDoesNotInvokeBaseMethod() throws Exception {
Method getterMethod = BaseDimensionalItemObject.class.getMethod("getDimensionItem");

CategoryOption categoryOption = new CategoryOption("Test Option");
categoryOption.setUid("CatOptUid01");

try (MockedStatic<ReflectionUtils> reflectionUtils =
mockStatic(ReflectionUtils.class, CALLS_REAL_METHODS)) {
String result = DefaultSchemaService.safeInvoke(categoryOption, getterMethod);

assertEquals("CatOptUid01", result);
reflectionUtils.verify(
() -> ReflectionUtils.invokeMethod(categoryOption, getterMethod), never());
}
}

@Test
void testNullMethodReturnsNull() {
CategoryOption categoryOption = new CategoryOption("Test Option");

assertNull(DefaultSchemaService.safeInvoke(categoryOption, null));
}

@Test
void testNameNotNull() throws Exception {
Method getterMethod = BaseNameableObject.class.getMethod("getDisplayName");
Expand Down Expand Up @@ -84,20 +116,20 @@ void testMethodNotFound() throws Exception {

Object object = new Object();

assertThrows(
RuntimeException.class,
() -> DefaultSchemaService.safeInvoke(object, getterMethod),
"object is not an instance of declaring class");
RuntimeException exception =
assertThrows(
RuntimeException.class, () -> DefaultSchemaService.safeInvoke(object, getterMethod));
assertEquals("Failed to invoke method getDisplayName", exception.getMessage());
}

@Test
void testObjectDoesNotHaveProperty() throws Exception {
Method getterMethod = BaseNameableObject.class.getMethod("getDisplayShortName");
Legend legend = new Legend();
legend.setName("test");
assertThrows(
RuntimeException.class,
() -> DefaultSchemaService.safeInvoke(legend, getterMethod),
"object is not an instance of declaring class");
RuntimeException exception =
assertThrows(
RuntimeException.class, () -> DefaultSchemaService.safeInvoke(legend, getterMethod));
assertEquals("Failed to invoke method getDisplayShortName", exception.getMessage());
}
}
Loading