diff --git a/source/java/org/alfresco/util/BeanExtender.java b/source/java/org/alfresco/util/BeanExtender.java new file mode 100644 index 0000000000..eabcdf4e0c --- /dev/null +++ b/source/java/org/alfresco/util/BeanExtender.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.util; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +/** + * Extends the definition of a bean with another. + *

+ * Implements bean factory post processor. + * + * @author Roy Wetherall + * @since 5.0 + */ +public class BeanExtender implements BeanFactoryPostProcessor +{ + /** name of bean to extend */ + private String beanName; + + /** extending bean name */ + private String extendingBeanName; + + /** + * @param beanName bean name + */ + public void setBeanName(String beanName) + { + this.beanName = beanName; + } + + /** + * @param extendingBeanName extending bean name + */ + public void setExtendingBeanName(String extendingBeanName) + { + this.extendingBeanName = extendingBeanName; + } + + /** + * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory) + */ + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + { + ParameterCheck.mandatory("beanName", beanName); + ParameterCheck.mandatory("extendingBeanName", extendingBeanName); + + // check for bean name + if (!beanFactory.containsBean(beanName)) + { + throw new NoSuchBeanDefinitionException("Can't find bean '" + beanName + "' to be extended."); + } + + // check for extending bean + if (!beanFactory.containsBean(extendingBeanName)) + { + throw new NoSuchBeanDefinitionException("Can't find bean '" + extendingBeanName + "' that is going to extend origional bean definition."); + } + + // get the bean definitions + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); + BeanDefinition extendingBeanDefinition = beanFactory.getBeanDefinition(extendingBeanName); + + // update class + if (StringUtils.isNotBlank(extendingBeanDefinition.getBeanClassName()) && + !beanDefinition.getBeanClassName().equals(extendingBeanDefinition.getBeanClassName())) + { + beanDefinition.setBeanClassName(extendingBeanDefinition.getBeanClassName()); + } + + // update properties + MutablePropertyValues properties = beanDefinition.getPropertyValues(); + MutablePropertyValues extendingProperties = extendingBeanDefinition.getPropertyValues(); + for (PropertyValue propertyValue : extendingProperties.getPropertyValueList()) + { + properties.add(propertyValue.getName(), propertyValue.getValue()); + } + } +} diff --git a/source/test-java/org/alfresco/AllUnitTestsSuite.java b/source/test-java/org/alfresco/AllUnitTestsSuite.java index 461b3d02d1..1fe2b27b42 100644 --- a/source/test-java/org/alfresco/AllUnitTestsSuite.java +++ b/source/test-java/org/alfresco/AllUnitTestsSuite.java @@ -11,7 +11,7 @@ public class AllUnitTestsSuite extends TestSuite { /** * Creates the test suite - * + * * @return the test suite */ public static Test suite() @@ -97,5 +97,6 @@ public class AllUnitTestsSuite extends TestSuite suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.solr.SolrQueryHTTPClientTest.class)); suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.solr.SolrStatsResultTest.class)); suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.solr.facet.SolrFacetComparatorTest.class)); + suite.addTest(new JUnit4TestAdapter(org.alfresco.util.BeanExtenderUnitTest.class)); } } diff --git a/source/test-java/org/alfresco/util/BeanExtenderUnitTest.java b/source/test-java/org/alfresco/util/BeanExtenderUnitTest.java new file mode 100644 index 0000000000..de1dbff6b6 --- /dev/null +++ b/source/test-java/org/alfresco/util/BeanExtenderUnitTest.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.util; + +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; +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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.alfresco.util.GUID; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +/** + * Bean extender unit test. + * + * @author Roy Wetherall + * @since 5.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class BeanExtenderUnitTest +{ + private static final String BEAN_NAME = GUID.generate(); + private static final String EXTENDING_BEAN_NAME = GUID.generate(); + + @Mock private ConfigurableListableBeanFactory mockedBeanFactory; + @Mock private BeanDefinition mockedBeanDefinition; + @Mock private BeanDefinition mockedExtendingBeanDefinition; + @Mock private MutablePropertyValues mockedPropertyValuesBean; + @Mock private MutablePropertyValues mockedPropertyValuesExtendingBean; + + @InjectMocks private BeanExtender beanExtender; + + /** expected exception rule */ + @Rule + public ExpectedException exception = ExpectedException.none(); + + /** + * Test method setup + */ + @Before + public void before() throws Exception + { + MockitoAnnotations.initMocks(this); + + // setup common interactions + doReturn(mockedPropertyValuesBean).when(mockedBeanDefinition).getPropertyValues(); + doReturn(mockedPropertyValuesExtendingBean).when(mockedExtendingBeanDefinition).getPropertyValues(); + } + + /** + * given that the bean name is not set, ensure that an Illegal Argument + * exception is thrown. + */ + @Test + public void beanNameNotSet() + { + // === given === + + // set the extending bean name + beanExtender.setExtendingBeanName(EXTENDING_BEAN_NAME); + + // expecting exception + exception.expect(IllegalArgumentException.class); + + // === when === + beanExtender.postProcessBeanFactory(mockedBeanFactory); + } + + /** + * given that the extending bean name is not set, ensure that an illegal + * argument exception is thrown. + */ + @Test + public void extendingBeanNameNotSet() + { + // === given === + + // set the extending bean name + beanExtender.setBeanName(BEAN_NAME); + + // expecting exception + exception.expect(IllegalArgumentException.class); + + // === when === + beanExtender.postProcessBeanFactory(mockedBeanFactory); + } + + /** + * given that the bean does not exist ensure that an exception is thrown + */ + @Test + public void beanDoesNotExist() + { + // === given === + + // set the bean names + beanExtender.setBeanName(BEAN_NAME); + beanExtender.setExtendingBeanName(EXTENDING_BEAN_NAME); + doReturn(false).when(mockedBeanFactory).containsBean(BEAN_NAME); + doReturn(true).when(mockedBeanFactory).containsBean(EXTENDING_BEAN_NAME); + + // expecting exception + exception.expect(NoSuchBeanDefinitionException.class); + + // === when === + beanExtender.postProcessBeanFactory(mockedBeanFactory); + } + + /** + * given that the extending bean does not exist ensure that an exception is thrown + */ + @Test + public void extendingBeanDoesNotExist() + { + // === given === + + // set the bean names + beanExtender.setBeanName(BEAN_NAME); + beanExtender.setExtendingBeanName(EXTENDING_BEAN_NAME); + doReturn(true).when(mockedBeanFactory).containsBean(BEAN_NAME); + doReturn(false).when(mockedBeanFactory).containsBean(EXTENDING_BEAN_NAME); + + // expecting exception + exception.expect(NoSuchBeanDefinitionException.class); + + // === when === + beanExtender.postProcessBeanFactory(mockedBeanFactory); + } + + /** + * given that a different class name has been set on the extending bean ensure it is + * set correctly on the origional bean + */ + @Test + public void beanClassNameSet() + { + // === given === + + // set the bean names + beanExtender.setBeanName(BEAN_NAME); + beanExtender.setExtendingBeanName(EXTENDING_BEAN_NAME); + + // both beans are available in the bean factory + doReturn(true).when(mockedBeanFactory).containsBean(BEAN_NAME); + doReturn(true).when(mockedBeanFactory).containsBean(EXTENDING_BEAN_NAME); + + // return the mocked bean definitions + doReturn(mockedBeanDefinition).when(mockedBeanFactory).getBeanDefinition(BEAN_NAME); + doReturn(mockedExtendingBeanDefinition).when(mockedBeanFactory).getBeanDefinition(EXTENDING_BEAN_NAME); + + // bean class names + doReturn("a").when(mockedBeanDefinition).getBeanClassName(); + doReturn("b").when(mockedExtendingBeanDefinition).getBeanClassName(); + + // no properties have been defined + doReturn(Collections.EMPTY_LIST).when(mockedPropertyValuesExtendingBean).getPropertyValueList(); + + // === when === + beanExtender.postProcessBeanFactory(mockedBeanFactory); + + // === then === + + // expect the class name to be set on the bean + verify(mockedBeanDefinition, times(1)).setBeanClassName("b"); + verify(mockedPropertyValuesBean, never()).add(anyString(), anyString()); + + } + + /** + * given that new property values have been set on the extending bean ensure that they + * are correctly set on the original bean. + */ + @Test + public void beanPropertyValuesSet() + { + // === given === + + // set the bean names + beanExtender.setBeanName(BEAN_NAME); + beanExtender.setExtendingBeanName(EXTENDING_BEAN_NAME); + + // both beans are available in the bean factory + doReturn(true).when(mockedBeanFactory).containsBean(BEAN_NAME); + doReturn(true).when(mockedBeanFactory).containsBean(EXTENDING_BEAN_NAME); + + // return the mocked bean definitions + doReturn(mockedBeanDefinition).when(mockedBeanFactory).getBeanDefinition(BEAN_NAME); + doReturn(mockedExtendingBeanDefinition).when(mockedBeanFactory).getBeanDefinition(EXTENDING_BEAN_NAME); + + // bean class names + doReturn("a").when(mockedBeanDefinition).getBeanClassName(); + doReturn(null).when(mockedExtendingBeanDefinition).getBeanClassName(); + + PropertyValue mockedPropertyValueOne = generateMockedPropertyValue("one", "1"); + PropertyValue mockedPropertyValueTwo = generateMockedPropertyValue("two", "2"); + List list = new ArrayList(2); + list.add(mockedPropertyValueOne); + list.add(mockedPropertyValueTwo); + doReturn(list).when(mockedPropertyValuesExtendingBean).getPropertyValueList(); + + // === when === + beanExtender.postProcessBeanFactory(mockedBeanFactory); + + // === then === + + // expect the class name to be set on the bean + verify(mockedBeanDefinition, never()).setBeanClassName(anyString()); + verify(mockedPropertyValuesBean, times(1)).add("one", "1"); + verify(mockedPropertyValuesBean, times(1)).add("two", "2"); + } + + /** + * Helper method to generate a mocked property value + */ + private PropertyValue generateMockedPropertyValue(String name, String value) + { + PropertyValue result = mock(PropertyValue.class); + doReturn(name).when(result).getName(); + doReturn(value).when(result).getValue(); + return result; + } +}