Catalog Integration

The SAP Commerce Cartridge exports your products, variations, and categories to Constructor on a cron job. This guide explains what each export sends, how the FULL and DELTA strategies work, and how to customize the data.

Requirements

Before you start, install the cartridge and create at least one export target.

For assistance, please contact our team at [email protected].

Export targets

The cartridge exports through one job, constructorProductExportJob. Each export target is a ConstructorExportCronJob that runs this job for one store, in one language, into one Constructor index.

Each run does the following:

  1. On a FULL run with categoryRoots set, it first sends your category tree as item groups. It sends the categories as their own upload, and Constructor ingests them into your index before any product is sent.
  2. It sends your products, in pages of constructorcore.export.pageSize products.
  3. It completes the product upload. Constructor ingests the products into your index only after this step. If a run fails while it sends products, the products in your index stay as they were.

Target fields

FieldDescription
codeUnique name of the target.
job(code)Always constructorProductExportJob.
apiKeyThe index key, or a connection slug, of the Constructor index to export to. Stored encrypted. When empty, the cartridge reads the property constructor.api.key.<target code>.
destinationIdOptional. The ID of a ConsumedDestination in the API registry. Its url and its credential's clientSecret replace constructorcore.export.url and apiKey. Requires apiregistryservices.
baseStoreUidThe store whose online catalog versions are exported. Requires basecommerce.
catalogVersionsOptional. The catalog versions to export. When set, it overrides baseStoreUid. Use it for a selection the store cannot express.
sessionLanguageThe language the target exports. One target exports one language.
strategyDELTA or FULL. Empty runs as DELTA. See Ingestion strategies.
forceOptional. Allows Constructor to ingest an upload that would remove more than 50% of the index. See Overriding the 50% safeguard.
categoryRootsOptional. Codes of the top categories your store navigates by. See Categories.
mediaBaseUrlPrefix added to media paths to build image_url, for example https://shop.example.com. Absolute media URLs are sent unchanged.
productUrlPatternPattern for each product's url. {code} is replaced by the product code, for example https://shop.example.com/p/{code}.
lastSuccessfulStartTimeSet by the cartridge. The start time of the last DELTA run whose upload completed.
fieldMappingsExtra fields to send. See Customizing your catalog data.

What gets sent

Items

The cartridge sends one item per product:

Constructor fieldSAP Commerce source
idThe product code.
item_nameThe product name, in the target's sessionLanguage.
descriptionThe product description, in the target's sessionLanguage.
urlproductUrlPattern, with {code} replaced by the product code.
image_urlThe product picture, prefixed with mediaBaseUrl.
group_idsThe codes of the product's direct parent categories. When categoryRoots is set, only categories in that tree are sent.
keywordsThe product keywords.
activetrue when the product is approved.
metadataVariant values, and any extra fields you map.

Empty attributes are left out instead of being sent blank. Classification features are sent only when you map them as extra fields.

The cartridge sends catalog data only. It does not send price and stock.

Variations

A variant product with no variants of its own is sent as a variation. Its item_id is its root base product, so every variation belongs to the item for its root base product.

  • Variant levels in between are not sent as items or variations. Their variant values, such as size or color, go into the variation's metadata.
  • A variation is active only when it, every level above it, and its root base product are all approved.
  • A variation with no name or picture of its own takes the nearest one from the levels above it.

Categories

When you set categoryRoots on a target, a FULL run sends the categories under those roots as item groups. Each item group has an id (the category code), a name (the code when the category has no name), and a parent_id. When a category has more than one parent category in the tree, parent_id is the first one.

For example, on the stock electronics catalog, categoryRoots is 1. On the stock apparel catalog, it is categories,collections. Categories outside these trees, such as brands and classification classes, are not sent.

Only FULL runs send the category tree, because a category upload replaces every item group in the index. A target with only DELTA runs sends no categories, so run a FULL target first.

Ingestion strategies

Each target has its own strategy:

  • FULL sends the whole catalog and replaces the index section. This is the only strategy that removes products that are no longer in your catalog.
  • DELTA adds and updates only the products that changed since the last successful run, and never deletes or removes products.

A product that is no longer approved goes out with active set to false. A product that you delete from the catalog version stays in the index until the next FULL run removes it, because a DELTA run cannot find it.

For this reason, most indexes use two targets that share one store, language, and index key: a frequent DELTA target and an occasional FULL target. See Schedule the exports for an example.

Partial updates by last sync date

When a DELTA run completes its upload, it records its start time in lastSuccessfulStartTime. The next DELTA run sends only the products that changed after that time, minus constructorcore.export.deltaOverlapSeconds (300 seconds by default). The overlap covers changes saved while the previous run was reading the catalog. When lastSuccessfulStartTime is empty, a DELTA run sends everything.

A change to the target itself, such as its store, language, mediaBaseUrl, productUrlPattern, categoryRoots, or field mappings, only reaches products that change afterwards. To send everything again, clear lastSuccessfulStartTime in HAC > Console > Scripting Languages:

def t = cronJobService.getCronJob("constructorExportUkDelta")
t.lastSuccessfulStartTime = null
modelService.save(t)

You can also run a FULL target instead.

Overriding the 50% safeguard

Constructor rejects an upload that would deactivate or remove more than 50% of what the index holds. Constructor counts items, variations, and item groups separately. If items or variations are over the limit, Constructor rejects the product upload. If item groups are over the limit, it rejects the category upload. This safeguard stops a broken selection from emptying your index.

The rejection happens after the cron job ends. The cron job still shows as successful, the index does not change, and Integration > Catalog Uploads in the Constructor dashboard shows the upload as rejected.

When the drop is intended, for example after you retire half your range, or on the first FULL run into an index that another source filled, use force:

  1. Set force on the target.
  2. Run the target once by hand.
  3. Unset force.
INSERT_UPDATE ConstructorExportCronJob;code[unique=true];force
;constructorExportUkFull;true

Nothing resets force automatically, and a target with force set logs a warning on every run. Do not leave it on a scheduled target.

A rejected DELTA upload still records lastSuccessfulStartTime, so with force alone, the next run sends only what changed after the rejected one. To have the next DELTA run bypass the safeguard and send everything, set force and also clear lastSuccessfulStartTime.

Multiple indexes and languages

One target exports one language to one index. A store in three languages needs three targets, three index keys, and three indexes. Two targets can name the same store, and only code must be unique.

Use baseStoreUid to export a store's online catalog versions. Use catalogVersions instead when you need a selection the store cannot express:

INSERT_UPDATE ConstructorExportCronJob;code[unique=true];job(code);catalogVersions(catalog(id),version);sessionLanguage(isocode);apiKey
;constructorExportElectronicsEn;constructorProductExportJob;electronicsProductCatalog:Online;en;<your index key>

Customizing your catalog data

Adding metadata

To send extra fields, add one ConstructorFieldMapping per field, linked to its target. No code is needed.

A mapping belongs to one target. Add each mapping to every target that writes to the index, both DELTA and FULL. Otherwise a FULL run replaces the index section without those fields.

INSERT_UPDATE ConstructorFieldMapping;exportTarget(code)[unique=true];outputKey[unique=true];kind(code)[unique=true];appliesTo(code)[unique=true];sourceType(code);source;localized
;constructorExportUkDelta;ean;METADATA;ITEMS;ATTRIBUTE;ean;false
;constructorExportUkDelta;brand;METADATA;BOTH;ATTRIBUTE;manufacturerName;false
;constructorExportUkDelta;description;TOP_LEVEL;ITEMS;ATTRIBUTE;summary;true
;constructorExportUkDelta;colour;METADATA;ITEMS;CLASSIFICATION_ATTRIBUTE;Colour of product, 1766;true
;constructorExportUkDelta;margin_band;METADATA;BOTH;PROVIDER;myMarginBandProvider;false
;constructorExportUkFull;ean;METADATA;ITEMS;ATTRIBUTE;ean;false
;constructorExportUkFull;brand;METADATA;BOTH;ATTRIBUTE;manufacturerName;false
;constructorExportUkFull;description;TOP_LEVEL;ITEMS;ATTRIBUTE;summary;true
;constructorExportUkFull;colour;METADATA;ITEMS;CLASSIFICATION_ATTRIBUTE;Colour of product, 1766;true
;constructorExportUkFull;margin_band;METADATA;BOTH;PROVIDER;myMarginBandProvider;false
ColumnDescription
exportTargetThe target that sends the field.
outputKeyThe key the value is sent under.
kindMETADATA sends the value under metadata. TOP_LEVEL replaces one of item_name, description, url, image_url, or keywords when the mapping has a value.
appliesToITEMS, VARIATIONS, or BOTH (the default). A TOP_LEVEL description must use ITEMS.
sourceTypeATTRIBUTE (a product attribute qualifier), CLASSIFICATION_ATTRIBUTE (a classification attribute code, or its full feature code), or PROVIDER (the Spring bean ID of a provider, see below).
sourceThe attribute qualifier, classification attribute code, or bean ID.
localizedtrue reads the value in the target's sessionLanguage.

