All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.commercetools.sync.customers.utils.CustomerUpdateActionUtils Maven / Gradle / Ivy

package com.commercetools.sync.customers.utils;

import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction;
import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActionForReferences;
import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang3.StringUtils.isBlank;

import com.commercetools.api.models.common.Address;
import com.commercetools.api.models.common.BaseAddress;
import com.commercetools.api.models.customer.Customer;
import com.commercetools.api.models.customer.CustomerAddAddressAction;
import com.commercetools.api.models.customer.CustomerAddAddressActionBuilder;
import com.commercetools.api.models.customer.CustomerAddBillingAddressIdAction;
import com.commercetools.api.models.customer.CustomerAddBillingAddressIdActionBuilder;
import com.commercetools.api.models.customer.CustomerAddShippingAddressIdAction;
import com.commercetools.api.models.customer.CustomerAddShippingAddressIdActionBuilder;
import com.commercetools.api.models.customer.CustomerAddStoreAction;
import com.commercetools.api.models.customer.CustomerAddStoreActionBuilder;
import com.commercetools.api.models.customer.CustomerChangeAddressAction;
import com.commercetools.api.models.customer.CustomerChangeAddressActionBuilder;
import com.commercetools.api.models.customer.CustomerChangeEmailAction;
import com.commercetools.api.models.customer.CustomerChangeEmailActionBuilder;
import com.commercetools.api.models.customer.CustomerDraft;
import com.commercetools.api.models.customer.CustomerRemoveAddressAction;
import com.commercetools.api.models.customer.CustomerRemoveAddressActionBuilder;
import com.commercetools.api.models.customer.CustomerRemoveBillingAddressIdAction;
import com.commercetools.api.models.customer.CustomerRemoveBillingAddressIdActionBuilder;
import com.commercetools.api.models.customer.CustomerRemoveShippingAddressIdAction;
import com.commercetools.api.models.customer.CustomerRemoveShippingAddressIdActionBuilder;
import com.commercetools.api.models.customer.CustomerRemoveStoreAction;
import com.commercetools.api.models.customer.CustomerRemoveStoreActionBuilder;
import com.commercetools.api.models.customer.CustomerSetCompanyNameAction;
import com.commercetools.api.models.customer.CustomerSetCompanyNameActionBuilder;
import com.commercetools.api.models.customer.CustomerSetCustomerGroupAction;
import com.commercetools.api.models.customer.CustomerSetCustomerGroupActionBuilder;
import com.commercetools.api.models.customer.CustomerSetCustomerNumberAction;
import com.commercetools.api.models.customer.CustomerSetCustomerNumberActionBuilder;
import com.commercetools.api.models.customer.CustomerSetDateOfBirthAction;
import com.commercetools.api.models.customer.CustomerSetDateOfBirthActionBuilder;
import com.commercetools.api.models.customer.CustomerSetDefaultBillingAddressAction;
import com.commercetools.api.models.customer.CustomerSetDefaultBillingAddressActionBuilder;
import com.commercetools.api.models.customer.CustomerSetDefaultShippingAddressAction;
import com.commercetools.api.models.customer.CustomerSetDefaultShippingAddressActionBuilder;
import com.commercetools.api.models.customer.CustomerSetExternalIdAction;
import com.commercetools.api.models.customer.CustomerSetExternalIdActionBuilder;
import com.commercetools.api.models.customer.CustomerSetFirstNameAction;
import com.commercetools.api.models.customer.CustomerSetFirstNameActionBuilder;
import com.commercetools.api.models.customer.CustomerSetLastNameAction;
import com.commercetools.api.models.customer.CustomerSetLastNameActionBuilder;
import com.commercetools.api.models.customer.CustomerSetLocaleAction;
import com.commercetools.api.models.customer.CustomerSetLocaleActionBuilder;
import com.commercetools.api.models.customer.CustomerSetMiddleNameAction;
import com.commercetools.api.models.customer.CustomerSetMiddleNameActionBuilder;
import com.commercetools.api.models.customer.CustomerSetSalutationAction;
import com.commercetools.api.models.customer.CustomerSetSalutationActionBuilder;
import com.commercetools.api.models.customer.CustomerSetStoresAction;
import com.commercetools.api.models.customer.CustomerSetStoresActionBuilder;
import com.commercetools.api.models.customer.CustomerSetTitleAction;
import com.commercetools.api.models.customer.CustomerSetTitleActionBuilder;
import com.commercetools.api.models.customer.CustomerSetVatIdAction;
import com.commercetools.api.models.customer.CustomerSetVatIdActionBuilder;
import com.commercetools.api.models.customer.CustomerUpdateAction;
import com.commercetools.api.models.customer_group.CustomerGroup;
import com.commercetools.api.models.store.StoreKeyReference;
import com.commercetools.api.models.store.StoreResourceIdentifier;
import com.commercetools.api.models.store.StoreResourceIdentifierBuilder;
import com.commercetools.sync.commons.exceptions.SyncException;
import com.commercetools.sync.customers.CustomerSyncOptions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public final class CustomerUpdateActionUtils {

  public static final String CUSTOMER_NUMBER_EXISTS_WARNING =
      "Customer with key: \"%s\" has "
          + "already a customer number: \"%s\", once it's set it cannot be changed. "
          + "Hereby, the update action is not created.";

  private CustomerUpdateActionUtils() {}

  /**
   * Compares the {@code email} values of a {@link Customer} and a {@link CustomerDraft} and returns
   * an {@link Optional} of update action, which would contain the {@code "changeEmail"} {@link
   * CustomerChangeEmailAction}. If both {@link Customer} and {@link CustomerDraft} have the same
   * {@code email} values, then no update action is needed and empty optional will be returned.
   *
   * @param oldCustomer the customer that should be updated.
   * @param newCustomer the customer draft that contains the new email.
   * @return optional containing update action or empty optional if emails are identical.
   */
  @Nonnull
  public static Optional buildChangeEmailUpdateAction(
      @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) {

    return buildUpdateAction(
        oldCustomer.getEmail(),
        newCustomer.getEmail(),
        () -> CustomerChangeEmailActionBuilder.of().email(newCustomer.getEmail()).build());
  }

  /**
   * Compares the {@code firstName} values of a {@link Customer} and a {@link CustomerDraft} and
   * returns an {@link Optional} of update action, which would contain the {@code "setFirstName"}
   * {@link CustomerSetFirstNameAction}. If both {@link Customer} and {@link CustomerDraft} have the
   * same {@code firstName} values, then no update action is needed and empty optional will be
   * returned.
   *
   * @param oldCustomer the customer that should be updated.
   * @param newCustomer the customer draft that contains the new first name.
   * @return optional containing update action or empty optional if first names are identical.
   */
  @Nonnull
  public static Optional buildSetFirstNameUpdateAction(
      @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) {

    return buildUpdateAction(
        oldCustomer.getFirstName(),
        newCustomer.getFirstName(),
        () -> CustomerSetFirstNameActionBuilder.of().firstName(newCustomer.getFirstName()).build());
  }

  /**
   * Compares the {@code lastName} values of a {@link Customer} and a {@link CustomerDraft} and
   * returns an {@link Optional} of update action, which would contain the {@code "setLastName"}
   * {@link CustomerSetLastNameAction}. If both {@link Customer} and {@link CustomerDraft} have the
   * same {@code lastName} values, then no update action is needed and empty optional will be
   * returned.
   *
   * @param oldCustomer the customer that should be updated.
   * @param newCustomer the customer draft that contains the new last name.
   * @return optional containing update action or empty optional if last names are identical.
   */
  @Nonnull
  public static Optional buildSetLastNameUpdateAction(
      @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) {

    return buildUpdateAction(
        oldCustomer.getLastName(),
        newCustomer.getLastName(),
        () -> CustomerSetLastNameActionBuilder.of().lastName(newCustomer.getLastName()).build());
  }

  /**
   * Compares the {@code middleName} values of a {@link Customer} and a {@link CustomerDraft} and
   * returns an {@link Optional} of update action, which would contain the {@code "setMiddleName"}
   * {@link CustomerSetMiddleNameAction}. If both {@link Customer} and {@link CustomerDraft} have
   * the same {@code middleName} values, then no update action is needed and empty optional will be
   * returned.
   *
   * @param oldCustomer the customer that should be updated.
   * @param newCustomer the customer draft that contains the new middle name.
   * @return optional containing update action or empty optional if middle names are identical.
   */
  @Nonnull
  public static Optional buildSetMiddleNameUpdateAction(
      @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) {

    return buildUpdateAction(
        oldCustomer.getMiddleName(),
        newCustomer.getMiddleName(),
        () ->
            CustomerSetMiddleNameActionBuilder.of()
                .middleName(newCustomer.getMiddleName())
                .build());
  }

  /**
   * Compares the {@code title} values of a {@link Customer} and a {@link CustomerDraft} and returns
   * an {@link Optional} of update action, which would contain the {@code "setTitle"} {@link
   * CustomerSetTitleAction}. If both {@link Customer} and {@link CustomerDraft} have the same
   * {@code title} values, then no update action is needed and empty optional will be returned.
   *
   * @param oldCustomer the customer that should be updated.
   * @param newCustomer the customer draft that contains the new title.
   * @return optional containing update action or empty optional if titles are identical.
   */
  @Nonnull
  public static Optional buildSetTitleUpdateAction(
      @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) {

    return buildUpdateAction(
        oldCustomer.getTitle(),
        newCustomer.getTitle(),
        () -> CustomerSetTitleActionBuilder.of().title(newCustomer.getTitle()).build());
  }

  /**
   * Compares the {@code salutation} values of a {@link Customer} and a {@link CustomerDraft} and
   * returns an {@link Optional} of update action, which would contain the {@code "SetSalutation"}
   * {@link CustomerSetSalutationAction}. If both {@link Customer} and {@link CustomerDraft} have
   * the same {@code salutation} values, then no update action is needed and empty optional will be
   * returned.
   *
   * @param oldCustomer the Customer that should be updated.
   * @param newCustomer the Customer draft that contains the new salutation.
   * @return optional containing update action or empty optional if salutations are identical.
   */
  @Nonnull
  public static Optional buildSetSalutationUpdateAction(
      @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) {

    return buildUpdateAction(
        oldCustomer.getSalutation(),
        newCustomer.getSalutation(),
        () ->
            CustomerSetSalutationActionBuilder.of()
                .salutation(newCustomer.getSalutation())
                .build());
  }

  /**
   * Compares the {@code customerNumber} values of a {@link Customer} and a {@link CustomerDraft}
   * and returns an {@link Optional} of update action, which would contain the {@code
   * "setCustomerNumber"} {@link CustomerSetCustomerNumberAction}. If both {@link Customer} and
   * {@link CustomerDraft} have the same {@code customerNumber} values, then no update action is
   * needed and empty optional will be returned.
   *
   * 

Note: Customer number should be unique across a project. Once it's set it cannot be changed. * For this case, warning callback will be triggered and an empty optional will be returned. * * @param oldCustomer the customer that should be updated. * @param newCustomer the customer draft that contains the new customer number. * @param syncOptions responsible for supplying the sync options to the sync utility method. It is * used for triggering the warning callback when trying to change an existing customer number. * @return optional containing update action or empty optional if customer numbers are identical. */ @Nonnull public static Optional buildSetCustomerNumberUpdateAction( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer, @Nonnull final CustomerSyncOptions syncOptions) { final Optional setCustomerNumberAction = buildUpdateAction( oldCustomer.getCustomerNumber(), newCustomer.getCustomerNumber(), () -> CustomerSetCustomerNumberActionBuilder.of() .customerNumber(newCustomer.getCustomerNumber()) .build()); if (setCustomerNumberAction.isPresent() && !isBlank(oldCustomer.getCustomerNumber())) { syncOptions.applyWarningCallback( new SyncException( format( CUSTOMER_NUMBER_EXISTS_WARNING, oldCustomer.getKey(), oldCustomer.getCustomerNumber())), oldCustomer, newCustomer); return Optional.empty(); } return setCustomerNumberAction; } /** * Compares the {@code externalId} values of a {@link Customer} and a {@link CustomerDraft} and * returns an {@link Optional} of update action, which would contain the {@code "setExternalId"} * {@link CustomerSetExternalIdAction}. If both {@link Customer} and {@link CustomerDraft} have * the same {@code externalId} values, then no update action is needed and empty optional will be * returned. * * @param oldCustomer the customer that should be updated. * @param newCustomer the customer draft that contains the new external id. * @return optional containing update action or empty optional if external ids are identical. */ @Nonnull public static Optional buildSetExternalIdUpdateAction( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { return buildUpdateAction( oldCustomer.getExternalId(), newCustomer.getExternalId(), () -> CustomerSetExternalIdActionBuilder.of() .externalId(newCustomer.getExternalId()) .build()); } /** * Compares the {@code companyName} values of a {@link Customer} and a {@link CustomerDraft} and * returns an {@link Optional} of update action, which would contain the {@code "setCompanyName"} * {@link CustomerSetCompanyNameAction}. If both {@link Customer} and {@link CustomerDraft} have * the same {@code companyName} values, then no update action is needed and empty optional will be * returned. * * @param oldCustomer the customer that should be updated. * @param newCustomer the customer draft that contains the new company name. * @return optional containing update action or empty optional if company names are identical. */ @Nonnull public static Optional buildSetCompanyNameUpdateAction( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { return buildUpdateAction( oldCustomer.getCompanyName(), newCustomer.getCompanyName(), () -> CustomerSetCompanyNameActionBuilder.of() .companyName(newCustomer.getCompanyName()) .build()); } /** * Compares the {@code dateOfBirth} values of a {@link Customer} and a {@link CustomerDraft} and * returns an {@link Optional} of update action, which would contain the {@code "setDateOfBirth"} * {@link CustomerSetDateOfBirthAction}. If both {@link Customer} and {@link CustomerDraft} have * the same {@code dateOfBirth} values, then no update action is needed and empty optional will be * returned. * * @param oldCustomer the customer that should be updated. * @param newCustomer the customer draft that contains the new date of birth. * @return optional containing update action or empty optional if dates of birth are identical. */ @Nonnull public static Optional buildSetDateOfBirthUpdateAction( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { return buildUpdateAction( oldCustomer.getDateOfBirth(), newCustomer.getDateOfBirth(), () -> CustomerSetDateOfBirthActionBuilder.of() .dateOfBirth(newCustomer.getDateOfBirth()) .build()); } /** * Compares the {@code vatId} values of a {@link Customer} and a {@link CustomerDraft} and returns * an {@link Optional} of update action, which would contain the {@code "setVatId"} {@link * CustomerSetVatIdAction}. If both {@link Customer} and {@link CustomerDraft} have the same * {@code vatId} values, then no update action is needed and empty optional will be returned. * * @param oldCustomer the customer that should be updated. * @param newCustomer the customer draft that contains the new vat id. * @return optional containing update action or empty optional if vat ids are identical. */ @Nonnull public static Optional buildSetVatIdUpdateAction( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { return buildUpdateAction( oldCustomer.getVatId(), newCustomer.getVatId(), () -> CustomerSetVatIdActionBuilder.of().vatId(newCustomer.getVatId()).build()); } /** * Compares the {@code locale} values of a {@link Customer} and a {@link CustomerDraft} and * returns an {@link Optional} of update action, which would contain the {@code "setLocale"} * {@link CustomerSetLocaleAction}. If both {@link Customer} and {@link CustomerDraft} have the * same {@code locale} values, then no update action is needed and empty optional will be * returned. * * @param oldCustomer the customer that should be updated. * @param newCustomer the customer draft that contains the new locale. * @return optional containing update action or empty optional if locales are identical. */ @Nonnull public static Optional buildSetLocaleUpdateAction( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { return buildUpdateAction( oldCustomer.getLocale(), newCustomer.getLocale(), () -> CustomerSetLocaleActionBuilder.of().locale(newCustomer.getLocale()).build()); } /** * Compares the {@link CustomerGroup} references of an old {@link Customer} and new {@link * CustomerDraft}. If they are different - return {@link CustomerSetCustomerGroupAction} update * action. * *

If the old value is set, but the new one is empty - the command will unset the customer * group. * * @param oldCustomer the customer that should be updated. * @param newCustomer the customer draft with new {@link CustomerGroup} reference. * @return An optional with {@link CustomerUpdateAction} update action. */ @Nonnull public static Optional buildSetCustomerGroupUpdateAction( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { return buildUpdateActionForReferences( oldCustomer.getCustomerGroup(), newCustomer.getCustomerGroup(), () -> CustomerSetCustomerGroupActionBuilder.of() .customerGroup(newCustomer.getCustomerGroup()) .build()); } /** * Compares the stores of a {@link Customer} and a {@link CustomerDraft}. It returns a {@link * List} of {@link CustomerUpdateAction} as a result. If no update action is needed, for example * in case where both the {@link Customer} and the {@link CustomerDraft} have the identical * stores, an empty {@link List} is returned. * *

Note: Null values of the stores are filtered out. * * @param oldCustomer the customer which should be updated. * @param newCustomer the customer draft where we get the new data. * @return A list of customer store-related update actions. */ @Nonnull public static List buildStoreUpdateActions( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { final List oldStores = oldCustomer.getStores(); final List newStores = newCustomer.getStores(); return buildSetStoreUpdateAction(oldStores, newStores) .map(Collections::singletonList) .orElseGet(() -> prepareStoreActions(oldStores, newStores)); } private static List prepareStoreActions( @Nullable final List oldStores, @Nullable final List newStores) { if (oldStores != null && newStores != null) { final List removeStoreUpdateActions = buildRemoveStoreUpdateActions(oldStores, newStores); final List addStoreUpdateActions = buildAddStoreUpdateActions(oldStores, newStores); if (!removeStoreUpdateActions.isEmpty() && !addStoreUpdateActions.isEmpty()) { return buildSetStoreUpdateAction(newStores) .map(Collections::singletonList) .orElseGet(Collections::emptyList); } return removeStoreUpdateActions.isEmpty() ? addStoreUpdateActions : removeStoreUpdateActions; } return emptyList(); } /** * Compares the {@link List} of {@link StoreKeyReference}s and {@link StoreResourceIdentifier}s of * a {@link CustomerDraft} and a {@link Customer}. It returns a {@link CustomerSetStoresAction} * update action as a result. If both the {@link Customer} and the {@link CustomerDraft} have the * same set of stores, then no update actions are needed and hence an empty {@link List} is * returned. * *

Note: Null values of the stores are filtered out. * * @param oldStores the stores which should be updated. * @param newStores the stores where we get the new store. * @return A list containing the update actions or an empty list if the store references are * identical. */ @Nonnull private static Optional buildSetStoreUpdateAction( @Nullable final List oldStores, @Nullable final List newStores) { if (oldStores != null && !oldStores.isEmpty()) { if (newStores == null || newStores.isEmpty()) { return Optional.of(CustomerSetStoresActionBuilder.of().stores(emptyList()).build()); } } else if (newStores != null && !newStores.isEmpty()) { return buildSetStoreUpdateAction(newStores); } return Optional.empty(); } private static Optional buildSetStoreUpdateAction( @Nonnull final List newStores) { final List stores = newStores.stream().filter(Objects::nonNull).collect(toList()); if (!stores.isEmpty()) { return Optional.of(CustomerSetStoresActionBuilder.of().stores(stores).build()); } return Optional.empty(); } /** * Compares the {@link List} of {@link StoreKeyReference}s and {@link StoreResourceIdentifier}s of * a {@link CustomerDraft} and a {@link Customer}. It returns a {@link List} of {@link * CustomerRemoveStoreAction} update actions as a result, if the old store needs to be removed * from a customer to have the same set of stores as the new customer. If both the {@link * Customer} and the {@link CustomerDraft} have the same set of stores, then no update actions are * needed and hence an empty {@link List} is returned. * *

Note: Null values of the stores are filtered out. * * @param oldStores the stores which should be updated. * @param newStores the stores where we get the new store. * @return A list containing the update actions or an empty list if the store references are * identical. */ @Nonnull public static List buildRemoveStoreUpdateActions( @Nonnull final List oldStores, @Nonnull final List newStores) { final Map newStoreKeyToStoreMap = newStores.stream() .filter(Objects::nonNull) .filter(storeResourceIdentifier -> storeResourceIdentifier.getKey() != null) .collect(toMap(StoreResourceIdentifier::getKey, identity())); return oldStores.stream() .filter(Objects::nonNull) .filter(storeKeyReference -> !newStoreKeyToStoreMap.containsKey(storeKeyReference.getKey())) .map( storeKeyReference -> CustomerRemoveStoreActionBuilder.of() .store( StoreResourceIdentifierBuilder.of().key(storeKeyReference.getKey()).build()) .build()) .collect(toList()); } /** * Compares the {@link List} of {@link StoreKeyReference}s and {@link StoreResourceIdentifier}s of * a {@link CustomerDraft} and a {@link Customer}. It returns a {@link List} of {@link * CustomerAddStoreAction} update actions as a result, if the old store needs to be added to a * customer to have the same set of stores as the new customer. If both the {@link Customer} and * the {@link CustomerDraft} have the same set of stores, then no update actions are needed and * hence an empty {@link List} is returned. * *

Note: Null values of the stores are filtered out. * * @param oldStores the stores which should be updated. * @param newStores the stores where we get the new store. * @return A list containing the update actions or an empty list if the store references are * identical. */ @Nonnull public static List buildAddStoreUpdateActions( @Nonnull final List oldStores, @Nonnull final List newStores) { final Map oldStoreKeyToStoreMap = oldStores.stream() .filter(Objects::nonNull) .collect(toMap(StoreKeyReference::getKey, identity())); return newStores.stream() .filter(Objects::nonNull) .filter( storeResourceIdentifier -> !oldStoreKeyToStoreMap.containsKey(storeResourceIdentifier.getKey())) .map( storeResourceIdentifier -> CustomerAddStoreActionBuilder.of().store(storeResourceIdentifier).build()) .collect(toList()); } /** * Compares the addresses of a {@link Customer} and a {@link CustomerDraft}. It returns a {@link * List} of {@link CustomerUpdateAction} as a result. If both the {@link Customer} and the {@link * CustomerDraft} have the same set of addresses, then no update actions are needed and hence an * empty {@link List} is returned. * * @param oldCustomer the customer which should be updated. * @param newCustomer the customer draft where we get the new data. * @return A list of customer address-related update actions. */ @Nonnull public static List buildAllAddressUpdateActions( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { final List addressActions = new ArrayList<>(); final List removeAddressActions = buildRemoveAddressUpdateActions(oldCustomer, newCustomer); addressActions.addAll(removeAddressActions); addressActions.addAll(buildChangeAddressUpdateActions(oldCustomer, newCustomer)); addressActions.addAll(buildAddAddressUpdateActions(oldCustomer, newCustomer)); addressActions.addAll( collectAndFilterRemoveShippingAndBillingActions( removeAddressActions, oldCustomer, newCustomer)); buildSetDefaultShippingAddressUpdateAction(oldCustomer, newCustomer) .ifPresent(addressActions::add); buildSetDefaultBillingAddressUpdateAction(oldCustomer, newCustomer) .ifPresent(addressActions::add); addressActions.addAll(buildAddShippingAddressUpdateActions(oldCustomer, newCustomer)); addressActions.addAll(buildAddBillingAddressUpdateActions(oldCustomer, newCustomer)); return addressActions; } @Nonnull private static List collectAndFilterRemoveShippingAndBillingActions( @Nonnull final List removeAddressActions, @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { /* An action combination like below will cause a bad request error in API, so we need to filter out to avoid such cases: { "version": 1, "actions": [ { "action" : "removeAddress", "addressId": "-FWSGZNy" }, { "action" : "removeBillingAddressId", "addressId" : "-FWSGZNy" } ] } { "statusCode": 400, "message": "The customers billingAddressIds don't contain id '-FWSGZNy'.", "errors": [ { "code": "InvalidOperation", "message": "The customers billingAddressIds don't contain id '-FWSGZNy'.", "action": { "action": "removeBillingAddressId", "addressId": "-FWSGZNy" }, "actionIndex": 2 } ] } */ final Set addressIdsToRemove = removeAddressActions.stream() .map(customerUpdateAction -> (CustomerRemoveAddressAction) customerUpdateAction) .map(CustomerRemoveAddressAction::getAddressId) .collect(toSet()); final List removeActions = new ArrayList<>(); removeActions.addAll( buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer).stream() .map( customerUpdateAction -> (CustomerRemoveShippingAddressIdAction) customerUpdateAction) .filter(action -> !addressIdsToRemove.contains(action.getAddressId())) .collect(toList())); removeActions.addAll( buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer).stream() .map( customerUpdateAction -> (CustomerRemoveBillingAddressIdAction) customerUpdateAction) .filter(action -> !addressIdsToRemove.contains(action.getAddressId())) .collect(toList())); return removeActions; } /** * Compares the {@link List} of a {@link CustomerDraft#getAddresses()} and a {@link * Customer#getAddresses()}. It returns a {@link List} of {@link CustomerRemoveAddressAction} * update actions as a result, if the old address needs to be removed from the {@code oldCustomer} * to have the same set of addresses as the {@code newCustomer}. If both the {@link Customer} and * the {@link CustomerDraft} have the same set of addresses, then no update actions are needed and * hence an empty {@link List} is returned. * *

Notes: * *

    *
  • Addresses are matching by their keys. *
  • Null values of the new addresses are filtered out. *
  • Address values without keys are filtered out. *
* * @param oldCustomer the customer which should be updated. * @param newCustomer the customer draft where we get the new addresses. * @return A list containing the update actions or an empty list if the addresses are identical. */ @Nonnull public static List buildRemoveAddressUpdateActions( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { if (oldCustomer.getAddresses().isEmpty()) { return Collections.emptyList(); } if (newCustomer.getAddresses() == null || newCustomer.getAddresses().isEmpty()) { return oldCustomer.getAddresses().stream() .map( address -> CustomerRemoveAddressActionBuilder.of().addressId(address.getId()).build()) .collect(Collectors.toList()); } final Set newAddressKeys = newCustomer.getAddresses().stream() .filter(Objects::nonNull) .map(BaseAddress::getKey) .filter(key -> !isBlank(key)) .collect(toSet()); return oldCustomer.getAddresses().stream() .filter( oldAddress -> isBlank(oldAddress.getKey()) || !newAddressKeys.contains(oldAddress.getKey())) .map( address -> { if (address.getId() != null) { return CustomerRemoveAddressActionBuilder.of().addressId(address.getId()).build(); } return CustomerRemoveAddressActionBuilder.of().addressKey(address.getKey()).build(); }) .collect(toList()); } /** * Compares the {@link List} of a {@link CustomerDraft#getAddresses()} and a {@link * Customer#getAddresses()}. It returns a {@link List} of {@link CustomerChangeAddressAction} * update actions as a result, if the old address needs to be changed/updated from the {@code * oldCustomer} to have the same set of addresses as the {@code newCustomer}. If both the {@link * Customer} and the {@link CustomerDraft} have the same set of addresses, then no update actions * are needed and hence an empty {@link List} is returned. * *

Notes: * *

    *
  • Addresses are matching by their keys. *
  • Null values of the new addresses are filtered out. *
  • Address values without keys are filtered out. *
* * @param oldCustomer the customer which should be updated. * @param newCustomer the customer draft where we get the new addresses. * @return A list containing the update actions or an empty list if the addresses are identical. */ @Nonnull public static List buildChangeAddressUpdateActions( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { if (newCustomer.getAddresses() == null || newCustomer.getAddresses().isEmpty()) { return Collections.emptyList(); } final Map oldAddressKeyToAddressMap = oldCustomer.getAddresses().stream() .filter(address -> !isBlank(address.getKey())) .collect(toMap(BaseAddress::getKey, identity())); return newCustomer.getAddresses().stream() .filter(Objects::nonNull) .filter(newAddress -> !isBlank(newAddress.getKey())) .filter(newAddress -> oldAddressKeyToAddressMap.containsKey(newAddress.getKey())) .map( newAddress -> { // todo: better checks also other address related utilities // like mapping between Address and AddressDraft // comparing AddressImpl and AddressDraftImpl final Address oldAddress = oldAddressKeyToAddressMap.get(newAddress.getKey()); // ignore id on equals String oldAddressId = oldAddress.getId(); String newAddressId = newAddress.getId(); oldAddress.setId(null); newAddress.setId(null); if (!newAddress.equals(oldAddress)) { oldAddress.setId(oldAddressId); newAddress.setId(newAddressId); return CustomerChangeAddressActionBuilder.of() .addressId(oldAddressId) .address(newAddress) .build(); } oldAddress.setId(oldAddressId); newAddress.setId(newAddressId); return null; }) .filter(Objects::nonNull) .collect(toList()); } /** * Compares the {@link List} of a {@link CustomerDraft#getAddresses()} and a {@link * Customer#getAddresses()}. It returns a {@link List} of {@link CustomerAddAddressAction} update * actions as a result, if the new address needs to be added to have the same set of addresses as * the {@code newCustomer}. If both the {@link Customer} and the {@link CustomerDraft} have the * same set of addresses, then no update actions are needed and hence an empty {@link List} is * returned. * *

Notes: * *

    *
  • Addresses are matching by their keys. *
  • Null values of the new addresses are filtered out. *
  • Address values without keys are filtered out. *
* * @param oldCustomer the customer which should be updated. * @param newCustomer the customer draft where we get the new addresses. * @return A list containing the update actions or an empty list if the addresses are identical. */ @Nonnull public static List buildAddAddressUpdateActions( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { if (newCustomer.getAddresses() == null || newCustomer.getAddresses().isEmpty()) { return Collections.emptyList(); } final Map oldAddressKeyToAddressMap = oldCustomer.getAddresses().stream() .filter(address -> !isBlank(address.getKey())) .collect(toMap(Address::getKey, identity())); return newCustomer.getAddresses().stream() .filter(Objects::nonNull) .filter(newAddress -> !isBlank(newAddress.getKey())) .filter(newAddress -> !oldAddressKeyToAddressMap.containsKey(newAddress.getKey())) .map(newAddress -> CustomerAddAddressActionBuilder.of().address(newAddress).build()) .collect(toList()); } /** * Compares the {@link Customer#getDefaultShippingAddressId()} and {@link * CustomerDraft#getDefaultShippingAddress()}. If they are different - return {@link * CustomerSetDefaultShippingAddressAction} update action. If the old shipping address is set, but * the new one is empty - the command will unset the default shipping address. * * @param oldCustomer the customer that should be updated. * @param newCustomer the customer draft with new default shipping address. * @return An optional with {@link CustomerSetDefaultShippingAddressAction} update action. */ @Nonnull public static Optional buildSetDefaultShippingAddressUpdateAction( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { final Optional
oldAddress = oldCustomer.getAddresses().stream() .filter( address -> address.getId() != null && address.getId().equals(oldCustomer.getDefaultShippingAddressId())) .findFirst(); final String newAddressKey = getAddressKeyAt(newCustomer.getAddresses(), newCustomer.getDefaultShippingAddress()); if (newAddressKey != null) { if (!oldAddress.isPresent() || !Objects.equals(oldAddress.get().getKey(), newAddressKey)) { return Optional.of( CustomerSetDefaultShippingAddressActionBuilder.of().addressKey(newAddressKey).build()); } } else if (oldAddress.isPresent()) { // unset return Optional.of(CustomerSetDefaultShippingAddressActionBuilder.of().build()); } return Optional.empty(); } /** * Compares the {@link Customer#getDefaultBillingAddressId()} and {@link * CustomerDraft#getDefaultBillingAddress()}. If they are different - return {@link * CustomerSetDefaultBillingAddressAction} update action. If the old billing address id value is * set, but the new one is empty - the command will unset the default billing address. * * @param oldCustomer the customer that should be updated. * @param newCustomer the customer draft with new default billing address. * @return An optional with {@link CustomerSetDefaultBillingAddressAction} update action. */ @Nonnull public static Optional buildSetDefaultBillingAddressUpdateAction( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { final Optional
oldAddress = oldCustomer.getAddresses().stream() .filter( address -> address.getId() != null && address.getId().equals(oldCustomer.getDefaultBillingAddressId())) .findFirst(); final String newAddressKey = getAddressKeyAt(newCustomer.getAddresses(), newCustomer.getDefaultBillingAddress()); if (newAddressKey != null) { if (!oldAddress.isPresent() || !Objects.equals(oldAddress.get().getKey(), newAddressKey)) { return Optional.of( CustomerSetDefaultBillingAddressActionBuilder.of().addressKey(newAddressKey).build()); } } else if (oldAddress.isPresent()) { // unset return Optional.of(CustomerSetDefaultBillingAddressActionBuilder.of().build()); } return Optional.empty(); } @Nullable private static String getAddressKeyAt( @Nullable final List addressList, @Nullable final Integer index) { if (index == null) { return null; } if (addressList == null || index < 0 || index >= addressList.size()) { throw new IllegalArgumentException( format("Addresses list does not contain an address at the index: %s", index)); } final BaseAddress address = addressList.get(index); if (address == null) { throw new IllegalArgumentException( format("Address is null at the index: %s of the addresses list.", index)); } else if (isBlank(address.getKey())) { throw new IllegalArgumentException( format("Address does not have a key at the index: %s of the addresses list.", index)); } else { return address.getKey(); } } /** * Compares the {@link List} of a {@link Customer#getShippingAddressIds()} and a {@link * CustomerDraft#getShippingAddresses()}. It returns a {@link List} of {@link * CustomerAddShippingAddressIdAction} update actions as a result, if the new shipping address * needs to be added to have the same set of addresses as the {@code newCustomer}. If both the * {@link Customer} and the {@link CustomerDraft} have the same set of shipping addresses, then no * update actions are needed and hence an empty {@link List} is returned. * *

Notes: * *

    *
  • Addresses are matching by their keys. *
  • Old address values without keys are filtered out. *
  • Each address in the new addresses list satisfies the following conditions: *
      *
    1. It is not null *
    2. It has a key which is not blank (null/empty) *
    * Otherwise, a {@link IllegalArgumentException} will be thrown. *
* * @param oldCustomer the customer which should be updated. * @param newCustomer the customer draft where we get the new shipping addresses. * @return A list containing the update actions or an empty list if the shipping addresses are * identical. */ @Nonnull public static List buildAddShippingAddressUpdateActions( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { if (newCustomer.getShippingAddresses() == null || newCustomer.getShippingAddresses().isEmpty()) { return Collections.emptyList(); } final Map oldAddressKeyToAddressMap = getShippingAddresses(oldCustomer).stream() .filter(address -> !isBlank(address.getKey())) .collect(toMap(Address::getKey, identity())); final Set newAddressKeys = newCustomer.getShippingAddresses().stream() .map(index -> getAddressKeyAt(newCustomer.getAddresses(), index)) .collect(toSet()); return newAddressKeys.stream() .filter(newAddressKey -> !oldAddressKeyToAddressMap.containsKey(newAddressKey)) .map(key -> CustomerAddShippingAddressIdActionBuilder.of().addressKey(key).build()) .collect(toList()); } @Nonnull private static List
getShippingAddresses(@Nonnull final Customer oldCustomer) { final Set ids = new HashSet<>(oldCustomer.getShippingAddressIds()); return oldCustomer.getAddresses().stream() .filter(id -> ids.contains(id.getId())) .collect(toList()); } @Nonnull private static List
getBillingAddresses(@Nonnull final Customer oldCustomer) { final Set ids = new HashSet<>(oldCustomer.getBillingAddressIds()); return oldCustomer.getAddresses().stream() .filter(id -> ids.contains(id.getId())) .collect(toList()); } /** * Compares the {@link List} of a {@link Customer#getShippingAddressIds()} and a {@link * CustomerDraft#getShippingAddresses()}. It returns a {@link List} of {@link * CustomerRemoveShippingAddressIdAction} update actions as a result, if the old shipping address * needs to be removed to have the same set of addresses as the {@code newCustomer}. If both the * {@link Customer} and the {@link CustomerDraft} have the same set of shipping addresses, then no * update actions are needed and hence an empty {@link List} is returned. * *

Notes: * *

    *
  • Addresses are matching by their keys. *
  • Old shipping addresses without keys will be removed. *
  • Each address in the new addresses list satisfies the following conditions: *
      *
    1. It exists in the given index. *
    2. It has a key which is not blank (null/empty) *
    * Otherwise, a {@link IllegalArgumentException} will be thrown. *
* * @param oldCustomer the customer which should be updated. * @param newCustomer the customer draft where we get the new shipping addresses. * @return A list containing the update actions or an empty list if the shipping addresses are * identical. */ @Nonnull public static List buildRemoveShippingAddressUpdateActions( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { final List
shippingAddresses = getShippingAddresses(oldCustomer); if (shippingAddresses.isEmpty()) { return Collections.emptyList(); } if (newCustomer.getShippingAddresses() == null || newCustomer.getShippingAddresses().isEmpty()) { return shippingAddresses.stream() .map( address -> CustomerRemoveShippingAddressIdActionBuilder.of() .addressId(address.getId()) .build()) .collect(Collectors.toList()); } final Set newAddressKeys = newCustomer.getShippingAddresses().stream() .map(index -> getAddressKeyAt(newCustomer.getAddresses(), index)) .collect(toSet()); return shippingAddresses.stream() .filter(address -> isBlank(address.getKey()) || !newAddressKeys.contains(address.getKey())) .map( address -> CustomerRemoveShippingAddressIdActionBuilder.of() .addressId(address.getId()) .build()) .collect(toList()); } /** * Compares the {@link List} of a {@link Customer#getBillingAddressIds()} ()} and a {@link * CustomerDraft#getBillingAddresses()}. It returns a {@link List} of {@link * CustomerAddBillingAddressIdAction} update actions as a result, if the new billing address needs * to be added to have the same set of addresses as the {@code newCustomer}. If both the {@link * Customer} and the {@link CustomerDraft} have the same set of billing addresses, then no update * actions are needed and hence an empty {@link List} is returned. * *

Notes: * *

    *
  • Addresses are matching by their keys. *
  • Old address values without keys are filtered out. *
  • Each address in the new addresses list satisfies the following conditions: *
      *
    1. It is not null *
    2. It has a key which is not blank (null/empty) *
    * Otherwise, a {@link IllegalArgumentException} will be thrown. *
* * @param oldCustomer the customer which should be updated. * @param newCustomer the customer draft where we get the new billing addresses. * @return A list containing the update actions or an empty list if the billing addresses are * identical. */ @Nonnull public static List buildAddBillingAddressUpdateActions( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { final List
billingAddresses = getBillingAddresses(oldCustomer); if (newCustomer.getBillingAddresses() == null || newCustomer.getBillingAddresses().isEmpty()) { return Collections.emptyList(); } final Map oldAddressKeyToAddressMap = billingAddresses.stream() .filter(address -> !isBlank(address.getKey())) .collect(toMap(Address::getKey, identity())); final Set newAddressKeys = newCustomer.getBillingAddresses().stream() .map(index -> getAddressKeyAt(newCustomer.getAddresses(), index)) .collect(toSet()); return newAddressKeys.stream() .filter(newAddressKey -> !oldAddressKeyToAddressMap.containsKey(newAddressKey)) .map(key -> CustomerAddBillingAddressIdActionBuilder.of().addressKey(key).build()) .collect(toList()); } /** * Compares the {@link List} of a {@link Customer#getBillingAddressIds()} ()} and a {@link * CustomerDraft#getBillingAddresses()}. It returns a {@link List} of {@link * CustomerRemoveBillingAddressIdAction} update actions as a result, if the old billing address * needs to be removed to have the same set of addresses as the {@code newCustomer}. If both the * {@link Customer} and the {@link CustomerDraft} have the same set of billing addresses, then no * update actions are needed and hence an empty {@link List} is returned. * *

Notes: * *

    *
  • Addresses are matching by their keys. *
  • Null values of the old addresses are filtered out. *
  • Old shipping address values without keys are filtered out. *
  • Each address in the new addresses list satisfies the following conditions: *
      *
    1. It exists in the given index. *
    2. It has a key which is not blank (null/empty) *
    * Otherwise, a {@link IllegalArgumentException} will be thrown. *
* * @param oldCustomer the customer which should be updated. * @param newCustomer the customer draft where we get the new shipping addresses. * @return A list containing the update actions or an empty list if the shipping addresses are * identical. */ @Nonnull public static List buildRemoveBillingAddressUpdateActions( @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { final List
billingAddresses = getBillingAddresses(oldCustomer); if (billingAddresses.isEmpty()) { return Collections.emptyList(); } if (newCustomer.getBillingAddresses() == null || newCustomer.getBillingAddresses().isEmpty()) { return billingAddresses.stream() .map( address -> CustomerRemoveBillingAddressIdActionBuilder.of() .addressId(address.getId()) .build()) .collect(Collectors.toList()); } final Set newAddressKeys = newCustomer.getBillingAddresses().stream() .map(index -> getAddressKeyAt(newCustomer.getAddresses(), index)) .collect(toSet()); return billingAddresses.stream() .filter(address -> isBlank(address.getKey()) || !newAddressKeys.contains(address.getKey())) .map( address -> CustomerRemoveBillingAddressIdActionBuilder.of().addressId(address.getId()).build()) .collect(toList()); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy