Extending Data Sync Tool
The data sync tool can be extended to support new object types. In most cases, it requires very little additional coding. The basic steps are:
Add the class to the global dependency descriptor
Set the object merge boundaries
Set the property merge boundaries
Specify the GUID getter method
Create an entity locator for the object type
Create the DAO adapter for the object type
Handle association object types as required
Adding the class to the global dependency descriptor
The globalEpDependencyDescriptor
bean definition in conf/spring/sync-tool-context.xml
contains the list of all the objects that can be exported and imported using the data sync tool.
<bean id="globalEpDependencyDescriptor" parent="abstractGlobalEpDependencyDescriptor" />
<bean id="abstractGlobalEpDependencyDescriptor" abstract="true" class="com.elasticpath.tools.sync.job.impl.GlobalEpDependencyDescriptorImpl">
<property name="domainClassOrdering">
<map>
<entry key="com.elasticpath.domain.catalog.Catalog" value="-13000" />
<entry key="com.elasticpath.domain.skuconfiguration.SkuOption" value="-12800" />
<entry key="com.elasticpath.domain.skuconfiguration.SkuOptionValue" value="-12700" />
<entry key="com.elasticpath.domain.attribute.Attribute" value="-12500" />
<entry key="com.elasticpath.domain.catalog.Brand" value="-12400" />
<entry key="com.elasticpath.domain.catalog.CategoryType" value="-12000" />
<entry key="com.elasticpath.domain.catalog.ProductType" value="-11000" />
<entry key="com.elasticpath.domain.catalog.Category" value="-10000" />
<entry key="com.elasticpath.domain.catalog.Product" value="-9000" />
<entry key="com.elasticpath.domain.catalog.ProductBundle" value="-8000" />
<entry key="com.elasticpath.domain.catalog.ProductSku" value="-7000" />
<entry key="com.elasticpath.domain.rules.Rule" value="-6000" />
<entry key="com.elasticpath.common.dto.pricing.PriceListDescriptorDTO" value="-5000" />
<entry key="com.elasticpath.common.dto.pricing.BaseAmountDTO" value="-4000" />
<entry key="com.elasticpath.domain.pricing.PriceListAssignment" value="-3000" />
<entry key="com.elasticpath.domain.contentspace.DynamicContent" value="-2000" />
<entry key="com.elasticpath.domain.targetedselling.DynamicContentDelivery" value="-1000" />
<entry key="com.elasticpath.domain.pricing.PriceListDescriptor" value="-500" />
<entry key="com.elasticpath.domain.pricing.PriceListAssignment" value="-400" />
<entry key="com.elasticpath.domain.pricing.BaseAmount" value="-300" />
</map>
</property>
</bean>
To add support for another object type, simply add an additional <entry>
element to the <map>
with key equal to the fully qualified interface name. The value
attribute is used to define the how the domain classes should be ordered when synchronizing between two systems.
For more information, take a look at the GlobalEpDependencyDescriptorImpl
class where this value is used to set the ordering.
note
If you add an object type that has a dependency on another object type, the new object must appear in the list after the type on which it depends.
Setting object merge boundaries
When syncing an object, the data sync tool will attempt to sync the object and all supported object types in the object’s graph.
For example when syncing a category, it would attempt to sync that category and following objects associated with it:
- Parent category
- All child categories
- Its catalog
- Its products and so on
This could result in a very large amount of unnecessary data being synced and could cause performance issues. You can specify restrictions on which related objects to sync.
The mergeBoundarySpecification
bean definition in conf/spring/service/sync-merge-configuration.xml
allows you to configure these restrictions.
<bean id="mergeBoundarySpecification" parent="abstractMergeBoundarySpecification"/>
<bean id="abstractMergeBoundarySpecification" abstract="true" class="com.elasticpath.tools.sync.merge.configuration.impl.MergeBoundarySpecificationImpl">
<property name="mergeBoundaryMap">
<map>
<entry key="com.elasticpath.domain.contentspace.impl.DynamicContentImpl">
<set/>
</entry>
<entry key="com.elasticpath.domain.pricing.impl.PriceListAssignmentImpl">
<set>
<value>com.elasticpath.domain.catalog.impl.CatalogImpl</value>
<value>com.elasticpath.domain.pricing.impl.PriceListDescriptorImpl</value>
</set>
</entry>
<entry key="com.elasticpath.domain.targetedselling.impl.DynamicContentDeliveryImpl">
<set>
<value>com.elasticpath.domain.contentspace.impl.ContentSpaceImpl</value>
<value>com.elasticpath.domain.contentspace.impl.DynamicContentImpl</value>
</set>
</entry>
<entry key="com.elasticpath.domain.skuconfiguration.impl.SkuOptionImpl">
<set>
<value>com.elasticpath.domain.catalog.impl.CatalogImpl</value>
</set>
</entry>
<entry key="com.elasticpath.domain.skuconfiguration.impl.SkuOptionValueImpl">
<set>
<value>com.elasticpath.domain.skuconfiguration.impl.SkuOptionImpl</value>
</set>
</entry>
<entry key="com.elasticpath.domain.pricing.impl.PriceListDescriptorImpl">
<set/>
</entry>
<entry key="com.elasticpath.domain.pricing.impl.BaseAmountImpl">
<set/>
</entry>
<entry key="com.elasticpath.domain.catalog.impl.ProductImpl">
<set>
<value>com.elasticpath.domain.catalog.impl.CategoryImpl</value>
<value>com.elasticpath.domain.catalog.impl.LinkedCategoryImpl</value>
<value>com.elasticpath.domain.catalog.impl.BrandImpl</value>
<value>com.elasticpath.domain.catalog.impl.ProductTypeImpl</value>
<value>com.elasticpath.domain.tax.impl.TaxCodeImpl</value>
<value>com.elasticpath.domain.catalog.impl.CatalogImpl</value>
<value>com.elasticpath.domain.attribute.impl.AttributeImpl</value>
<value>com.elasticpath.domain.skuconfiguration.impl.SkuOptionImpl</value>
<value>com.elasticpath.domain.skuconfiguration.impl.SkuOptionValueImpl</value>
</set>
</entry>
<entry key="com.elasticpath.domain.catalog.impl.ProductBundleImpl">
<set>
<value>com.elasticpath.domain.catalog.impl.CategoryImpl</value>
<value>com.elasticpath.domain.catalog.impl.LinkedCategoryImpl</value>
<value>com.elasticpath.domain.catalog.impl.BrandImpl</value>
<value>com.elasticpath.domain.catalog.impl.ProductImpl</value>
<value>com.elasticpath.domain.catalog.impl.ProductBundleImpl</value>
<value>com.elasticpath.domain.catalog.impl.ProductTypeImpl</value>
<value>com.elasticpath.domain.tax.impl.TaxCodeImpl</value>
<value>com.elasticpath.domain.catalog.impl.CatalogImpl</value>
<value>com.elasticpath.domain.attribute.impl.AttributeImpl</value>
<value>com.elasticpath.domain.skuconfiguration.impl.SkuOptionImpl</value>
<value>com.elasticpath.domain.skuconfiguration.impl.SkuOptionValueImpl</value>
</set>
</entry>
<entry key="com.elasticpath.domain.catalog.impl.ProductSkuImpl">
<set>
<value>com.elasticpath.domain.catalog.impl.CatalogImpl</value>
<value>com.elasticpath.domain.catalog.impl.ProductImpl</value>
<value>com.elasticpath.domain.attribute.impl.AttributeImpl</value>
<value>com.elasticpath.domain.skuconfiguration.impl.SkuOptionImpl</value>
<value>com.elasticpath.domain.skuconfiguration.impl.SkuOptionValueImpl</value>
</set>
</entry>
</property>
</bean>
The mergeBoundarySpecification
bean contains a mergeBoundaryMap
property. This map contains one entry for each supported object type (keyed on the object’s implementation class name). Each entry is a set containing the implementation class names of the objects that must not be included.
Setting property merge boundaries
In addition to object merge boundaries, you can also control which properties are excluded during sync operations.
This is done by modifying the mergeFilter
bean configuration in conf/spring/service/sync-service.xml
:
<bean id="mergeFilter" class="com.elasticpath.tools.sync.merge.impl.SimpleMergeFilter">
<property name="mergeAll">
<value>true</value>
</property>
<property name="excludeMethods">
<map>
<entry key="com.elasticpath.domain.rules.impl.PromotionRuleImpl">
<set>
<value>getCmUser</value>
<value>getCurrentLupNumber</value>
</set>
</entry>
<entry key="com.elasticpath.domain.skuconfiguration.impl.SkuOptionImpl">
<set>
<value>getOptionValues</value>
</set>
</entry>
<entry key="com.elasticpath.domain.catalog.impl.CategoryImpl">
<set>
<value>getChildren</value>
</set>
</entry>
<entry key="com.elasticpath.domain.catalog.impl.ProductSkuImpl">
<set>
<value>getInventories</value>
</set>
</entry>
<entry key="com.elasticpath.domain.rules.impl.CouponUsageImpl">
<set>
<value>getUseCount</value>
<value>getLimitedDurationEndDate</value>
<value>isSuspended</value>
</set>
</entry>
</map>
</property>
</bean>
The excludeMethods
property contains sets keyed on the objects’ fully-qualified class names. Each set contains the getter methods for the properties to be excluded during sync operations.
Specifying the GUID getter method
In the conf/spring/service/sync-merge-configuration.xml
file, update the guidMethodNames
map in the guidLocator
bean to include the GUID getter method that should be called on the object:
<bean id="guidLocator" class="com.elasticpath.tools.sync.merge.configuration.impl.GuidLocatorImpl">
<property name="guidMethodNames">
<map>
<entry key="com.elasticpath.domain.catalog.impl.ProductTypeImpl"><value>getName</value></entry>
<entry key="com.elasticpath.domain.catalog.impl.BrandImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.catalog.impl.LinkedCategoryImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.catalog.impl.ProductAssociationImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.catalog.impl.CategoryImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.catalog.impl.ProductImpl"><value>getCode</value></entry>
<entry key="com.elasticpath.domain.catalog.impl.ProductBundleImpl"><value>getCode</value></entry>
<entry key="com.elasticpath.domain.catalog.impl.BundleConstituentImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.pricing.impl.PriceAdjustmentImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.tax.impl.TaxCodeImpl"><value>getCode</value></entry>
<entry key="com.elasticpath.domain.catalog.impl.CatalogImpl"><value>getCode</value></entry>
<entry key="com.elasticpath.domain.catalog.impl.ProductSkuImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.attribute.impl.AttributeImpl"><value>getKey</value></entry>
<entry key="com.elasticpath.domain.skuconfiguration.impl.SkuOptionImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.skuconfiguration.impl.SkuOptionValueImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.skuconfiguration.impl.JpaAdaptorOfSkuOptionValueImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.store.impl.StoreImpl"><value>getCode</value></entry>
<entry key="com.elasticpath.domain.rules.impl.RuleSetImpl"><value>getName</value></entry>
<entry key="com.elasticpath.domain.catalog.impl.CategoryTypeImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.catalog.impl.ProductTypeImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.catalog.impl.BrandImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.attribute.impl.AttributeImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.pricing.impl.BaseAmountImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.pricing.impl.PriceListDescriptorImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.pricing.impl.PriceListAssignmentImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.tags.domain.impl.ConditionalExpressionImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.contentspace.impl.ParameterValueImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.contentspace.impl.ParameterLocaleDependantValueImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.targetedselling.impl.DynamicContentDeliveryImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.contentspace.impl.DynamicContentImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.contentspace.impl.ContentSpaceImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.sellingcontext.impl.SellingContextImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.rules.impl.CouponConfigImpl"><value>getRuleCode</value></entry>
<entry key="com.elasticpath.domain.rules.impl.CouponImpl"><value>getGuid</value></entry>
<entry key="com.elasticpath.domain.rules.impl.CouponUsageImpl"><value>getGuid</value></entry>
</map>
</property>
</bean>
Note that in most cases (and always for classes that implement Entity
), the GUID getter method is getGuid
.
Create an entity locator
The data sync tool uses entity locators to find objects for syncing.
To enable the data sync to retrieve instances of the object type, you must create a new entity locator (extending AbstractEntityLocator
). Override the locatePersistence
methods to retrieve objects using the appropriate core service as follows:
public class PriceListDescriptorLocatorImpl extends AbstractEntityLocator {
private PriceListDescriptorService priceListDescriptorService;
@Override
public Persistable locatePersistence(final String guid, final Class< ? > clazz) {
return priceListDescriptorService.findByGuid(guid);
}
/**
* @param priceListDescriptorService price list descriptor service
*/
public void setPriceListDescriptorService(final PriceListDescriptorService priceListDescriptorService) {
this.priceListDescriptorService = priceListDescriptorService;
}
@Override
public boolean isResponsibleFor(final Class< ? > clazz) {
//This class should be responsible for both PriceListDescriptorDTO and PriceListDescriptor
return PriceListDescriptorDTO.class.isAssignableFrom(clazz) || PriceListDescriptor.class.isAssignableFrom(clazz);
}
}
In sync-merge-configuration.xml
, add a bean definition for the new entity locator:
<bean id="priceListDescriptorLocator" class="com.elasticpath.tools.sync.merge.configuration.impl.PriceListDescriptorLocatorImpl">
<property name="priceListDescriptorService" ref="priceListDescriptorService" />
</bean>
Update the entityLocator
bean to include the core service used by the new entity locator:
<bean id="priceListDescriptorLocator" class="com.elasticpath.tools.sync.merge.configuration.impl.PriceListDescriptorLocatorImpl">
<property name="priceListDescriptorService" ref="priceListDescriptorService" />
</bean>
Update the entityLocator
bean to include the core service used by the new entity locator.
Creating the DAO adapter
The next step is to create the DAO (Data Access Object) adapter for the new object type. The adapter is responsible for delegating to the persistence methods in the appropriate DAO. It must implement com.elasticpath.tools.sync.target.DaoAdapter
. For example:
public class CatalogDaoAdapterImpl extends AbstractDaoAdapter<Catalog> {
private static final Logger LOG = Logger.getLogger(CatalogDaoAdapterImpl.class);
private CatalogService catalogService;
private BeanFactory beanFactory;
@Override
public void add(final Catalog newPersistence) throws SyncToolRuntimeException {
catalogService.saveOrUpdate(newPersistence);
}
@Override
public Catalog createBean(final Catalog catalog) {
return beanFactory.getBean(ContextIdNames.CATALOG);
}
@Override
public Catalog get(final String guid) {
try {
return (Catalog) getEntityLocator().locatePersistence(guid, Catalog.class);
} catch (SyncToolConfigurationException e) {
throw new SyncToolRuntimeException("Unable to locate persistence", e);
}
}
@Override
public boolean remove(final String guid) throws SyncToolRuntimeException {
final Catalog catalog = get(guid);
if (catalog == null) {
// TODO: think of error collection to receive a notification about inexisting product here.
LOG.warn("Attempt to remove unknown Catalog with code: " + guid);
return false;
}
catalogService.remove(catalog);
return true;
}
@Override
public Catalog update(final Catalog mergedPersistence) throws SyncToolRuntimeException {
return catalogService.saveOrUpdate(mergedPersistence);
}
/**
* @param catalogService the catalogService to set
*/
public void setCatalogService(final CatalogService catalogService) {
this.catalogService = catalogService;
}
/**
* @param beanFactory the beanFactory to set
*/
public void setBeanFactory(final BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}
In spring/service/sync-service.xml
, add the bean definition for the DAO adapter:
<bean id="productDaoAdapter" class="com.elasticpath.tools.sync.target.impl.ProductDaoAdapterImpl" parent="abstractDaoAdapter">
<property name="productLookup" ref="productLookup"/>
<property name="productService" ref="productService"/>
<property name="beanFactory" ref="coreBeanFactory"/>
<property name="associatedTypes">
<list>
<value>com.elasticpath.domain.catalog.ProductAssociation</value>
</list>
</property>
</bean>
Modify the daoAdapterFactory
bean definition in spring/service/sync-service.xml
to include the DAO adapter bean in its syncAdapters
map:
<bean id="daoAdapterFactory" parent="abstractDaoAdapterFactory"/>
<bean id="abstractDaoAdapterFactory" abstract="true" class="com.elasticpath.tools.sync.target.DaoAdapterFactory">
<property name="syncAdapters">
<map>
<entry key="com.elasticpath.domain.catalog.Product" value-ref="productDaoAdapter"/>
<entry key="com.elasticpath.domain.catalog.ProductSku" value-ref="productSkuDaoAdapter"/>
<entry key="com.elasticpath.domain.rules.Rule" value-ref="promotionDaoAdapter"/>
<entry key="com.elasticpath.domain.catalog.Category" value-ref="categoryDaoAdapter"/>
<entry key="com.elasticpath.domain.catalog.Catalog" value-ref="catalogDaoAdapter"/>
<entry key="com.elasticpath.domain.catalog.ProductBundle" value-ref="productBundleDaoAdapter"/>
<entry key="com.elasticpath.domain.pricing.PriceListDescriptor" value-ref="priceListDescriptorDaoAdapter"/>
<entry key="com.elasticpath.domain.pricing.BaseAmount" value-ref="baseAmountDaoAdapter"/>
<entry key="com.elasticpath.domain.pricing.PriceListAssignment" value-ref="priceListAssignmentDaoAdapter"/>
<entry key="com.elasticpath.domain.contentspace.DynamicContent" value-ref="dynamicContentDaoAdapter"/>
<entry key="com.elasticpath.tags.domain.ConditionalExpression" value-ref="conditionalExpressionDaoAdapter"/>
<entry key="com.elasticpath.domain.targetedselling.DynamicContentDelivery" value-ref="dynamicContentDeliveryDaoAdapter"/>
<entry key="com.elasticpath.domain.catalog.ProductAssociation" value-ref="productAssociationDaoAdapter" />
<entry key="com.elasticpath.domain.rules.CouponConfig" value-ref="couponConfigDaoAdapter"/>
<entry key="com.elasticpath.domain.rules.Coupon" value-ref="couponDaoAdapter"/>
<entry key="com.elasticpath.domain.rules.CouponUsage" value-ref="couponUsageDaoAdapter"/>
<entry key="com.elasticpath.domain.skuconfiguration.SkuOption" value-ref="skuOptionDaoAdapter"/>
<entry key="com.elasticpath.domain.skuconfiguration.SkuOptionValue" value-ref="skuOptionValueDaoAdapter"/>
<entry key="com.elasticpath.domain.catalog.CategoryType" value-ref="categoryTypeDaoAdapter"/>
<entry key="com.elasticpath.domain.catalog.ProductType" value-ref="productTypeDaoAdapter"/>
<entry key="com.elasticpath.domain.catalog.Brand" value-ref="brandDaoAdapter"/>
<entry key="com.elasticpath.domain.attribute.Attribute" value-ref="attributeDaoAdapter"/>
<entry key="com.elasticpath.domain.catalog.ProductCategory" value-ref="productCategoryDaoAdapter"/>
</map>
</property>
</bean>
Handling association object types
Some objects represent associations between two or more objects. For example, ProductCategory
represents an association between a Product
and a Category
. These objects usually don’t have a GUID, so they are usually implemented as ValueObject instances. Because they do not have GUIDs, they are not automatically synced when their associated objects are synced.
Some association objects contain properties that must be included in the sync. For example, ProductCategory
has an isDefault
property, which needs to be included when syncing changes. If the association object contains properties that must be included in sync operations, update the valueObjectMerger
bean definition in spring/service/sync-service.xml
to include each property’s getter name:
<bean id="valueObjectMerger" class="com.elasticpath.tools.sync.merge.configuration.impl.ValueObjectMergerImpl">
<property name="syncUtils" ref="syncUtils" />
<property name="fieldMethodNames">
<map>
<entry key="com.elasticpath.domain.catalog.impl.ProductCategoryImpl">
<util:set id="productCategoryFieldMethodNames">
<value>isDefaultCategory</value>
<value>getFeaturedProductOrder</value>
</util:set>
</entry>
</map>
</property>
</bean>