Unified Information Access Blog
Welcome to Attivio's Unified Information Access Blog. Join us for discussions on topics ranging from enterprise search solutions, information access insights, Agile software development methodology to programming with Java. We hope you'll find the articles informative and participate in the discussions by leaving a comment.
With the release of AIE 2.1, we are moving to a Spring Framework-based configuration and away from a proprietary configuration syntax and parsing mechanism. Moving to a standards-based approach is almost always a good thing. However, we did come across a few challenges that we needed to solve. The most serious one was the notion of overriding or replacing the definition of a component or bean.
A number of Spring users had the same issue a while back and a couple enhancement requests were filed as a result:
Out of those requests came a new feature to control this behavior. By default Spring will operate in one of two modes based on the values of the bean factory's allowBeanDefinitionOverriding.
allowBeanDefinitionOverriding = true
If the allowBeanDefinitionOverriding was set to true a few things happened. For starters, bean definitions could replace previous definitions, but only in a separate file. For example the following would fail:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean name="foo" class="com.attivio.Foo" />
<bean name="foo" class="com.attivio.Bar" />
</beans>
However, if those bean definitions were in separate files it would work and the last bean definition (class=com.attivio.Bar) would be the one used at run time. In addition, this replacement was silent as far as the user was concerned. Neither configuration file had any notion that it was replacing another bean or being replaced. Further, there were no log messages or pluggable hooks that users could listen for in order to know that was happening.
allowBeanDefinitionOverriding = false
This case is decidedly clearer and much more restrictive. Any time two beans had the same id, a bean exception occurred and the application would fail to start. The error messages are pretty good in this case:
Caused by:
org.springframework.beans.factory.parsing.BeanDefinitionParsingException:
Configuration problem: Bean name 'foo' is already used in this file
Offending resource: file [C:\myOverrideBeans.xml]
Our solution
We wanted to mimic the previous application's ability to be explicit about overriding so that customers know what beans were being loaded but throw an exception if the setup wasn't quite correct. The sample XML we wanted to support was as follows:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:attivio="http://www.attivio.com/configuration/features/core"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.attivio.com/configuration/features/core
http://www.attivio.com/configuration/features/coreFeatures.xsd">
<bean name="foo" class="com.attivio.Foo" />
<bean name="foo" class="com.attivio.Bar" attivio:override="true" />
</beans>
This new attivio:override attribute uses Spring's Extensible XML Authoring to add an attribute to the spring syntax. The Spring documentation does a great job of walking you through the process of setting up and registering the new attribute. All that we needed to do from there was to define a schema to represent the new attribute.
<xsd:schema xmlns="http://www.attivio.com/configuration/features/core"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.attivio.com/configuration/features/core"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<!-- support bean overriding -->
<xsd:attribute name="override" type="xsd:boolean" />
</xsd:schema>
Then we needed to define the actual parser for the attribute which Spring calls a BeanDefinitionDecorator.
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
/** Reads the 'override' property for a bean and if set to true, deletes any previous
* bean defined with that name and replaces it with this bean definition.
*/
public class OverrideBeanDefinitionDecorator implements BeanDefinitionDecorator {
/** {@inheritDoc} */
@Override
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
Attr attr = (Attr) node;
if ("true".equals(attr.getValue())) {
// don't allow people to set override=true if there is nothing to override
if
(parserContext.getRegistry().getBeanDefinition(definition.getBeanName())==null) {
throw new SomeNiceException("No previous bean definition for " + definition.getBeanName());
} else {
parserContext.getRegistry().removeBeanDefinition(definition.getBeanName());
}
}
return definition;
}
}
We also needed a custom BeanDefinitionReader in order to handle the override hooks properly:
import java.util.List;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader;
import org.springframework.beans.factory.xml.XmlReaderContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
/** Handles override parsing. */
public class AttivioBeanDefinitionDocumentReader extends DefaultBeanDefinitionDocumentReader {
/** Create a parser that is compatible with {@link com.attivio.spring.parser.OverrideBeanDefinitionDecorator}. */
@Override
protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root) {
BeanDefinitionParserDelegate delegate = new
OverrideAwareBeanDefinitionParserDelegate(readerContext);
delegate.initDefaults(root);
return delegate;
}
/** Delegate that is ok with overrides if attivio:override == true.
*
* @see OverrideBeanDefinitionDecorator
*/
private static final class OverrideAwareBeanDefinitionParserDelegate extends BeanDefinitionParserDelegate {
@Override
protected void checkNameUniqueness(String beanName, List<String> aliases, Element beanElement) {
Attr attr = beanElement.getAttributeNodeNS("http://www.attivio.com/configuration/features/core", "override");
if (attr != null && "true".equals(attr.getValue())) {
System.out.println("Saw override of bean named %s", beanName);
} else {
// this will throw an exception if the names are the same
super.checkNameUniqueness(beanName, aliases, beanElement);
}
}
public OverrideAwareBeanDefinitionParserDelegate(XmlReaderContext readerContext) {
super(readerContext);
}
}
}
Results
With this new override attribute we're able to consistently and predictably control which beans are overriding others. More importantly, if something is wrong we're able to notify users as to what happened and why. We found this feature to be most useful in creating unit tests. Our tests can load a default configuration file for the normal application setup, and then we load another test file that overrides normal beans with mock objects that we can inspect in the unit tests.

Articles by Date
Recent Posts
Thinking Like a Tester
What AIE and unified information access mean for developers
The (Real) Semantic Web Requires Machine Learning
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8

