About the Author

Elbert Bautista

Software Architect

@elbertbautista
Published
in Development 9 min read

Broadleaf Merge Process 2.0

Utilizing Spring's BeanPostProcessor

If you haven't read the blog post entitled "Broadleaf's Unique Application Context Merge Process Explained", it is worth a quick read to familiarize yourself with the motivations behind why we use it. Now, if you've actually worked with Broadleaf and dealt with the merge process in your own implementations, you may have noticed the lack of fine grain control. Well, we've been working hard to improve on it to give you a more flexible and easy "Spring" way of controlling the entire process. But, before I explain how to use it, I'd like to go over some of the issues we had with the old process and how this new merge overcomes it.

The Issues

In the old process, the merge strategy for the most part is defined in a file called default.properties, located in the package org.broadleafcommerce.common.extensibility.context.merge. The only way to control how beans were merged was to override this "proprietary" properties file. Well, in the spirit of open source, our goal was to come up with a more flexible and standardized solution other than overriding this file.

Second, dealing with merging collections in the old process was a big issue. For example, let's say you wanted to create two "plugins" that override a bean defined in Broadleaf. In essence, they would need to inject several implementations into Broadleaf's pre-defined source list. However... say you have a requirement where you would like to inject these beans in a specific order AND would only like to inject certain beans only if another bean was present. This particular use-case would prove to be very difficult with the old process.

The Problem

Let's take the same example Kelly mentioned in his post above. Let's add two new fulfillment providers to the Broadleaf Fulfillment Pricing Providers list. Take a look at the following default Broadleaf configuration:

<bean id="blFulfillmentPricingProviders" class="org.springframework.beans.factory.config.ListFactoryBean">
    <property name="sourceList"> 
     <list> 
         <ref bean="blFixedPriceFulfillmentPricingProvider" /> 
            <ref bean="blBandedFulfillmentPricingProvider" />
     </list>
 </property> 
</bean>

In the old process, if you had two plugins that add "myFedExFulfillmentPricingProvider" and "myUPSFulfillmentPricingProvider" the resulting merged source list would look something like this:

<bean id="blFulfillmentPricingProviders" class="org.springframework.beans.factory.config.ListFactoryBean">
    <property name="sourceList"> 
     <list> 
         <ref bean="blFixedPriceFulfillmentPricingProvider" /> 
            <ref bean="blBandedFulfillmentPricingProvider" />
         <ref bean="myFedExFulfillmentPricingProvider" />
          <ref bean="myUPSFulfillmentPricingProvider" /> 
       </list>
 </property> 
</bean>

The outcome of this merge was a result of the strategy defined in default.properties that basically appended each bean to the end of the list:

handler.16.1=org.broadleafcommerce.common.extensibility.context.merge.handlers.InsertItems

Now, how would you configure it so that myFedExFulfillmentPricingProvider is loaded in a specific position, and myUPSFulfillmentPricingProvider was loaded only if the bean myInternationalShippingService was defined as well?

The Solution

Using Spring's BeanPostProcessor

Broadleaf's new merge pattern utilizes Spring's BeanPostProcessor interface, which is a factory hook that allows for custom modification of new bean instances. We created an abstract implementation called AbstractMergeBeanPostProcessor along with two concrete implementations: LateStageMergeBeanPostProcessor and EarlyStageMergeBeanPostProcessor.

These BeanPostProcessor instances can be used to merge additional collection members into collections declared elsewhere. In effect, this allows an implementer to only declare the collection members they're currently interested in and get them merged into a larger, pre-existing list. This is more desirable than a traditional, comprehensive override that would require re-declaring the original bean and all of its members in addition to the current members being considered as well as beans it may not know about in other dependencies. In addition you can specify attributes to this processor that tells Broadleaf and Spring how to merge the beans properly.

You can specify

  • placement: PREPEND, APPEND, or SPECIFIC (the location of where to place the bean)
  • position: If a placement of type Placement.SPECIFIC is used, then this is the integer position in the target collection at which the merge will be performed.
  • status provider: which controls whether or not this post processor is activated. If no statusProvider is set, then it will always execute.

Here's an example of what a new declaration would look like:

<bean class="org.broadleafcommerce.common.extensibility.context.merge.LateStageMergeBeanPostProcessor">
  <property name="collectionRef" value="myFulfillmentPricingProviders"/>
  <property name="targetRef" value="blFulfillmentPricingProviders"/>
  <property name="placement" value="SPECIFIC"/>
   <property name="position" value="1"/>
</bean>

<bean id="myFulfillmentPricingProviders" class="org.springframework.beans.factory.config.ListFactoryBean">
  <property name="sourceList">
      <list>
          <ref bean="myFedExFulfillmentPricingProvider"/>
       </list>
 </property>
</bean>

As you can see, now you have the ability to declare the merge strategy just like you would any other bean. By doing this, we hope to give implementors the ease and flexibility to control their own configurations without having to to override and understand the complex merge logic rules pre-defined in Broadleaf.