diff --git a/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java b/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java index f828eb00..39f511a5 100644 --- a/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java +++ b/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,17 +21,32 @@ import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.springframework.beans.PropertyValue; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.Order; +import org.springframework.core.annotation.OrderUtils; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -41,9 +56,15 @@ import org.springframework.util.StringUtils; * @author John Blum * @see java.lang.Class * @see java.lang.Object + * @see java.util.function.Function + * @see java.util.stream.Stream * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.FactoryBean * @see org.springframework.beans.factory.config.BeanDefinition * @see org.springframework.beans.factory.config.RuntimeBeanReference + * @see org.springframework.core.Ordered + * @see org.springframework.core.annotation.AnnotationAwareOrderComparator + * @see org.springframework.core.annotation.Order * @since 1.8.0 */ @SuppressWarnings("unused") @@ -57,26 +78,178 @@ public abstract class SpringUtils { * @param beanName {@link String name} of the bean. * @param beanType {@link Class type} of the bean. * @return a boolean value indicating whether the {@link BeanFactory Spring container} contains a bean - * matching by both {@link String name} as well as {@link Class type}. - * @see BeanFactory - * @see Class - * @see String + * matching by both {@link String name} and {@link Class type}. + * @see org.springframework.beans.factory.BeanFactory + * @see java.lang.Class + * @see java.lang.String */ - public static boolean isMatchingBean(BeanFactory beanFactory, String beanName, Class beanType) { + public static boolean isMatchingBean(@NonNull BeanFactory beanFactory, String beanName, Class beanType) { return beanFactory.containsBean(beanName) && beanFactory.isTypeMatch(beanName, beanType); } - public static BeanDefinition addDependsOn(BeanDefinition beanDefinition, String... beanNames) { + /** + * Adds an array of bean dependencies (by name) to the given {@link BeanDefinition}. + * + * @param beanDefinition {@link BeanDefinition} to add the bean dependencies to. + * @param beanNames {@link String} array containing names of beans to which the {@link BeanDefinition} + * has a dependency. + * @return the given {@link BeanDefinition}. + * @see org.springframework.beans.factory.config.BeanDefinition + */ + @NonNull + public static BeanDefinition addDependsOn(@NonNull BeanDefinition beanDefinition, @Nullable String... beanNames) { List dependsOnList = new ArrayList<>(); - Collections.addAll(dependsOnList, nullSafeArray(beanDefinition.getDependsOn(), String.class)); + Collections.addAll(dependsOnList, ArrayUtils.nullSafeArray(beanDefinition.getDependsOn(), String.class)); dependsOnList.addAll(Arrays.asList(nullSafeArray(beanNames, String.class))); beanDefinition.setDependsOn(dependsOnList.toArray(new String[0])); return beanDefinition; } + /** + * Returns a {@link List} of beans by the given {@link Class type} in order. + * + * @param {@link Class type} of the bean. + * @param beanFactory {@link ConfigurableListableBeanFactory Spring container} used to acquire the ordered beans. + * @param beanType {@link Class type} of beans to acquire. + * @return a {@link List} of beans of the given {@link Class type} in order. + * @see #getBeansOfTypeOrdered(ConfigurableListableBeanFactory, Class, boolean, boolean) + * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory + * @see java.lang.Class + * @see java.util.List + */ + @NonNull + public static List getBeansOfTypeOrdered(@NonNull ConfigurableListableBeanFactory beanFactory, + @NonNull Class beanType) { + + return getBeansOfTypeOrdered(beanFactory, beanType, true, true); + } + + /** + * Returns a {@link List} of beans by the given {@link Class type} in order. + * + * @param {@link Class type} of the bean. + * @param beanFactory {@link ConfigurableListableBeanFactory Spring container} used to acquire the ordered beans. + * @param beanType {@link Class type} of beans to acquire. + * @param includeNonSingletons boolean indicating whether to include non-Singleton beans from the Spring container. + * @param allowEagerInit boolean indicating whether to eagerly initialize {@link FactoryBean FactoryBeans}. + * @return a {@link List} of beans of the given {@link Class type} in order. + * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory + * @see java.lang.Class + * @see java.util.List + */ + @NonNull + public static List getBeansOfTypeOrdered(@NonNull ConfigurableListableBeanFactory beanFactory, + @NonNull Class beanType, boolean includeNonSingletons, boolean allowEagerInit) { + + Assert.notNull(beanFactory, "BeanFactory must not be null"); + Assert.notNull(beanType, "Bean type must not be null"); + + Map beansOfType = + CollectionUtils.nullSafeMap(beanFactory.getBeansOfType(beanType, includeNonSingletons, allowEagerInit)); + + Set beanNamesOfType = new HashSet<>(beansOfType.keySet()); + + // Handles @Order annotated beans and beans implementing the Ordered interface + List> orderedBeansOfType = beansOfType.entrySet().stream() + .map(SpringUtils::toOrderedBeanWrapper) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + Set orderedBeanNamesOfType = orderedBeansOfType.stream() + .map(OrderedBeanWrapper::getBeanName) + .collect(Collectors.toSet()); + + Set unorderedBeanNamesOfType = new HashSet<>(beanNamesOfType); + + // Set Difference + unorderedBeanNamesOfType.removeAll(orderedBeanNamesOfType); + + orderedBeansOfType.addAll(orderUnorderedBeans(beanFactory, beansOfType, unorderedBeanNamesOfType)); + orderedBeansOfType.sort(AnnotationAwareOrderComparator.INSTANCE); + + return orderedBeansOfType.stream() + .map(OrderedBeanWrapper::getBean) + .collect(Collectors.toList()); + } + + @NonNull + private static List> orderUnorderedBeans(@NonNull ConfigurableListableBeanFactory beanFactory, + @NonNull Map beansOfType, @NonNull Set unorderedBeanNames) { + + List> orderedBeanWrappers = new ArrayList<>(unorderedBeanNames.size()); + + for (String beanName : unorderedBeanNames) { + + Integer order = Optional.ofNullable(beanName) + .filter(StringUtils::hasText) + .map(beanFactory::getBeanDefinition) + .filter(AnnotatedBeanDefinition.class::isInstance) + .map(AnnotatedBeanDefinition.class::cast) + .map(AnnotatedBeanDefinition::getFactoryMethodMetadata) + .filter(methodMetadata -> methodMetadata.isAnnotated(Order.class.getName())) + .map(methodMetadata -> methodMetadata.getAnnotationAttributes(Order.class.getName())) + .map(annotationAttributes -> annotationAttributes.getOrDefault("value", Ordered.LOWEST_PRECEDENCE)) + .map(Integer.class::cast) + .orElse(Ordered.LOWEST_PRECEDENCE); + + orderedBeanWrappers.add(DefaultOrderedBeanWrapper.from(beanName, beansOfType.get(beanName), order)); + } + + return orderedBeanWrappers; + } + + @Nullable + private static OrderedBeanWrapper toOrderedBeanWrapper(@NonNull Map.Entry beanEntry) { + + T bean = beanEntry.getValue(); + + Integer order = getOrder(bean); + + if (order == null) { + order = bean != null ? OrderUtils.getOrder(bean.getClass()) : null; + } + + return order != null + ? DefaultOrderedBeanWrapper.from(beanEntry.getKey(), bean, order) + : null; + } + + /** + * Null-safe operation to return the {@link Integer order} of the given {@link Object} if it is {@link Ordered} + * or {@literal null} if the given {@link Object} is not {@link Ordered}. + * + * @param target {@link Object} to evaluate; may be {@literal null}. + * @return the {@link Integer order} of the given {@link Object} if {@link Ordered}, + * otherwise return {@literal null}. + * @see org.springframework.core.Ordered + */ + public static @Nullable Integer getOrder(@Nullable Object target) { + return target instanceof Ordered ? ((Ordered) target).getOrder() : null; + } + + /** + * Returns bean of the given {@link Class type} in an ordered {@link Stream}. + * + * @param {@link Class type} of the beans. + * @param beanFactory {@link BeanFactory} from which to acquire the beans. + * @param beanType {@link Class type} of the beans. + * @return an ordered {@link Stream} of beans from the {@link BeanFactory} of the given {@link Class type}. + * @see org.springframework.beans.factory.BeanFactory + * @see java.util.stream.Stream + * @see java.lang.Class + */ + public static Stream getOrderedStreamOfBeansByType(@NonNull BeanFactory beanFactory, + @NonNull Class beanType) { + + Assert.notNull(beanFactory, "BeanFactory must not be null"); + Assert.notNull(beanType,"Bean type must not be null"); + + return beanFactory.getBeanProvider(beanType).orderedStream(); + } + public static Optional getPropertyValue(BeanDefinition beanDefinition, String propertyName) { return Optional.ofNullable(beanDefinition) @@ -141,33 +314,47 @@ public abstract class SpringUtils { return type != null ? type.getSimpleName() : null; } + public static Class nullSafeType(Object target) { + return nullSafeType(target, null); + } + + public static Class nullSafeType(Object target, Class defaultType) { + return target != null ? target.getClass() : defaultType; + } + public static boolean safeDoOperation(VoidReturningThrowableOperation operation) { + return safeDoOperation(operation, () -> {}); + } + + public static boolean safeDoOperation(VoidReturningThrowableOperation operation, Runnable backupOperation) { try { operation.run(); return true; } catch (Throwable cause) { + backupOperation.run(); return false; } } - public static T safeGetValue(Supplier valueSupplier) { - return safeGetValue(valueSupplier, (T) null); + public static T safeGetValue(ValueReturningThrowableOperation operation) { + return safeGetValue(operation, (T) null); } - public static T safeGetValue(Supplier valueSupplier, T defaultValue) { - return safeGetValue(valueSupplier, (Supplier) () -> defaultValue); + public static T safeGetValue(ValueReturningThrowableOperation operation, T defaultValue) { + return safeGetValue(operation, (Supplier) () -> defaultValue); } - public static T safeGetValue(Supplier valueSupplier, Supplier defaultValueSupplier) { - return safeGetValue(valueSupplier, (Function) exception -> defaultValueSupplier.get()); + public static T safeGetValue(ValueReturningThrowableOperation operation, Supplier defaultValueSupplier) { + return safeGetValue(operation, (Function) exception -> defaultValueSupplier.get()); } - public static T safeGetValue(Supplier valueSupplier, Function exceptionHandler) { + public static T safeGetValue(ValueReturningThrowableOperation operation, + Function exceptionHandler) { try { - return valueSupplier.get(); + return operation.get(); } catch (Throwable cause) { return exceptionHandler.apply(cause); @@ -189,6 +376,61 @@ public abstract class SpringUtils { } } + private static class DefaultOrderedBeanWrapper implements OrderedBeanWrapper { + + private static OrderedBeanWrapper from(String beanName, T bean) { + return from(beanName, bean, Ordered.LOWEST_PRECEDENCE); + } + + private static OrderedBeanWrapper from(String beanName, T bean, int order) { + return new DefaultOrderedBeanWrapper<>(beanName, bean, order); + } + + private final int order; + + private final T bean; + + private final String beanName; + + private DefaultOrderedBeanWrapper(String beanName, T bean, int order) { + + Assert.notNull(bean, "Bean must not be null"); + Assert.hasText(beanName, "Bean name is required"); + + this.bean = bean; + this.beanName = beanName; + this.order = order; + } + + @Override + public T getBean() { + return this.bean; + } + + @Override + public String getBeanName() { + return this.beanName; + } + + @Override + public int getOrder() { + return this.order; + } + } + + public interface OrderedBeanWrapper extends Ordered { + + T getBean(); + + String getBeanName(); + + } + + @FunctionalInterface + public interface ValueReturningThrowableOperation { + T get() throws Throwable; + } + /** * @deprecated use {@link VoidReturningThrowableOperation}. */ diff --git a/src/test/java/org/springframework/data/gemfire/util/SpringUtilsUnitTests.java b/src/test/java/org/springframework/data/gemfire/util/SpringUtilsUnitTests.java index ff2bc949..6fe21870 100644 --- a/src/test/java/org/springframework/data/gemfire/util/SpringUtilsUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/util/SpringUtilsUnitTests.java @@ -26,11 +26,14 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.springframework.data.gemfire.util.ArrayUtils.asArray; import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newRuntimeException; +import java.sql.Time; import java.util.Collections; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -50,16 +53,20 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.gemfire.test.model.Person; +import org.springframework.data.gemfire.util.SpringUtils.ValueReturningThrowableOperation; /** * Unit Tests for {@link SpringUtils}. * * @author John Blum + * @see java.util.function.Function * @see org.junit.Test - * @see org.junit.runner.RunWith * @see org.mockito.Mock * @see org.mockito.Mockito * @see org.mockito.junit.MockitoJUnitRunner + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.config.BeanDefinition * @see org.springframework.data.gemfire.util.SpringUtils * @since 1.9.0 */ @@ -213,7 +220,6 @@ public class SpringUtilsUnitTests { } @Test - @SuppressWarnings("all") public void setBeanDefinitionPropertyReference() { MutablePropertyValues mutablePropertyValues = new MutablePropertyValues(); @@ -233,7 +239,6 @@ public class SpringUtilsUnitTests { } @Test - @SuppressWarnings("all") public void setBeanDefinitionPropertyValue() { MutablePropertyValues mutablePropertyValues = new MutablePropertyValues(); @@ -333,7 +338,6 @@ public class SpringUtilsUnitTests { } @Test - @SuppressWarnings("all") public void equalsIgnoreNullIsFalse() { assertThat(SpringUtils.equalsIgnoreNull(null, "null")).isFalse(); @@ -355,7 +359,6 @@ public class SpringUtilsUnitTests { } @Test - @SuppressWarnings("all") public void nullOrEqualsWithNullIsTrue() { assertThat(SpringUtils.nullOrEquals(null, "test")).isTrue(); } @@ -371,7 +374,6 @@ public class SpringUtilsUnitTests { } @Test - @SuppressWarnings("all") public void nullSafeEqualsWithNullObjectsIsFalse() { assertThat(SpringUtils.nullSafeEquals(null, "test")).isFalse(); assertThat(SpringUtils.nullSafeEquals("test", null)).isFalse(); @@ -382,6 +384,58 @@ public class SpringUtilsUnitTests { assertThat(SpringUtils.nullSafeEquals("test", "mock")).isFalse(); } + @Test + public void nullSafeNameWithType() { + + assertThat(SpringUtils.nullSafeName(Boolean.class)).isEqualTo(Boolean.class.getName()); + assertThat(SpringUtils.nullSafeName(Integer.class)).isEqualTo(Integer.class.getName()); + assertThat(SpringUtils.nullSafeName(Double.class)).isEqualTo(Double.class.getName()); + assertThat(SpringUtils.nullSafeName(String.class)).isEqualTo(String.class.getName()); + assertThat(SpringUtils.nullSafeName(Time.class)).isEqualTo(Time.class.getName()); + assertThat(SpringUtils.nullSafeName(Person.class)).isEqualTo(Person.class.getName()); + } + + @Test + public void nullSafeNameWithNull() { + assertThat(SpringUtils.nullSafeName(null)).isNull(); + } + + @Test + public void nullSafeSimpleNameWithType() { + + assertThat(SpringUtils.nullSafeSimpleName(Boolean.class)).isEqualTo(Boolean.class.getSimpleName()); + assertThat(SpringUtils.nullSafeSimpleName(Integer.class)).isEqualTo(Integer.class.getSimpleName()); + assertThat(SpringUtils.nullSafeSimpleName(Double.class)).isEqualTo(Double.class.getSimpleName()); + assertThat(SpringUtils.nullSafeSimpleName(String.class)).isEqualTo(String.class.getSimpleName()); + assertThat(SpringUtils.nullSafeSimpleName(Time.class)).isEqualTo(Time.class.getSimpleName()); + assertThat(SpringUtils.nullSafeSimpleName(Person.class)).isEqualTo(Person.class.getSimpleName()); + } + + @Test + public void nullSafeSimpleNameWithNull() { + assertThat(SpringUtils.nullSafeSimpleName(null)).isNull(); + } + + @Test + public void nullSafeTypeWithObject() { + assertThat(SpringUtils.nullSafeType(new Object())).isEqualTo(Object.class); + } + + @Test + public void nullSafeTypeWithObjectAndDefaultType() { + assertThat(SpringUtils.nullSafeType("test", Person.class)).isEqualTo(String.class); + } + + @Test + public void nullSafeTypeWithNull() { + assertThat(SpringUtils.nullSafeType(null)).isNull(); + } + + @Test + public void nullSafeTypeWithNullAndDefaultType() { + assertThat(SpringUtils.nullSafeType(null, Person.class)).isEqualTo(Person.class); + } + @Test public void safeDoOperationWithNonThrowingOperation() { @@ -396,6 +450,30 @@ public class SpringUtilsUnitTests { assertThat(SpringUtils.safeDoOperation(() -> { throw new RuntimeException("TEST"); })).isFalse(); } + @Test + public void safeDoOperationWithNonThrowingOperationAndBackupOperation() { + + AtomicReference operationValue = new AtomicReference<>(); + + Runnable mockRunnable = mock(Runnable.class); + + assertThat(SpringUtils.safeDoOperation(() -> operationValue.set("MOCK"), mockRunnable)).isTrue(); + assertThat(operationValue.get()).isEqualTo("MOCK"); + + verifyNoInteractions(mockRunnable); + } + + @Test + public void safeDoOperationWithThrowingOperationAndBackupOperation() { + + Runnable mockRunnable = mock(Runnable.class); + + assertThat(SpringUtils.safeDoOperation(() -> { throw new RuntimeException("TEST"); }, mockRunnable)).isFalse(); + + verify(mockRunnable, times(1)).run(); + verifyNoMoreInteractions(mockRunnable); + } + @Test public void safeGetValueReturnsSuppliedValue() { assertThat(SpringUtils.safeGetValue(() -> "test")).isEqualTo("test"); @@ -415,16 +493,19 @@ public class SpringUtilsUnitTests { @Test public void safeGetValueReturnsSuppliedDefaultValue() { - Supplier exceptionThrowingSupplier = () -> { throw newRuntimeException("error"); }; + ValueReturningThrowableOperation exceptionThrowingOperation = + () -> { throw newRuntimeException("error"); }; + Supplier defaultValueSupplier = () -> "test"; - assertThat(SpringUtils.safeGetValue(exceptionThrowingSupplier, defaultValueSupplier)).isEqualTo("test"); + assertThat(SpringUtils.safeGetValue(exceptionThrowingOperation, defaultValueSupplier)).isEqualTo("test"); } @Test public void safeGetValueHandlesExceptionReturnsValue() { - Supplier exceptionThrowingSupplier = () -> { throw newRuntimeException("error"); }; + ValueReturningThrowableOperation exceptionThrowingOperation = + () -> { throw newRuntimeException("error"); }; Function exceptionHandler = exception -> { @@ -435,13 +516,14 @@ public class SpringUtilsUnitTests { return "test"; }; - assertThat(SpringUtils.safeGetValue(exceptionThrowingSupplier, exceptionHandler)).isEqualTo("test"); + assertThat(SpringUtils.safeGetValue(exceptionThrowingOperation, exceptionHandler)).isEqualTo("test"); } @Test(expected = IllegalStateException.class) public void safeGetValueHandlesExceptionAndCanThrowException() { - Supplier exceptionThrowingSupplier = () -> { throw newRuntimeException("error"); }; + ValueReturningThrowableOperation exceptionThrowingOperation = + () -> { throw newRuntimeException("error"); }; Function exceptionHandler = exception -> { @@ -453,7 +535,7 @@ public class SpringUtilsUnitTests { }; try { - SpringUtils.safeGetValue(exceptionThrowingSupplier, exceptionHandler); + SpringUtils.safeGetValue(exceptionThrowingOperation, exceptionHandler); } catch (IllegalStateException expected) {