Skip to content

Commit 5a1f8d2

Browse files
authored
#564 - Reference resolution of custom object key-value-document references (and set of) as an attribute of nested type. (#563)
1 parent ff8a981 commit 5a1f8d2

49 files changed

Lines changed: 2171 additions & 411 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/RELEASE_NOTES.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@
3535
[Javadoc](https://commercetools.github.io/commercetools-sync-java/v/2.2.0/) |
3636
[Jar](https://bintray.com/commercetools/maven/commercetools-sync-java/2.2.0)
3737
38-
- 🎉 **New Features** (1)
38+
- 🎉 **New Features** (2)
39+
- **Product Sync** - Added support for resolving `key-value-document` (custom object) references on attributes of type `Reference`, `Set` of `Reference`, `NestedType` or `Set` of `NestedType`. [#564](https://github.com/commercetools/commercetools-sync-java/issues/564)
3940
- Introduced new concept for the validation of `the drafts in batches` for each `Sync` instance, exposed with
4041
`BaseBatchValidator` implementations (i.e. ProductBatchValidator, CategoryBatchValidator). [#233](https://github.com/commercetools/commercetools-sync-java/issues/233)
41-
42+
4243
- ✨ **Enhancements** (2)
4344
- **Category Sync** - Passed category keys in batch to `cacheKeysToIds` method of `CategoryService` to avoid fetching all categories for every batch.
4445
[#235](https://github.com/commercetools/commercetools-sync-java/issues/235)

docs/usage/PRODUCT_SYNC.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,12 @@ order for the sync to resolve the actual ids of those references, those `key`s h
4646
> Note: Some references in the product like `state`, `customerGroup` of prices, and variant attributes with type `reference` do not support the `ResourceIdentifier` yet,
4747
for those references you need to provide the `key` value on the `id` field of the reference. This means that calling `getId()` on the
4848
reference would return its `key`.
49-
49+
50+
- For resolving `key-value-document` (custom object) references on attributes of type `Reference`, `Set` of `Reference`, `NestedType` or `Set` of `NestedType`, The `id` field of the reference in the attribute draft should be defined in the correct format.
51+
The correct format must have a vertical bar `|` character between the values of the container and key.
52+
For example, if the custom object has a container value `container` and key value `key`, the `id` field should be `container|key"`,
53+
also, the key and container value should match the pattern `[-_~.a-zA-Z0-9]+`.
54+
5055
4. Create a `sphereClient` [as described here](IMPORTANT_USAGE_TIPS.md#sphereclient-creation).
5156

5257
5. After the `sphereClient` is set up, a `ProductSyncOptions` should be built as follows:
@@ -198,7 +203,7 @@ attributes.
198203
| `“category”` | ✅ |
199204
| `“channel”` | ❌ |
200205
| `“customer”` | ❌ |
201-
| `“key-value-document”` | |
206+
| `“key-value-document”` | |
202207
| `“order”` | ❌ |
203208
| `“product”` | ✅ |
204209
| `“product-type”` | ✅ |

src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedCustomObjectsIT.java

Lines changed: 690 additions & 0 deletions
Large diffs are not rendered by default.

src/integration-test/java/com/commercetools/sync/integration/services/impl/CustomObjectServiceImplIT.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,17 +140,20 @@ void fetchMatchingCustomObjects_WithDifferentExistingCombinationOfKeysAndContain
140140
OLD_CUSTOM_OBJECT_CONTAINER + "_2", OLD_CUSTOM_OBJECT_VALUE);
141141

142142
final Set<CustomObjectCompositeIdentifier> customObjectCompositeIdentifiers = new HashSet<>();
143+
customObjectCompositeIdentifiers.add(CustomObjectCompositeIdentifier.of(
144+
OLD_CUSTOM_OBJECT_KEY + "_1", OLD_CUSTOM_OBJECT_CONTAINER + "_1"));
143145
customObjectCompositeIdentifiers.add(CustomObjectCompositeIdentifier.of(
144146
OLD_CUSTOM_OBJECT_KEY + "_1", OLD_CUSTOM_OBJECT_CONTAINER + "_2"));
145147
customObjectCompositeIdentifiers.add(CustomObjectCompositeIdentifier.of(
146148
OLD_CUSTOM_OBJECT_KEY + "_2", OLD_CUSTOM_OBJECT_CONTAINER + "_1"));
147149

150+
148151
final Set<CustomObject<JsonNode>> matchingCustomObjects = customObjectService
149152
.fetchMatchingCustomObjects(customObjectCompositeIdentifiers)
150153
.toCompletableFuture()
151154
.join();
152155

153-
assertThat(matchingCustomObjects).size().isEqualTo(2);
156+
assertThat(matchingCustomObjects).size().isEqualTo(3);
154157
assertThat(errorCallBackExceptions).isEmpty();
155158
assertThat(errorCallBackMessages).isEmpty();
156159

src/main/java/com/commercetools/sync/commons/utils/SyncUtils.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
import java.util.ArrayList;
1010
import java.util.List;
1111
import java.util.function.Supplier;
12+
import java.util.regex.Pattern;
1213

1314
public final class SyncUtils {
15+
private static final String UUID_REGEX = "[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
1416

1517
/**
1618
* Given a list of elements and a {@code batchSize}, this method distributes the elements into batches with the
@@ -83,6 +85,17 @@ public static <T extends WithKey> ResourceIdentifier<T> getResourceIdentifierWit
8385
return null;
8486
}
8587

88+
/**
89+
* Given an id as {@link String}, this method checks whether if it is in UUID format or not.
90+
*
91+
* @param id to check if it is in UUID format.
92+
* @return true if it is in UUID format, otherwise false.
93+
*/
94+
public static boolean isUuid(@Nonnull final String id) {
95+
final Pattern regexPattern = Pattern.compile(UUID_REGEX);
96+
return regexPattern.matcher(id).matches();
97+
}
98+
8699
private SyncUtils() {
87100
}
88101
}

src/main/java/com/commercetools/sync/customobjects/helpers/CustomObjectCompositeIdentifier.java

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ public final class CustomObjectCompositeIdentifier {
1818

1919
private final String key;
2020
private final String container;
21+
private static final String WRONG_FORMAT_ERROR_MESSAGE = "The custom object identifier value: \"%s\" does not have "
22+
+ "the correct format. The correct format must have a vertical bar \"|\" character "
23+
+ "between the container and key.";
2124

2225
private CustomObjectCompositeIdentifier(@Nonnull final String key,
2326
@Nonnull final String container) {
@@ -68,7 +71,24 @@ public static CustomObjectCompositeIdentifier of(@Nonnull final CustomObject cus
6871
*/
6972
@Nonnull
7073
public static CustomObjectCompositeIdentifier of(@Nonnull final String key, @Nonnull final String container) {
71-
return new CustomObjectCompositeIdentifier(key, container);
74+
return CustomObjectCompositeIdentifier.of(
75+
CustomObjectDraft.ofUnversionedUpsert(container, key, null));
76+
}
77+
78+
/**
79+
* Given a {@link String} with a format "container|key", creates a {@link CustomObjectCompositeIdentifier} using
80+
* the following fields from the supplied identifier as string.
81+
*
82+
* @param identifierAsString string format of the identifier to build composite Id.
83+
* @return a composite id comprised of the fields of the supplied {@code key} and {@code container}.
84+
*/
85+
@Nonnull
86+
public static CustomObjectCompositeIdentifier of(@Nonnull final String identifierAsString) {
87+
final String[] containerAndKey = identifierAsString.split("\\|");
88+
if (containerAndKey.length == 2) {
89+
return of(containerAndKey[1], containerAndKey[0]);
90+
}
91+
throw new IllegalArgumentException(format(WRONG_FORMAT_ERROR_MESSAGE, identifierAsString));
7292
}
7393

7494
public String getKey() {
@@ -79,11 +99,6 @@ public String getContainer() {
7999
return this.container;
80100
}
81101

82-
@Override
83-
public String toString() {
84-
return format("{key='%s', container='%s'}", key, container);
85-
}
86-
87102
@Override
88103
public boolean equals(final Object obj) {
89104
if (this == obj) {
@@ -100,4 +115,9 @@ public boolean equals(final Object obj) {
100115
public int hashCode() {
101116
return Objects.hash(key, container);
102117
}
118+
119+
@Override
120+
public String toString() {
121+
return format("%s|%s", container, key);
122+
}
103123
}

src/main/java/com/commercetools/sync/products/ProductSync.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
import com.commercetools.sync.commons.BaseSync;
55
import com.commercetools.sync.commons.exceptions.SyncException;
66
import com.commercetools.sync.commons.models.WaitingToBeResolved;
7+
import com.commercetools.sync.customobjects.CustomObjectSyncOptionsBuilder;
78
import com.commercetools.sync.products.helpers.ProductBatchValidator;
89
import com.commercetools.sync.products.helpers.ProductReferenceResolver;
910
import com.commercetools.sync.products.helpers.ProductSyncStatistics;
1011
import com.commercetools.sync.services.CategoryService;
1112
import com.commercetools.sync.services.ChannelService;
13+
import com.commercetools.sync.services.CustomObjectService;
1214
import com.commercetools.sync.services.CustomerGroupService;
1315
import com.commercetools.sync.services.ProductService;
1416
import com.commercetools.sync.services.ProductTypeService;
@@ -18,6 +20,7 @@
1820
import com.commercetools.sync.services.UnresolvedReferencesService;
1921
import com.commercetools.sync.services.impl.CategoryServiceImpl;
2022
import com.commercetools.sync.services.impl.ChannelServiceImpl;
23+
import com.commercetools.sync.services.impl.CustomObjectServiceImpl;
2124
import com.commercetools.sync.services.impl.CustomerGroupServiceImpl;
2225
import com.commercetools.sync.services.impl.ProductServiceImpl;
2326
import com.commercetools.sync.services.impl.ProductTypeServiceImpl;
@@ -93,21 +96,23 @@ public ProductSync(@Nonnull final ProductSyncOptions productSyncOptions) {
9396
new CustomerGroupServiceImpl(productSyncOptions),
9497
new TaxCategoryServiceImpl(TaxCategorySyncOptionsBuilder.of(productSyncOptions.getCtpClient()).build()),
9598
new StateServiceImpl(StateSyncOptionsBuilder.of(productSyncOptions.getCtpClient()).build()),
96-
new UnresolvedReferencesServiceImpl(productSyncOptions));
99+
new UnresolvedReferencesServiceImpl(productSyncOptions),
100+
new CustomObjectServiceImpl(CustomObjectSyncOptionsBuilder.of(productSyncOptions.getCtpClient()).build()));
97101
}
98102

99103
ProductSync(@Nonnull final ProductSyncOptions productSyncOptions, @Nonnull final ProductService productService,
100104
@Nonnull final ProductTypeService productTypeService, @Nonnull final CategoryService categoryService,
101105
@Nonnull final TypeService typeService, @Nonnull final ChannelService channelService,
102106
@Nonnull final CustomerGroupService customerGroupService,
103107
@Nonnull final TaxCategoryService taxCategoryService, @Nonnull final StateService stateService,
104-
@Nonnull final UnresolvedReferencesService unresolvedReferencesService) {
108+
@Nonnull final UnresolvedReferencesService unresolvedReferencesService,
109+
@Nonnull final CustomObjectService customObjectService) {
105110
super(new ProductSyncStatistics(), productSyncOptions);
106111
this.productService = productService;
107112
this.productTypeService = productTypeService;
108113
this.productReferenceResolver = new ProductReferenceResolver(getSyncOptions(), productTypeService,
109114
categoryService, typeService, channelService, customerGroupService, taxCategoryService, stateService,
110-
productService);
115+
productService, customObjectService);
111116
this.unresolvedReferencesService = unresolvedReferencesService;
112117
this.batchValidator = new ProductBatchValidator(getSyncOptions(), getStatistics());
113118
}

src/main/java/com/commercetools/sync/products/helpers/ProductBatchValidator.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package com.commercetools.sync.products.helpers;
22

33
import com.commercetools.sync.commons.helpers.BaseBatchValidator;
4+
import com.commercetools.sync.commons.utils.SyncUtils;
5+
import com.commercetools.sync.customobjects.helpers.CustomObjectCompositeIdentifier;
46
import com.commercetools.sync.products.ProductSyncOptions;
57
import com.fasterxml.jackson.databind.JsonNode;
68
import io.sphere.sdk.categories.Category;
9+
import io.sphere.sdk.customobjects.CustomObject;
710
import io.sphere.sdk.products.Product;
811
import io.sphere.sdk.products.ProductDraft;
912
import io.sphere.sdk.products.ProductVariantDraft;
@@ -15,6 +18,7 @@
1518
import javax.annotation.Nullable;
1619
import java.util.ArrayList;
1720
import java.util.Collection;
21+
import java.util.Collections;
1822
import java.util.HashSet;
1923
import java.util.List;
2024
import java.util.Objects;
@@ -27,6 +31,7 @@
2731
import static java.lang.String.format;
2832
import static java.util.Collections.emptySet;
2933
import static java.util.Objects.requireNonNull;
34+
import static java.util.stream.Collectors.toSet;
3035
import static org.apache.commons.lang3.StringUtils.isBlank;
3136

3237
public class ProductBatchValidator
@@ -181,6 +186,9 @@ private void collectReferencedKeysInAttributes(
181186

182187
referencedKeys.productTypeKeys.addAll(
183188
getReferencedKeysWithReferenceTypeId(variantDraft, ProductType.referenceTypeId()));
189+
190+
referencedKeys.customObjectCompositeIdentifiers.addAll(
191+
getReferencedKeysWithReferenceTypeId(variantDraft, CustomObject.referenceTypeId()));
184192
}
185193

186194
@Nonnull
@@ -274,6 +282,7 @@ public static class ReferencedKeys {
274282
private final Set<String> typeKeys = new HashSet<>();
275283
private final Set<String> channelKeys = new HashSet<>();
276284
private final Set<String> customerGroupKeys = new HashSet<>();
285+
private final Set<String> customObjectCompositeIdentifiers = new HashSet<>();
277286

278287
public Set<String> getProductKeys() {
279288
return productKeys;
@@ -306,5 +315,36 @@ public Set<String> getChannelKeys() {
306315
public Set<String> getCustomerGroupKeys() {
307316
return customerGroupKeys;
308317
}
318+
319+
/**
320+
* Applies mapping of {@link Set}&lt;{@link String}&gt; identifiers
321+
* (collected from reference id fields of product `key-value-document` references) to {@link Set}&lt;
322+
* {@link CustomObjectCompositeIdentifier}&gt; to be used for caching purposes.
323+
*
324+
* <p>Note: Invalid identifiers and uuid formatted identifiers will be filtered out.
325+
* Validation handling will be part of the {@link VariantReferenceResolver}.
326+
*
327+
* @return a result set with valid identifiers mapped to {@link CustomObjectCompositeIdentifier}.
328+
*/
329+
public Set<CustomObjectCompositeIdentifier> getCustomObjectCompositeIdentifiers() {
330+
if (!customObjectCompositeIdentifiers.isEmpty()) {
331+
return customObjectCompositeIdentifiers
332+
.stream()
333+
.map((identifierAsString) -> {
334+
if (SyncUtils.isUuid(identifierAsString)) {
335+
return null;
336+
}
337+
try {
338+
return CustomObjectCompositeIdentifier.of(identifierAsString);
339+
} catch (IllegalArgumentException ignored) {
340+
return null;
341+
}
342+
})
343+
.filter(Objects::nonNull)
344+
.collect(toSet());
345+
}
346+
347+
return Collections.emptySet();
348+
}
309349
}
310350
}

src/main/java/com/commercetools/sync/products/helpers/ProductReferenceResolver.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import com.commercetools.sync.commons.exceptions.ReferenceResolutionException;
44
import com.commercetools.sync.commons.helpers.BaseReferenceResolver;
5+
import com.commercetools.sync.customobjects.helpers.CustomObjectCompositeIdentifier;
56
import com.commercetools.sync.products.ProductSyncOptions;
67
import com.commercetools.sync.services.CategoryService;
78
import com.commercetools.sync.services.ChannelService;
9+
import com.commercetools.sync.services.CustomObjectService;
810
import com.commercetools.sync.services.CustomerGroupService;
911
import com.commercetools.sync.services.ProductService;
1012
import com.commercetools.sync.services.ProductTypeService;
@@ -54,6 +56,7 @@ public final class ProductReferenceResolver extends BaseReferenceResolver<Produc
5456
private final ChannelService channelService;
5557
private final ProductService productService;
5658
private final CustomerGroupService customerGroupService;
59+
private final CustomObjectService customObjectService;
5760

5861
public static final String FAILED_TO_RESOLVE_REFERENCE = "Failed to resolve '%s' resource identifier on "
5962
+ "ProductDraft with key:'%s'. Reason: %s";
@@ -80,6 +83,7 @@ public final class ProductReferenceResolver extends BaseReferenceResolver<Produc
8083
* @param stateService the service to fetch product states for reference resolution.
8184
* @param productService the service to fetch products for product reference resolution on reference
8285
* attributes.
86+
* @param customObjectService the service to fetch custom objects for reference resolution.
8387
*/
8488
public ProductReferenceResolver(@Nonnull final ProductSyncOptions productSyncOptions,
8589
@Nonnull final ProductTypeService productTypeService,
@@ -89,7 +93,8 @@ public ProductReferenceResolver(@Nonnull final ProductSyncOptions productSyncOpt
8993
@Nonnull final CustomerGroupService customerGroupService,
9094
@Nonnull final TaxCategoryService taxCategoryService,
9195
@Nonnull final StateService stateService,
92-
@Nonnull final ProductService productService) {
96+
@Nonnull final ProductService productService,
97+
@Nonnull final CustomObjectService customObjectService) {
9398
super(productSyncOptions);
9499
this.productTypeService = productTypeService;
95100
this.categoryService = categoryService;
@@ -99,9 +104,10 @@ public ProductReferenceResolver(@Nonnull final ProductSyncOptions productSyncOpt
99104
this.channelService = channelService;
100105
this.productService = productService;
101106
this.customerGroupService = customerGroupService;
107+
this.customObjectService = customObjectService;
102108
this.variantReferenceResolver =
103109
new VariantReferenceResolver(productSyncOptions, typeService, channelService, customerGroupService,
104-
productService, productTypeService, categoryService);
110+
productService, productTypeService, categoryService, customObjectService);
105111
}
106112

107113
/**
@@ -422,6 +428,12 @@ public CompletableFuture<Map<String, String>> populateKeyToIdCachesForReferenced
422428
futures.add(customerGroupService.cacheKeysToIds(customerGroupKeys));
423429
}
424430

431+
final Set<CustomObjectCompositeIdentifier> customObjectCompositeIdentifiers =
432+
referencedKeys.getCustomObjectCompositeIdentifiers();
433+
if (!customObjectCompositeIdentifiers.isEmpty()) {
434+
futures.add(customObjectService.cacheKeysToIds(customObjectCompositeIdentifiers));
435+
}
436+
425437
return collectionOfFuturesToFutureOfCollection(futures, toList())
426438
.thenApply(maps -> productKeys.isEmpty() ? Collections.emptyMap() : maps.get(0));
427439
}

0 commit comments

Comments
 (0)