Class ProductRowConverter

java.lang.Object
com.broadleafcommerce.common.dataimport.conversion.AbstractRowConverter<com.broadleafcommerce.common.dataimport.util.ConversionUtils.ConversionResponse<Product>>
com.broadleafcommerce.catalog.dataimport.converter.ProductRowConverter

public class ProductRowConverter extends com.broadleafcommerce.common.dataimport.conversion.AbstractRowConverter<com.broadleafcommerce.common.dataimport.util.ConversionUtils.ConversionResponse<Product>>
Row converter to convert a Map (or row from a CSV file) into a Product.
Author:
Kelly Tisdell (ktisdell)
  • Field Details

    • BATCH_CONTEXT_PREFETCHED_PRODUCTS_BY_ID_MAP

      public static final String BATCH_CONTEXT_PREFETCHED_PRODUCTS_BY_ID_MAP
      In BatchRequest.BatchContext.getAdditionalContextMap(), we expect a nested map under this key to contain prefetched products by their ID.
      See Also:
    • BATCH_CONTEXT_PREFETCHED_PRODUCTS_BY_EXTERNAL_ID_MAP

      public static final String BATCH_CONTEXT_PREFETCHED_PRODUCTS_BY_EXTERNAL_ID_MAP
      In BatchRequest.BatchContext.getAdditionalContextMap(), we expect a nested map under this key to contain prefetched products by their external ID.
      See Also:
    • BATCH_CONTEXT_PREFETCHED_BUSINESS_TYPES_BY_TYPE_KEY_MAP

      public static final String BATCH_CONTEXT_PREFETCHED_BUSINESS_TYPES_BY_TYPE_KEY_MAP
      In BatchRequest.BatchContext.getAdditionalContextMap(), we expect a nested map under this key to contain prefetched business types by their type key.
      See Also:
  • Constructor Details

    • ProductRowConverter

      public ProductRowConverter(com.fasterxml.jackson.databind.ObjectMapper mapper, com.broadleafcommerce.common.extension.TypeFactory typeFactory, com.broadleafcommerce.common.dataimport.util.IdResolver idResolver)
  • Method Details

    • convert

      @NonNull public com.broadleafcommerce.common.dataimport.util.ConversionUtils.ConversionResponse<Product> convert(@Nullable Object parent, @NonNull @NonNull com.broadleafcommerce.common.dataimport.messaging.BatchRecord record, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
      Specified by:
      convert in class com.broadleafcommerce.common.dataimport.conversion.AbstractRowConverter<com.broadleafcommerce.common.dataimport.util.ConversionUtils.ConversionResponse<Product>>
    • canConvert

      public boolean canConvert(@NonNull @NonNull com.broadleafcommerce.common.dataimport.messaging.BatchRecord record, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
      Specified by:
      canConvert in class com.broadleafcommerce.common.dataimport.conversion.AbstractRowConverter<com.broadleafcommerce.common.dataimport.util.ConversionUtils.ConversionResponse<Product>>
    • determineOperationType

      protected com.broadleafcommerce.data.tracking.core.type.OperationType determineOperationType(@NonNull @NonNull Product product, boolean productAlreadyExistsInDatastore, @NonNull @NonNull com.broadleafcommerce.common.dataimport.messaging.BatchRecord record, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
      Determine the effective operation type for the given batch record and product instance. This can help inform decisions on how to instantiate/map fields.
      Parameters:
      product - the resolved product instance
      productAlreadyExistsInDatastore - whether the product was determined to already exist in the datastore. See instantiateOrGetPrefetchedProduct(BatchRecord, Map, BatchContext)
      record - the original batch record
      context - the batch context
      Returns:
      the effective operation type for the given batch record
    • resolveProductId

      protected String resolveProductId(@NonNull @NonNull Product product, @NonNull @NonNull com.broadleafcommerce.common.dataimport.messaging.BatchRecord record, @NonNull @NonNull com.broadleafcommerce.data.tracking.core.type.OperationType operation, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
    • validateBusinessType

      protected void validateBusinessType(@NonNull @NonNull Product product, @NonNull @NonNull com.broadleafcommerce.common.dataimport.messaging.BatchRecord record, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
    • parseAndSetKeywords

      protected void parseAndSetKeywords(@NonNull @NonNull Product product, @NonNull @NonNull com.broadleafcommerce.data.tracking.core.type.OperationType operationType, @NonNull @NonNull com.broadleafcommerce.common.dataimport.messaging.BatchRecord record, @NonNull @NonNull Map<String,String> specialColumns, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
    • parseAndSetAttributes

      protected void parseAndSetAttributes(@NonNull @NonNull Product product, @NonNull @NonNull com.broadleafcommerce.data.tracking.core.type.OperationType operationType, @NonNull @NonNull com.broadleafcommerce.common.dataimport.messaging.BatchRecord record, @NonNull @NonNull Map<String,String> specialColumns, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
    • toKeyAttributePair

      protected org.springframework.data.util.Pair<String,Attribute> toKeyAttributePair(String[] keyValueArray)
    • parseAndSetOptions

      protected void parseAndSetOptions(@NonNull @NonNull Product product, @NonNull @NonNull com.broadleafcommerce.data.tracking.core.type.OperationType operationType, @NonNull @NonNull com.broadleafcommerce.common.dataimport.messaging.BatchRecord record, @NonNull @NonNull Map<String,String> mutableRow, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
      The syntax we expect for the ProductRowConverter.ProductProperties.OPTIONS_HEADER column is a series of individual option values separated by RowUtils.VALUE_SEPARATOR.

      Each option value consists of a few properties separated by RowUtils.PROPERTY_SEPARATOR. Assignment of property values is done with RowUtils.VALUE_ASSIGNMENT_SEPARATOR.

      For convenience, some properties can be omitted in certain scenarios with an implied default. Here are a few examples of option values:

      • label::Label for Option and Attribute Choice;required::true;type::Size
      • label::Label for Option and Attribute Choice;type::Size -> implies 'required' is false
      • label::Size -> implies 'required' is false, and 'type' will be set to match 'label' since it matches one of DefaultAttributeChoiceType
      • Size -> implies 'required' is false, and both 'label' and 'type' will be set to match the given value since it matches one of DefaultAttributeChoiceType

      Note that 'type' is only optional in situations where the given 'label' value matches (ignoring case) one of DefaultAttributeChoiceType. This prevents inadvertent creation/usage of random types. If custom types need to be used, they should be explicitly provided.

      Furthermore, AttributeChoice.getAllowedValues() are set on each option from the values found in CompleteProductImportBatchHandler.RecordTypes.VARIANT dependent records of the product record.

      Parameters:
      product - the product to set the parsed options on
      operationType - the determined operation type
      record - the original product batch record
      mutableRow - the row from which to obtain the options column
      context - the batch context
    • parseAndSetCurrency

      protected void parseAndSetCurrency(@NonNull @NonNull Product product, @NonNull @NonNull com.broadleafcommerce.data.tracking.core.type.OperationType operationType, @NonNull @NonNull com.broadleafcommerce.common.dataimport.messaging.BatchRecord record, @NonNull @NonNull Map<String,String> mutableRow, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
      Currency is not complex but gets special treatment because we should not mix different currencies in a single Catalog without using the Pricing Service. Therefore, this method will ensure that there is no currency mismatch issue. By default, this is actually a no-op because JpaProduct.preToMe(ContextInfo, Object) already handles setting the currency.
      Parameters:
      product - the product to set the parsed currency on
      operationType - the determined operation type
      record - the original product batch record
      mutableRow - the row from which to obtain the currency column
      context - the batch context
    • parseIndividualOptionToken

      protected ProductOption parseIndividualOptionToken(String givenOptionToken, AtomicInteger displayOrderCounter, com.broadleafcommerce.common.dataimport.messaging.BatchRecord productRecord)
    • parseGivenOptionProperties

      protected Map<String,String> parseGivenOptionProperties(String givenOptionToken)
    • initializeAttributeChoiceType

      protected void initializeAttributeChoiceType(@Nullable String explicitlyProvidedAttributeChoiceType, @NonNull @NonNull String attributeChoiceLabel, AttributeChoice initializationTarget)
    • getCaseCorrectedAttributeChoiceTypeIfMatchesDefault

      @Nullable protected String getCaseCorrectedAttributeChoiceTypeIfMatchesDefault(String attributeChoiceType)
      It's possible that a provided attribute choice type is the same as one of the defaults but doesn't have the same casing. To avoid a situation where there are, for example, both 'SIZE' and 'Size' types, we will force-set the value to match the enum.
      Parameters:
      attributeChoiceType - the attribute choice type value to compare
      Returns:
      the case-corrected attribute choice type if it matched a default, else null
    • parseAttributeChoiceAllowedValuesForOption

      protected List<AttributeChoiceValue> parseAttributeChoiceAllowedValuesForOption(Map<String,String> givenOptionPropertyMap, com.broadleafcommerce.common.dataimport.messaging.BatchRecord productRecord)
    • initializeAttributeChoiceValue

      protected AttributeChoiceValue initializeAttributeChoiceValue(String label, String value, Integer displayOrder)
    • parseAndSetFulfillmentFlatRates

      protected void parseAndSetFulfillmentFlatRates(@NonNull @NonNull Product product, @NonNull @NonNull com.broadleafcommerce.data.tracking.core.type.OperationType operationType, @NonNull @NonNull com.broadleafcommerce.common.dataimport.messaging.BatchRecord record, @NonNull @NonNull Map<String,String> mutableRow, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
    • toKeyFulfillmentFlatRatePair

      protected org.springframework.data.util.Pair<String,FulfillmentFlatRate> toKeyFulfillmentFlatRatePair(String[] keyValueArray)
    • parseAndSetTerms

      protected void parseAndSetTerms(@NonNull @NonNull Product product, @NonNull @NonNull com.broadleafcommerce.data.tracking.core.type.OperationType operationType, @NonNull @NonNull com.broadleafcommerce.common.dataimport.messaging.BatchRecord record, @NonNull @NonNull Map<String,String> specialColumns, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
    • parseGivenTermProperties

      protected ProductTerm parseGivenTermProperties(String givenTermToken)
    • parseAndSetCharacteristics

      protected void parseAndSetCharacteristics(@NonNull @NonNull Product product, @NonNull @NonNull com.broadleafcommerce.data.tracking.core.type.OperationType operationType, @NonNull @NonNull com.broadleafcommerce.common.dataimport.messaging.BatchRecord record, @NonNull @NonNull Map<String,String> mutableRow, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
    • toKeyProductCharacteristicPair

      protected org.springframework.data.util.Pair<String,ProductCharacteristic> toKeyProductCharacteristicPair(String[] keyValueArray, BusinessType businessType)
    • validateCharacteristicValues

      protected Characteristic validateCharacteristicValues(BusinessType businessType, String key, List<Object> convertedValues, AtomicReference<BusinessTypeCharacteristic> foundCharacteristic)
      Extension point that allows implementers to extend or override the basic validation around imported characteristic values based on the defined value type on the business type
      Parameters:
      businessType -
      key -
      convertedValues -
      foundCharacteristic -
      Returns:
    • instantiateOrGetPrefetchedProduct

      protected org.springframework.data.util.Pair<Boolean,Product> instantiateOrGetPrefetchedProduct(@NonNull @NonNull com.broadleafcommerce.common.dataimport.messaging.BatchRecord batchRecord, @NonNull @NonNull Map<String,String> mutableRow, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
      We expect the CompleteProductImportBatchHandler to have pre-fetched products by various identifier fields if supplied in the original rows. Any products that were found in the datastore should have been populated into BatchRequest.BatchContext.getAdditionalContextMap().

      If a product is present in this map, we will use this existing instance.

      If a product is not present in this map, we will simply instantiate a new instance.

      Parameters:
      batchRecord - the original BatchRecord
      mutableRow - the row from the batch record (usually with any special columns pre-removed via extractSpecialColumns(Map, BatchContext))
      context - the BatchRequest.BatchContext, which should have any pre-fetched products in BatchRequest.BatchContext.getAdditionalContextMap()
      Returns:
      a Pair containing a boolean describing whether the product was found in the pre-fetched map, as well as the product instance itself
    • getPrefetchedProduct

      @Nullable protected Product getPrefetchedProduct(com.broadleafcommerce.common.dataimport.messaging.BatchRecord batchRecord, Map<String,String> batchRecordRow, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
      Checks the BatchRequest.BatchContext.getAdditionalContextMap() to see if there is already a pre-fetched product matching the batchRecord.
      Parameters:
      batchRecord - the batch record for which to find the corresponding pre-fetched product instance (if exists)
      batchRecordRow - the row from the batch record (pre-processed if necessary)
      context - the batch context which should contain pre-fetched products set by CompleteProductImportBatchHandler
      Returns:
      the pre-fetched product instance if found, or null
    • getPrefetchedProductFromAdditionalContextMap

      protected Optional<Product> getPrefetchedProductFromAdditionalContextMap(Map<String,Object> additionalContextMap, String keyForPrefetchedProductsMap, String keyForProductInPrefetchedProductsMap)
      Parameters:
      additionalContextMap - the BatchRequest.BatchContext.getAdditionalContextMap()
      keyForPrefetchedProductsMap - the key in additionalContextMap where the value must be a nested Map<String, Product> of prefetched products by some key
      keyForProductInPrefetchedProductsMap - the key in the prefetched products map for which to find a given product
      Returns:
      an Optional containing the prefetched product if found in the given map
    • getPrefetchedBusinessTypesFromAdditionalContextMap

      protected Optional<BusinessType> getPrefetchedBusinessTypesFromAdditionalContextMap(Map<String,Object> additionalContextMap, String keyForPrefetchedBusinessTypesMap, String keyForTypeInPrefetchedBusinessTypesProductsMap)
      Parameters:
      additionalContextMap - the BatchRequest.BatchContext.getAdditionalContextMap()
      keyForPrefetchedBusinessTypesMap - the key in additionalContextMap where the value must be a nested Map<String, BusinessType> of prefetched business types by some key
      keyForTypeInPrefetchedBusinessTypesProductsMap - the key in the prefetched business types map for which to find a given type
      Returns:
      an Optional containing the prefetched business type if found in the given map
    • modifyProduct

      protected Product modifyProduct(@NonNull @NonNull Product product, @NonNull @NonNull com.broadleafcommerce.common.dataimport.messaging.BatchRecord record, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
      Hook point to allow implementers to modify the details of the product or return a different product instance.

      This MUST return a non-null value. By default, it returns the provided value.

      Parameters:
      product - the product that has been created populated with data from the row
      record - the record that represents the row that was mapped to the product
      context - the batch context
    • initializeData

      protected void initializeData(@NonNull @NonNull Product product, @NonNull @NonNull com.broadleafcommerce.common.dataimport.messaging.BatchRecord record, @NonNull @NonNull Map<String,String> mutableRow, @NonNull @NonNull com.broadleafcommerce.data.tracking.core.type.OperationType operation, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
      Initialize the data from the row onto the product. Set additional data as needed. For example, set ID and activeStartDate.
      Parameters:
      product - the target product onto which we set data.
      record - the original BatchRecord
      mutableRow - the mutable row from the BatchRecord with entries removed that cannot be handled by reflection
      operation - the operation; typically CREATE or UPDATE
      context - the BatchContext
    • extractSpecialColumns

      protected Map<String,String> extractSpecialColumns(@NonNull @NonNull Map<String,String> mutableRow, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context)
      Overrides:
      extractSpecialColumns in class com.broadleafcommerce.common.dataimport.conversion.AbstractRowConverter<com.broadleafcommerce.common.dataimport.util.ConversionUtils.ConversionResponse<Product>>
    • setProductTypeBasedOnRecord

      protected void setProductTypeBasedOnRecord(Product product, com.broadleafcommerce.common.dataimport.messaging.BatchRecord productRecord, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context, com.broadleafcommerce.data.tracking.core.type.OperationType operationType)

      Set Product.getProductType() based on whether or not the given BatchRecord for product has any variants. If it does, Product.getProductType() is set to VARIANT_BASED. Otherwise, STANDARD. This way, the Product.getProductType() wouldn't need to be explicitly declared.

      The Product.getProductType() should only be set if the BatchRecord.getOperation() is CREATE, since Product.getProductType() should never be changed once it's set.

      This method will set the product type to null if the operation is anything but create. This will cause the eventual update operation to skip updating the field.

      Parameters:
      product - product created from the BatchRecord
      productRecord - BatchRecord for the product
      context - BatchRequest.BatchContext
    • setProductUriIfNeeded

      protected void setProductUriIfNeeded(Product product, com.broadleafcommerce.common.dataimport.messaging.BatchRecord productRecord, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context, com.broadleafcommerce.data.tracking.core.type.OperationType operationType)
    • setProductPricingKeyIfNeeded

      protected void setProductPricingKeyIfNeeded(Product product, com.broadleafcommerce.common.dataimport.messaging.BatchRecord productRecord, @Nullable com.broadleafcommerce.common.dataimport.messaging.BatchRequest.BatchContext context, com.broadleafcommerce.data.tracking.core.type.OperationType operationType)
    • isEligibleForPricingKeyAutoGeneration

      protected boolean isEligibleForPricingKeyAutoGeneration(Product product, com.broadleafcommerce.data.tracking.core.type.OperationType operationType)
    • pricingKeyConceptAppliesToProduct

      protected boolean pricingKeyConceptAppliesToProduct(Product product)
      Product.getPricingKey() only makes sense for certain types of products, so this method checks those conditions. Essentially, it only makes sense for products that don't have their own SKUs but could have a price.

      NOTE - this method would only work in OperationType.CREATE situations, where the provided product instance has all fields set. It would not work in an update scenario, where some fields may be null and thus fail to provide accurate data for the checks this method performs.

      NOTE - some of the checks in this method are likely not ever going to be hit, since setProductTypeBasedOnRecord(Product, BatchRecord, BatchContext, OperationType) is fairly restrictive in the types it sets. Nonetheless, we define all potentially relevant checks here.

      Parameters:
      product - the product instance to test
      Returns:
      whether the pricing key concept makes sense for this product
    • productRecordHasAnyVariants

      protected boolean productRecordHasAnyVariants(com.broadleafcommerce.common.dataimport.messaging.BatchRecord productRecord)
    • getAttributeName

      protected String getAttributeName(Map<String,String> propertyMap)
    • getMapper

      protected com.fasterxml.jackson.databind.ObjectMapper getMapper()
    • getTypeFactory

      protected com.broadleafcommerce.common.extension.TypeFactory getTypeFactory()
    • getIdResolver

      protected com.broadleafcommerce.common.dataimport.util.IdResolver getIdResolver()
    • getVariantRowConverter

      protected VariantRowConverter getVariantRowConverter()
      Lazy injection to avoid circular dependency issues.
    • setVariantRowConverter

      @Autowired @Lazy public void setVariantRowConverter(VariantRowConverter variantRowConverter)
      Lazy injection to avoid circular dependency issues.