Extending the Data Sync Tool
Extending the 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.
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, its 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"/commerce-legacy/> <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>
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:
Creating the DAO adapter
The next step is to create the DAO 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"/commerce-legacy/> <property name="productService" ref="productService"/commerce-legacy/> <property name="beanFactory" ref="coreBeanFactory"/commerce-legacy/> <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"/commerce-legacy/> <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"/commerce-legacy/> <entry key="com.elasticpath.domain.catalog.ProductSku" value-ref="productSkuDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.rules.Rule" value-ref="promotionDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.catalog.Category" value-ref="categoryDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.catalog.Catalog" value-ref="catalogDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.catalog.ProductBundle" value-ref="productBundleDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.pricing.PriceListDescriptor" value-ref="priceListDescriptorDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.pricing.BaseAmount" value-ref="baseAmountDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.pricing.PriceListAssignment" value-ref="priceListAssignmentDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.contentspace.DynamicContent" value-ref="dynamicContentDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.tags.domain.ConditionalExpression" value-ref="conditionalExpressionDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.targetedselling.DynamicContentDelivery" value-ref="dynamicContentDeliveryDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.catalog.ProductAssociation" value-ref="productAssociationDaoAdapter" /> <entry key="com.elasticpath.domain.rules.CouponConfig" value-ref="couponConfigDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.rules.Coupon" value-ref="couponDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.rules.CouponUsage" value-ref="couponUsageDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.skuconfiguration.SkuOption" value-ref="skuOptionDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.skuconfiguration.SkuOptionValue" value-ref="skuOptionValueDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.catalog.CategoryType" value-ref="categoryTypeDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.catalog.ProductType" value-ref="productTypeDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.catalog.Brand" value-ref="brandDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.attribute.Attribute" value-ref="attributeDaoAdapter"/commerce-legacy/> <entry key="com.elasticpath.domain.catalog.ProductCategory" value-ref="productCategoryDaoAdapter"/commerce-legacy/> </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>