For a variation, a mapping reads the variant first, then each level above it up to the root base product. The nearest level with a value wins. For example, a brand mapped from manufacturerName reaches every variation, even when only the root base product has a value.

Only the five keys listed under kind can be sent at the top level. A TOP_LEVEL mapping for any other key fails the run. Send other fields as METADATA. Dates are sent as ISO-8601 text and maps as objects, under metadata only.

These mappings fail the run before anything is sent:

  • A mapping that reads an attribute no product type declares.
  • A localized mapping for an attribute that is not localized.
  • A classification attribute that is not assigned to any classification class.
  • A PROVIDER mapping whose bean is missing.
  • A TOP_LEVEL mapping for a key other than the five above, or a TOP_LEVEL description that does not use ITEMS.
📘

Facets

The cartridge does not create facets. To filter on a value, send it as METADATA, then configure a facet on its key in the Constructor dashboard.

🚧

Apply new mappings with a FULL run

A DELTA target applies a new mapping only to products that change afterwards. Run a FULL target after you add or change a mapping.

Writing a value provider

When an attribute or a classification attribute cannot provide a value, write a provider. Implement com.constructor.core.ConstructorFieldValueProvider in an extension of your own:

public interface ConstructorFieldValueProvider
{
	Object getValue(ProductModel product, FieldMapping field, Locale locale);
}

locale is the target's language for a localized mapping, and null otherwise. Return text, a number, a boolean, an enum value, or a collection of those, or null when the product has no value.

Declare the provider as a Spring bean, then use its bean ID as the source of a PROVIDER mapping:

<bean id="myMarginBandProvider" class="com.example.MarginBandProvider"/>

Overriding the payload

For changes that mappings cannot express, override these four methods from an extension of your own that requires constructorcore:

MethodWhat it decides
ProductPayload.active(ProductModel)Whether a product is active. Default: approved.
ProductExportJob.payload(cronJob)Which ProductPayload a run uses, so it can return your subclass. Copy the target's mediaBaseUrl and productUrlPattern onto it, as the default does.
ProductExportJob.categoryPayload(cronJob, tree)Which CategoryPayload a FULL run sends the category tree through.
CategoryPayload.data(CategoryModel)The data of each item group. Default: none.

For example, to add a landing page URL to every item group:

public class MyExportJob extends ProductExportJob
{
	@Override
	protected CategoryPayload categoryPayload(final ConstructorExportCronJobModel cronJob,
			final List<CategoryModel> tree)
	{
		return new CategoryPayload(tree)
		{
			@Override
			protected Map<String, Object> data(final CategoryModel category)
			{
				return Map.of("landing_url", "https://shop.example.com/c/" + category.getCode());
			}
		};
	}
}

Then redefine the job's bean in your extension's *-spring.xml, with your class and the same properties. Your extension loads after constructorcore, so its definition wins, and every target keeps using constructorProductExportJob:

<bean id="constructorProductExportJob" class="com.example.MyExportJob" parent="abstractJobPerformable">
    <property name="productLister" ref="constructorProductLister"/>
    <property name="categoryLister" ref="constructorCategoryLister"/>
    <property name="destinationLookup" ref="constructorConsumedDestinationLookup"/>
    <property name="feedClient" ref="constructorCartridgeFeedClient"/>
    <property name="configurationService" ref="configurationService"/>
    <property name="typeService" ref="typeService"/>
    <property name="variantsService" ref="variantsService"/>
    <property name="fieldValueReader" ref="constructorFieldValueReader"/>
</bean>

data values convert the same way as mapped fields. A value with no feed form, such as an item reference, fails the run.

Settings reference

These properties apply to every target on the system. Set them in local.properties, or in the environment properties in the CCv2 Cloud Portal.

PropertyDefaultDescription
constructorcore.export.urlemptyRequired. The host to send data to: https://connect.cnstrc.com.
constructorcore.export.apiTokenemptyRequired. Your company's API token.
constructorcore.export.sectionProductsThe index section to write into.
constructorcore.export.pageSize500Products per request. Lower it if requests are too large: Constructor accepts up to 50 MB per request.
constructorcore.export.deltaOverlapSeconds300How far before lastSuccessfulStartTime a DELTA run reaches back.
constructorcore.export.connectTimeoutSeconds10Connection timeout, in seconds.
constructorcore.export.requestTimeoutSeconds60Timeout for each request, in seconds.
constructorcore.export.version1.0.0The upload format version. Leave it unchanged.
constructor.api.key.<target code>noneThe index key for a target whose apiKey is empty.

Did this page help you?