From 78d0f143497bd0b84117175d5750c6312c778e3a Mon Sep 17 00:00:00 2001 From: John Blum Date: Wed, 18 Aug 2021 14:44:21 -0700 Subject: [PATCH] Add SpringExtensions abstract utility class extending the functionality of core Spring. --- .../geode/core/util/SpringExtensions.java | 100 +++++++++ .../core/util/SpringExtensionsUnitTests.java | 207 ++++++++++++++++++ 2 files changed, 307 insertions(+) create mode 100644 spring-geode/src/main/java/org/springframework/geode/core/util/SpringExtensions.java create mode 100644 spring-geode/src/test/java/org/springframework/geode/core/util/SpringExtensionsUnitTests.java diff --git a/spring-geode/src/main/java/org/springframework/geode/core/util/SpringExtensions.java b/spring-geode/src/main/java/org/springframework/geode/core/util/SpringExtensions.java new file mode 100644 index 00000000..0a847f03 --- /dev/null +++ b/spring-geode/src/main/java/org/springframework/geode/core/util/SpringExtensions.java @@ -0,0 +1,100 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package org.springframework.geode.core.util; + +import java.util.Optional; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; + +/** + * {@link SpringExtensions} is an abstract utility class containing functions to extend the functionality of Spring. + * + * @author John Blum + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.config.BeanDefinition + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry + * @see org.springframework.context.ApplicationContext + * @since 1.6.0 + */ +@SuppressWarnings("unused") +public abstract class SpringExtensions { + + public static final String BEAN_DEFINITION_METADATA_JSON = "{\n" + + "\t'beanName': '%1$s',%n" + + "\t'beanClassName': '%2$s',%n" + + "\t'description': '%3$s',%n" + + "\t'originatingBeanDefinition': '%4$s',%n" + + "\t'parentName': '%5$s',%n" + + "\t'resourceDescription': '%6$s',%n" + + "\t'source': '%7$s',%n" + + "}"; + + public static final String EMPTY_JSON_OBJECT = "{}"; + + public static @NonNull String getBeanDefinitionMetadata(@NonNull String beanName, + @Nullable ApplicationContext applicationContext) { + + return Optional.ofNullable(applicationContext) + .filter(ConfigurableApplicationContext.class::isInstance) + .map(ConfigurableApplicationContext.class::cast) + .map(ConfigurableApplicationContext::getBeanFactory) + .map(beanFactory -> getBeanDefinitionMetadata(beanName, beanFactory)) + .orElse(EMPTY_JSON_OBJECT); + } + + public static @NonNull String getBeanDefinitionMetadata(@NonNull String beanName, + @Nullable BeanFactory beanFactory) { + + return Optional.ofNullable(beanFactory) + .filter(BeanDefinitionRegistry.class::isInstance) + .map(BeanDefinitionRegistry.class::cast) + .map(registry -> getBeanDefinitionMetadata(beanName, registry)) + .orElse(EMPTY_JSON_OBJECT); + } + + public static @NonNull String getBeanDefinitionMetadata(@NonNull String beanName, + @Nullable BeanDefinitionRegistry beanDefinitionRegistry) { + + return Optional.ofNullable(beanDefinitionRegistry) + .filter(registry -> StringUtils.hasText(beanName)) + .map(registry -> registry.getBeanDefinition(beanName)) + .map(beanDefinition -> getBeanDefinitionMetadata(beanName, beanDefinition)) + .orElse(EMPTY_JSON_OBJECT); + } + + public static @NonNull String getBeanDefinitionMetadata(@Nullable String beanName, + @Nullable BeanDefinition beanDefinition) { + + if (beanDefinition != null) { + return String.format(BEAN_DEFINITION_METADATA_JSON, beanName, + beanDefinition.getBeanClassName(), + beanDefinition.getDescription(), + beanDefinition.getOriginatingBeanDefinition(), + beanDefinition.getParentName(), + beanDefinition.getResourceDescription(), + beanDefinition.getSource()); + } + + return EMPTY_JSON_OBJECT; + } +} diff --git a/spring-geode/src/test/java/org/springframework/geode/core/util/SpringExtensionsUnitTests.java b/spring-geode/src/test/java/org/springframework/geode/core/util/SpringExtensionsUnitTests.java new file mode 100644 index 00000000..cc7398e5 --- /dev/null +++ b/spring-geode/src/test/java/org/springframework/geode/core/util/SpringExtensionsUnitTests.java @@ -0,0 +1,207 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package org.springframework.geode.core.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +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 org.junit.Test; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Unit Tests for {@link SpringExtensions}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.geode.core.util.SpringExtensions + * @since 1.0.0 + */ +public class SpringExtensionsUnitTests { + + private void verifyBeanDefinitionInteractions(BeanDefinition mockBeanDefinition) { + + verify(mockBeanDefinition, times(1)).getBeanClassName(); + verify(mockBeanDefinition, times(1)).getDescription(); + verify(mockBeanDefinition, times(1)).getOriginatingBeanDefinition(); + verify(mockBeanDefinition, times(1)).getParentName(); + verify(mockBeanDefinition, times(1)).getResourceDescription(); + verify(mockBeanDefinition, times(1)).getSource(); + + verifyNoMoreInteractions(mockBeanDefinition); + } + + @Test + public void getBeanDefinitionMetadataFromApplicationContext() { + + BeanDefinition mockBeanDefinition = mock(BeanDefinition.class); + + ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class); + + ConfigurableListableBeanFactoryBeanDefinitionRegistry mockBeanDefinitionRegistry = + mock(ConfigurableListableBeanFactoryBeanDefinitionRegistry.class); + + String beanName = "TestBeanName"; + + doReturn(mockBeanDefinitionRegistry).when(mockApplicationContext).getBeanFactory(); + doReturn(mockBeanDefinition).when(mockBeanDefinitionRegistry).getBeanDefinition(eq(beanName)); + + String actualJson = SpringExtensions.getBeanDefinitionMetadata(beanName, mockApplicationContext); + + assertThat(actualJson).isNotEmpty(); + assertThat(actualJson).contains(beanName); + + verifyBeanDefinitionInteractions(mockBeanDefinition); + } + + @Test + public void getBeanDefinitionMetadataFromNullApplicationContextReturnsEmptyJsonObject() { + + assertThat(SpringExtensions.getBeanDefinitionMetadata("TestBeanName", (ApplicationContext) null)) + .isEqualTo(SpringExtensions.EMPTY_JSON_OBJECT); + } + + @Test + public void getBeanDefinitionMetadataFromNonConfigurableApplicationContextReturnsEmptyJsonObject() { + + ApplicationContext mockApplicationContext = mock(ApplicationContext.class); + + assertThat(SpringExtensions.getBeanDefinitionMetadata("TestBeanName", mockApplicationContext)) + .isEqualTo(SpringExtensions.EMPTY_JSON_OBJECT); + + verifyNoInteractions(mockApplicationContext); + } + + @Test + public void getBeanDefinitionMetadataFromApplicationContextWithNullBeanFactoryReturnsEmptyJsonObject() { + + ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class); + + doReturn(null).when(mockApplicationContext).getBeanFactory(); + + assertThat(SpringExtensions.getBeanDefinitionMetadata("TestBeanName", mockApplicationContext)) + .isEqualTo(SpringExtensions.EMPTY_JSON_OBJECT); + + verify(mockApplicationContext, times(1)).getBeanFactory(); + + verifyNoMoreInteractions(mockApplicationContext); + } + + @Test + public void getBeanDefinitionMetadataFromNullBeanFactoryReturnsEmptyJsonObject() { + + assertThat(SpringExtensions.getBeanDefinitionMetadata("TestBeanName", (BeanFactory) null)) + .isEqualTo(SpringExtensions.EMPTY_JSON_OBJECT); + } + + @Test + public void getBeanDefinitionMetadataFromNullBeanDefinitionRegistryReturnsEmptyJsonObject() { + + assertThat(SpringExtensions.getBeanDefinitionMetadata("TestBeanName", (BeanDefinitionRegistry) null)) + .isEqualTo(SpringExtensions.EMPTY_JSON_OBJECT); + } + + private void testGetBeanDefinitionMetadataFromInvalidBeanNameReturnsEmptyJsonObject(String beanName) { + + BeanDefinitionRegistry mockBeanDefinitionRegistry = mock(BeanDefinitionRegistry.class); + + assertThat(SpringExtensions.getBeanDefinitionMetadata(beanName, mockBeanDefinitionRegistry)) + .isEqualTo(SpringExtensions.EMPTY_JSON_OBJECT); + + verifyNoInteractions(mockBeanDefinitionRegistry); + } + + @Test + public void getBeanDefinitionMetadataFromBlankBeanNameReturnsEmptyJsonObject() { + testGetBeanDefinitionMetadataFromInvalidBeanNameReturnsEmptyJsonObject(" "); + } + + @Test + public void getBeanDefinitionMetadataFromEmptyBeanNameReturnsEmptyJsonObject() { + testGetBeanDefinitionMetadataFromInvalidBeanNameReturnsEmptyJsonObject(""); + } + + @Test + public void getBeanDefinitionMetadataFromNullBeanNameReturnsEmptyJsonObject() { + testGetBeanDefinitionMetadataFromInvalidBeanNameReturnsEmptyJsonObject(null); + } + + @Test + public void getBeanDefinitionMetadataFromBeanDefinitionRegistryReturningNullBeanDefinitionReturnsEmptyJsonObject() { + + BeanDefinitionRegistry mockBeanDefinitionRegistry = mock(BeanDefinitionRegistry.class); + + doReturn(null).when(mockBeanDefinitionRegistry).getBeanDefinition(any()); + + assertThat(SpringExtensions.getBeanDefinitionMetadata("TestBeanName", mockBeanDefinitionRegistry)) + .isEqualTo(SpringExtensions.EMPTY_JSON_OBJECT); + + verify(mockBeanDefinitionRegistry, times(1)).getBeanDefinition(eq("TestBeanName")); + + verifyNoMoreInteractions(mockBeanDefinitionRegistry); + } + + @Test + public void getBeanDefinitionMetadataForBeanDefinitionReturnsJson() { + + BeanDefinition mockBeanDefinition = mock(BeanDefinition.class); + BeanDefinition mockOriginatingBeanDefinition = mock(BeanDefinition.class); + + Object testSource = new Object(); + + doReturn("test.bean.ClassName").when(mockBeanDefinition).getBeanClassName(); + doReturn("TestDescription").when(mockBeanDefinition).getDescription(); + doReturn(mockOriginatingBeanDefinition).when(mockBeanDefinition).getOriginatingBeanDefinition(); + doReturn("TestParentName").when(mockBeanDefinition).getParentName(); + doReturn("TestResourceDescription").when(mockBeanDefinition).getResourceDescription(); + doReturn(testSource).when(mockBeanDefinition).getSource(); + + String expectedJson = String.format(SpringExtensions.BEAN_DEFINITION_METADATA_JSON, + "TestBeanName", "test.bean.ClassName", "TestDescription", mockOriginatingBeanDefinition, "TestParentName", + "TestResourceDescription", testSource); + + String actualJson = SpringExtensions.getBeanDefinitionMetadata("TestBeanName", mockBeanDefinition); + + assertThat(actualJson).isEqualTo(expectedJson); + + verifyBeanDefinitionInteractions(mockBeanDefinition); + verifyNoInteractions(mockOriginatingBeanDefinition); + } + + @Test + public void getBeanDefinitionMetadataForNullBeanDefinitionReturnsEmptyJsonObject() { + + assertThat(SpringExtensions.getBeanDefinitionMetadata("TestBeanName", (BeanDefinition) null)) + .isEqualTo(SpringExtensions.EMPTY_JSON_OBJECT); + } + + private interface ConfigurableListableBeanFactoryBeanDefinitionRegistry + extends BeanDefinitionRegistry, ConfigurableListableBeanFactory { } + +}