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;
+ }
+}