Skip to main content
Version: 8.7.x

Self-Managed Commerce 8.7.x Upgrade Notes

Upgrade notes​

The upgrading Elastic Path guide provides general instructions on upgrading Elastic Path projects. This page provides details about specific changes that may be required to your customizations when upgrading to Self-Managed Commerce 8.7.

Changes to webapp startup instructions for local development​

There are several changes to the instructions for starting the Self-Managed Commerce webapps on a local development machine:

  • The command to start the webapps is now mvn clean package cargo:run instead of mvn clean tomcat8:run-war.
  • The Commerce Manager webapp is now started from the extensions/cm/ext-cm-modules/ext-cm-webapp folder instead of extensions/cm/ext-cm-modules/ext-cm-webapp-runner.
  • JVM parameters passed to the mvn clean package cargo:run command are no longer passed to the webapps.
    • To enable remote debugging, add the -Pdebug option.
    • To enable automation-id HTML attributes for Selenium Tests, add the -PenableUITests option to the Commerce Manager webapp startup command.
    • To add other JVM parameters, add them to the systemProperties section of the cargo-maven3-plugin configuration in the webapp pom.xml file.
  • Webapps (including ActiveMQ) must be restarted after rebuilding the source code. They will stop working properly if the source code is rebuilt without restarting.
  • Wait at least 30 seconds after stopping a webapp before restarting it, otherwise you may receive a "Port already in use" error at startup.

For more details, see Running Applications.

Jakarta migration​

Self-Managed Commerce 8.7 has been released in three commits:

  1. 8.7.0.20250730203654-29b4ea: This commit contains all Self-Managed Commerce 8.7 changes EXCEPT the Jakarta migration.
  2. Jakarta migration in replacer output: This commit contains the required Maven POM changes to support the Jakarta migration, plus plugins that modify the source code at build time to support the Jakarta migration. This commit exists mainly to make it easier for developers to review the changes that have been made to support the Jakarta migration, without the boilerplate "import" updates that make it hard to review.
  3. Jakarta migration in committed source code: This commit contains all the required changes to the source code to support the Jakarta migration.
note

This functionality is being delivered in three commits to make it easier for developers to review the Jakarta migration changes separately from the other 8.7 changes. Do not deploy to production unless the Jakarta migration in committed source code commit (or later) has been consumed.

Follow all steps of the Upgrade Guide to upgrade to Self-Managed Commerce 8.7.

Review the sections below for advice about common issues that you may encounter when applying the Jakarta migration commits.

Automated Jakarta Source Migration​

Some parts of your custom code (Java, Groovy, and XML files) will be automatically migrated to use the Jakarta namespace by the Self-Managed Commerce Upgrade Tool. This includes:

  • import com.fasterxml.jackson.jaxrs.base --> import com.fasterxml.jackson.jakarta.rs.base
  • import com.fasterxml.jackson.jaxrs.json --> import com.fasterxml.jackson.jakarta.rs.json
  • import com.sun.xml.bind.annotation.XmlLocation --> import org.glassfish.jaxb.core.annotation.XmlLocation
  • import com.sun.xml.bind.Locatable --> import org.glassfish.jaxb.core.Locatable
  • import javax.activation --> import jakarta.activation
  • import javax.annotation --> import jakarta.annotation
  • import javax.inject --> import jakarta.inject
  • import javax.jms --> import jakarta.jms
  • import javax.json --> import jakarta.json
  • import javax.mail --> import jakarta.mail
  • import javax.persistence --> import jakarta.persistence
  • import javax.servlet --> import jakarta.servlet
  • import javax.transaction --> import jakarta.transaction
  • import javax.validation --> import jakarta.validation
  • import javax.ws.rs --> import jakarta.ws.rs
  • import javax.xml.bind --> import jakarta.xml.bind
  • import javax.xml.soap --> import jakarta.xml.soap
  • import org.apache.commons.mail.Email --> import org.apache.commons.mail2.jakarta.Email
  • import org.apache.commons.mail.EmailAttachment --> import org.apache.commons.mail2.jakarta.EmailAttachment
  • import org.apache.commons.mail.EmailException --> import org.apache.commons.mail2.core.EmailException
  • import org.apache.commons.mail.HtmlEmail --> import org.apache.commons.mail2.jakarta.HtmlEmail
  • import org.apache.commons.mail.SimpleEmail --> import org.apache.commons.mail2.jakarta.SimpleEmail
  • javax.jms.ConnectionFactory --> jakarta.jms.ConnectionFactory
  • javax.jms.JMSException --> jakarta.jms.JMSException
  • javax.persistence.FlushModeType --> jakarta.persistence.FlushModeType
  • javax.persistence.PersistenceException --> jakarta.persistence.PersistenceException
  • javax.persistence.Query --> jakarta.persistence.Query
  • javax.validation.constraints.NotNull.message --> jakarta.validation.constraints.NotNull.message
  • javax.validation.constraints.Size.message --> jakarta.validation.constraints.Size.message
  • javax.validation.Validator --> jakarta.validation.Validator

Elastic Path hosted dependencies​

Some Maven dependencies required by Self-Managed Commerce 8.7 do not exist in Maven Central. Therefore, we are hosting these dependencies in the Elastic Path public Nexus repository. The following third-party dependencies are hosted by Elastic Path:

  • org.jvnet.mock-javamail:mock-javamail:2.2
  • org.eclipse.gemini.blueprint:gemini-blueprint-core:4.0.0-atlassian-5
  • org.eclipse.gemini.blueprint:gemini-blueprint-io:4.0.0-atlassian-5
  • org.eclipse.gemini.blueprint:gemini-blueprint-extender:4.0.0-atlassian-5

Jakarta transformed dependencies​

Some Maven dependencies required by Self-Managed Commerce 8.7 do not support the Jakarta namespace. Therefore, these dependencies have been wrapped in a custom artifact that is transformed to use the Jakarta namespace using the org.eclipse.transformer:transformer-maven-plugin Maven plugin. The following modules have been transformed:

  • org.apache.activemq:activemq-broker --> com.elasticpath.jakarta.transformed:com.elasticpath.jakarta.transformed.org.apache.activemq.activemq-broker
  • org.apache.activemq:activemq-jms-pool --> com.elasticpath.jakarta.transformed:com.elasticpath.jakarta.transformed.org.apache.activemq.activemq-jms-pool
  • org.apache.activemq:activemq-pool --> com.elasticpath.jakarta.transformed:com.elasticpath.jakarta.transformed.org.apache.activemq.activemq-pool
  • org.apache.activemq:activemq-web-console --> com.elasticpath.jakarta.transformed:com.elasticpath.jakarta.transformed.org.apache.activemq.activemq-web-console
  • org.apache.solr:solr-core --> com.elasticpath.jakarta.transformed:com.elasticpath.jakarta.transformed.org.apache.solr.solr-core
  • org.apache.velocity.tools:velocity-tools-view --> com.elasticpath.jakarta.transformed.org.apache.velocity.tools.velocity-tools-view
  • org.eclipse.sisu:org.eclipse.sisu.inject.extender --> com.elasticpath.rest.jakarta.transformed:com.elasticpath.rest.jakarta.transformed.org.eclipse.sisu.inject.extender (within the api-platform project)
  • org.eclipse.sisu:org.eclipse.sisu.inject --> com.elasticpath.rest.jakarta.transformed:com.elasticpath.rest.jakarta.transformed.org.eclipse.sisu.inject (within the api-platform project)
  • org.ops4j:peaberry --> com.elasticpath.rest.jakarta.transformed:com.elasticpath.rest.jakarta.transformed.org.ops4j.peaberry (within the api-platform project)

JSON dependency in bundles​

If you encounter a ClassNotFoundException on a org.json class, you may need to make the following changes:

Add this dependency to your bundle pom.xml:

<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>

Add org.json to the list of Import-Package instructions in the maven-bundle-plugin definition of your bundle pom.xml.

OSGi dependencies​

Any modules that declare a dependency on org.osgi.core or org.osgi.annotation should be updated to use the osgi.core and osgi.annotation dependencies instead.

For example:

<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<version>${osgi.core.version}</version>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.annotation</artifactId>
<version>${osgi.core.version}</version>
</dependency>

Becomes:

<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi.core</artifactId>
<version>${osgi.core.version}</version>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi.annotation</artifactId>
<version>${osgi.core.version}</version>
</dependency>

The "EP Core Tool Selenium Cucumber Tests" module has been moved to extensions​

The commerce-engine/core/ep-core-tool-itests/selenium module has been moved to extensions/system-tests/ep-core-tool-itests/selenium. Any custom tests in this module should be moved to the new location.

Spring 5.x to 6.x changes​

All @Autowired annotations with corresponding @Named annotations should use @Qualifier instead.

For example:

@Autowired
@Named("cachedModifierFieldValidationService")
private ModifierFieldValidationService modifierFieldValidationService;

Becomes:

@Autowired
@Qualifier("cachedModifierFieldValidationService")
private ModifierFieldValidationService modifierFieldValidationService;

Previously, beans declared using constructor injection could skip the definition of some or all of the constructor arguments if a bean was found in the Spring context with the same name as the constructor parameter. However, in Spring 6.x, all the constructor arguments must be defined explicitly, and they must be defined in the same order as the class constructor parameters.

For example, the following bean definition works in Spring 5.x even though the PaymentAPIWorkflowImpl class has three constructor parameters:

<bean id="paymentAPIWorkflow" class="com.elasticpath.provider.payment.workflow.impl.PaymentAPIWorkflowImpl">
<constructor-arg name="beanFactory" ref="coreBeanFactory"/>
</bean>

However, in Spring 6.x, the bean must be defined as follows:

<bean id="paymentAPIWorkflow" class="com.elasticpath.provider.payment.workflow.impl.PaymentAPIWorkflowImpl">
<constructor-arg name="beanFactory" ref="coreBeanFactory"/>
<constructor-arg name="paymentProviderService" ref="paymentProviderService" />
<constructor-arg name="paymentProviderConfigurationService" ref="paymentProviderConfigurationService"/>
</bean>

If you have custom EhCache bean definitions, Spring 6.x has removed the org.springframework.cache.ehcache.EhCacheFactoryBean class, so you will need switch to our legacy class definition:

Update any bean definitions that use org.springframework.cache.ehcache.EhCacheFactoryBean to use com.elasticpath.cache.ehcache.spring.legacy.EhCacheFactoryBean. For example:

<bean id="shippingCalculationResultCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
...etc...
</bean>

Becomes:

<bean id="shippingCalculationResultCache" class="com.elasticpath.cache.ehcache.spring.legacy.EhCacheFactoryBean">
...etc...
</bean>

Any org.springframework.cache.ehcache imports in the list of Import-Package instructions in the maven-bundle-plugin definition of your bundle pom.xmls will need to be replaced with com.elasticpath.cache.ehcache.spring.legacy.

Camel 3.x to 4.x changes​

Some Camel dependencies in your pom.xml files will need to be updated to use the new Camel 4.x dependencies:

  • Replace org.apache.camel:camel-swagger-java with org.apache.camel:camel-openapi-java.
  • Replace org.apache.camel:camel-test with org.apache.camel:camel-test-junit5.
  • Replace org.apache.camel:camel-test-spring with org.apache.camel:camel-test-spring-junit5.

Within your test class, replace all org.apache.camel.test.junit4.CamelTestSupport imports with org.apache.camel.test.junit5.CamelTestSupport.

Additionally, you will need to migrate all tests in modules using the camel-test dependency to JUnit 5. See Added support for JUnit 5 for more information.

For any test classes extending CamelTestSupport, the following changes are required:

  • Rename the setUp method with setUpTest and tearDown method with tearDownTest. Also remove the @Override annotation from these methods.
  • If your test uses JUnitRuleMockery to define a Camel route, you will need to remove this field from your class and instead define your route by overriding the createRouteBuilder method.

The signature of the Camel DataFormat interface has changed, so any mock expectations around DataFormat method calls will need to be updated:

when(eventMessageDataFormat.unmarshal(any(Exchange.class), any(InputStream.class)))

Becomes:

when(eventMessageDataFormat.unmarshal(any(Exchange.class), any(Object.class)))
verify(eventMessageDataFormat).unmarshal(any(), any());

Becomes:

verify(eventMessageDataFormat).unmarshal(any(Exchange.class), any(Object.class));
given(eventMessageDataFormat.unmarshal(any(Exchange.class), any(InputStream.class)))

Becomes:

given(eventMessageDataFormat.unmarshal(any(Exchange.class), any(Object.class)))
verify(eventMessageDataFormat).unmarshal(any(), any());

Becomes:

verify(eventMessageDataFormat).unmarshal(any(Exchange.class), any(Object.class));

Remote Application Platform 3.5 to 4.2 changes​

Any custom Commerce Manager modules will require some changes to the Require-Bundle definitions in the MANIFEST.MF files.

  • Replace org.eclipse.ui.* with org.eclipse.rap.ui.* (where * represents any package in the org.eclipse.ui package hierarchy).
  • Replace org.eclipse.jface.* with org.eclipse.rap.jface.* (where * represents any package in the org.eclipse.jface package hierarchy).
  • Replace javax.servlet with jakarta.servlet-api.

Additionally, any uses of the org.eclipse.core.databinding.beans.BeansObservables class must be replaced with org.eclipse.core.databinding.beans.typed.BeanProperties. You will also need to add org.eclipse.core.databinding.property to your bundle's Require-Bundles definitions.

Hamcrest 1.3 to 2.2 changes​

Any dependencies on org.hamcrest:hamcrest-core:1.3 should be replaced with org.hamcrest:hamcrest:2.2.

For example:

<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<scope>compile</scope>
</dependency>

Becomes:

<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<scope>compile</scope>
</dependency>

Dependency updates required​

Some dependencies in your pom.xml files will need to be updated to use the new dependencies:

  • javax.annotation:javax.annotation-api --> jakarta.annotation:jakarta.annotation-api
  • javax.inject:javax.inject --> jakarta.inject:jakarta.inject-api
  • javax.interceptor:javax.interceptor-api --> jakarta.interceptor:jakarta.interceptor-api
  • javax.json:javax.json-api --> jakarta.json:jakarta.json-api
  • javax.servlet:javax.servlet-api --> jakarta.servlet:jakarta.servlet-api
  • javax.transaction:javax.transaction-api --> jakarta.transaction:jakarta.transaction-api
  • javax.validation:validation-api --> jakarta.validation:jakarta.validation-api
  • javax.ws.rs:javax.ws.rs-api --> jakarta.ws.rs:jakarta.ws.rs-api
  • org.apache.activemq:activemq-broker --> com.elasticpath.jakarta.transformed:com.elasticpath.jakarta.transformed.org.apache.activemq.activemq-broker
  • org.apache.activemq:activemq-client -> org.apache.activemq:activemq-client-jakarta
  • org.apache.activemq:activemq-jms-pool --> com.elasticpath.jakarta.transformed:com.elasticpath.jakarta.transformed.org.apache.activemq.activemq-jms-pool
  • org.apache.activemq:activemq-pool --> com.elasticpath.jakarta.transformed:com.elasticpath.jakarta.transformed.org.apache.activemq.activemq-pool
  • org.apache.activemq:activemq-web-console --> com.elasticpath.jakarta.transformed:com.elasticpath.jakarta.transformed.org.apache.activemq.activemq-web-console
  • org.apache.commons:commons-email --> org.apache.commons:commons-email2-core
  • org.apache.solr:solr-core --> com.elasticpath.jakarta.transformed:com.elasticpath.jakarta.transformed.org.apache.solr.solr-core
  • org.apache.velocity.tools:velocity-tools-view --> com.elasticpath.jakarta.transformed.org.apache.velocity.tools.velocity-tools-view
  • org.eclipse.sisu:org.eclipse.sisu.inject.extender --> com.elasticpath.rest.jakarta.transformed:com.elasticpath.rest.jakarta.transformed.org.eclipse.sisu.inject.extender
  • org.eclipse.sisu:org.eclipse.sisu.inject --> com.elasticpath.rest.jakarta.transformed:com.elasticpath.rest.jakarta.transformed.org.eclipse.sisu.inject
  • org.glassfish:jakarta.el --> org.glassfish.expressly:expressly
  • org.glassfish:javax.el --> org.glassfish.expressly:expressly
  • org.glassfish:javax.json --> jakarta.json:jakarta.json-api and org.eclipse.parsson:parsson
  • org.ops4j:peaberry --> com.elasticpath.rest.jakarta.transformed:com.elasticpath.rest.jakarta.transformed.org.ops4j.peaberry

Additionally, the following dependencies can be removed:

  • com.sun.xml.bind:jaxb-core - its functionality is provided by com.sun.xml.bind:jaxb-impl
  • com.sun.activation:jakarta.activation

Java and bundle import changes required​

Within your Java and Groovy classes, some imports will need to be updated to use the new imports. Additionally, Import-Package declarations in your pom.xml files will need to be updated to use the new imports. Some of these will be handled automatically by the Self-Managed Commerce Upgrade Tool.

  • com.fasterxml.jackson.jaxrs.base --> com.fasterxml.jackson.jakarta.rs.base
  • com.fasterxml.jackson.jaxrs.json --> com.fasterxml.jackson.jakarta.rs.json
  • com.sun.xml.bind.annotation.XmlLocation --> org.glassfish.jaxb.core.annotation.XmlLocation
  • com.sun.xml.bind.Locatable --> org.glassfish.jaxb.core.Locatable
  • javax.activation --> jakarta.activation
  • javax.annotation --> jakarta.annotation
  • javax.inject --> jakarta.inject
  • javax.jms --> jakarta.jms
  • javax.json --> jakarta.json
  • javax.mail --> jakarta.mail
  • javax.persistence --> jakarta.persistence
  • javax.servlet --> jakarta.servlet
  • javax.transaction --> jakarta.transaction
  • javax.validation --> jakarta.validation
  • javax.ws.rs --> jakarta.ws.rs
  • javax.xml.bind --> jakarta.xml.bind
  • javax.xml.soap --> jakarta.xml.soap
  • org.apache.commons.mail.Email --> org.apache.commons.mail2.jakarta.Email
  • org.apache.commons.mail.EmailAttachment --> org.apache.commons.mail2.jakarta.EmailAttachment
  • org.apache.commons.mail.EmailException --> org.apache.commons.mail2.core.EmailException
  • org.apache.commons.mail.HtmlEmail --> org.apache.commons.mail2.jakarta.HtmlEmail
  • org.apache.commons.mail.SimpleEmail --> org.apache.commons.mail2.jakarta.SimpleEmail
  • org.springframework.mock.jndi.SimpleNamingContextBuilder --> org.springframework.mock.jndi.legacy.SimpleNamingContextBuilder

Dropped certification for MySQL 5.7​

Starting with Self-Managed Commerce 8.7.0, MySQL 5.7 is no longer officially supported. MySQL 5.7 reached its end-of-life on October 31, 2023. We recommend that customers upgrade to MySQL 8.0 or MySQL 8.4.

For a complete list of supported MySQL versions, see Supported Technologies.

Commerce Manager customizations​

If your project includes copies of Commerce Manager modules under extensions/cm/ext-cm-modules - a common pattern used to gain access to ext-core classes - you will need to migrate these customizations before upgrading to Self-Managed Commerce 8.7. The upgraded version of Tycho included in Self-Managed Commerce 8.7 no longer permits multiple modules sharing the same artifact ID, and will fail at compile time with a "Duplicate reactor project IUs" error if these copied modules are present.

To resolve this, Self-Managed Commerce 8.7 includes a platform patch that allows the standard commerce-manager/cm-modules/cm-plugins modules to access ext-core classes directly, eliminating the need for the copied modules entirely. You will need to migrate any customizations from your extensions/cm/ext-cm-modules modules into their corresponding commerce-manager/cm-modules/cm-plugins modules, then delete the copied modules. If you have accumulated significant customizations across many modules, this migration can be time-consuming to do manually. Therefore we recommend using an AI coding tool such as Claude Code to automate the process. You can use a prompt like the following:

This repo represents a customized version of Self-Managed Commerce. We are in the process of upgrading to version 8.7. In previous versions, Commerce Manager modules (in commerce-manager/cm-modules/cm-plugins) could not access ext-core classes. To work around this, some modules were copied into the extensions/cm/ext-cm-modules folder so they could access custom services in ext-core. However, the new version of Tycho in Self-Managed Commerce 8.7 no longer allows multiple modules with the same artifact ID — it causes a "Duplicate reactor project IUs" error during compilation. Self-Managed Commerce 8.7 now allows the platform modules to access ext-core classes directly. Please migrate the customizations in the extensions/cm/ext-cm-modules modules back into the corresponding commerce-manager/cm-modules/cm-plugins modules and delete the extensions/cm/ext-cm-modules modules. If there is no corresponding commerce-manager/cm-modules/cm-plugins module, leave it in extensions/cm/ext-cm-modules.
note

Make sure you have consumed all Self-Managed Commerce 8.7 patches before attempting to migrate your Commerce Manager modules back into the platform. Some patches released in Mar 2026 enabled this functionality.

Custom email templates​

If your project has customized email templates, you will need to update them to use the new entity classes.

Responsibility for rendering and delivery of notifications is now handled by the Notification Events extension point. By default, the embedded VelocitySMTPEmailSender extension renders emails using Apache Velocity and use SMTP to send out the emails, in the same way as previous versions. However, the properties that are passed into the email templates have changed from JPA domain classes to Extension Point Framework entity classes.

The table below shows the changes to the properties that are passed into the email templates:

Old PropertyOld Property TypeNew PropertyNew Property Type
localeLocalelocaleLocale
storeStoreImplstoreXPFStore
customerCustomerImplcustomerXPFCustomer
orderOrderImplorderXPFOrder
orderHoldsList<OrderHold>order.holdsList<XPFOrderHold>
orderItemFormBeanMapMap<Long, List<? extends OrderItemPresentationBean>>order.shipments.iterator.next.lineItemsList<XPFOrderItem>
orderItemFormBeanListList<? extends OrderItemPresentationBean>shipment.lineItemsList<XPFOrderItem>
orderReturnOrderReturnImplorderReturnXPFOrderReturn
orderShipmentOrderShipmentImplshipmentXPFOrderShipment

The classes responsible for injecting properties into each email template are shown in the table below:

Notification TypeConverter Class
CM_USER_PASSWORD_RESETCmUserPasswordResetNotificationContextConverter
CM_USER_PASSWORD_CHANGEDCmUserPasswordChangedNotificationContextConverter
CM_USER_CREATEDCmUserCreatedNotificationContextConverter
CUSTOMER_PASSWORD_CHANGEDCustomerPasswordChangedNotificationContextConverter
CUSTOMER_PASSWORD_FORGOTTENCustomerPasswordForgottenNotificationContextConverter
CUSTOMER_REGISTRATIONCustomerRegistrationNotificationContextConverter
CUSTOMER_ANONYMOUS_REGISTRATIONCustomerAnonymousRegistrationNotificationContextConverter
IMPORT_JOBImportJobCompletedNotificationContextConverter
GIFT_CERTIFICATEGiftCertificateNotificationContextConverter
ORDER_CONFIRMATIONOrderConfirmationNotificationContextConverter
ORDER_CANCELLATIONOrderCancellationNotificationContextConverter
ORDER_SHIPMENT_SHIPPEDOrderShipmentShippedNotificationContextConverter
ORDER_SHIPMENT_RELEASE_FAILEDOrderShipmentReleaseFailedNotificationContextConverter
ORDER_RETURN_EXCHANGEOrderReturnExchangeNotificationContextConverter
ORDER_ON_HOLDOrderOnHoldNotificationContextConverter

Additionally, the GRMA.txt.vm and GRMA.html.vm templates have been removed, and are now handled by the RMA.txt.vm and RMA.html.vm templates, which can differentiate between physical and non-physical returns using the orderReturn.physicalReturn property.

The Velocity template engine has also been configured to use runtime.references.strict mode. This means that Velocity will now throw an exception if a reference such as a property or method cannot be found. If you have any invalid references in your templates, these will need to be corrected.

If you have created custom email types, the way this is implemented will need to be updated. See Adding a new notification type for more details.

For a complete list of the available notification types and the properties they support, see Notification Events extension point.

Custom promotion actions and conditions​

Custom promotion rule delegate action methods​

Previously, there was an inconsistency in how the promotion rule actions were applied. For shopping cart promotions, discount classes were used (extending AbstractDiscountImpl), but for catalog and shipping promotions, special methods were implemented in PromotionRuleDelegateImpl. Now, all discount action methods have been moved out of PromotionRuleDelegateImpl and into their own discount type classes. Therefore, any custom discount action methods within PromotionRuleDelegateImpl or an extension of this class will need to be moved into custom discount type classes.

For example,

@Override
public void applyCatalogCurrencyDiscountAmount(final long ruleId, final long actionId, final Price price, final String cartCurrencyCode,
final String ruleCurrencyCode, final String amount) {
if (LOG.isTraceEnabled()) {
LOG.trace("applyCatalogCurrencyDiscountAmount rule {} price {} cartCurrency {} ruleCurrency {} amount {}", ruleId, price,
cartCurrencyCode, ruleCurrencyCode, amount);
}
if (!cartCurrencyCode.equals(ruleCurrencyCode)) {
return;
}

final Money discount = stringAmountToMoney(amount, Currency.getInstance(cartCurrencyCode));
// Apply the discount to the product
discountPriceByAmount(ruleId, actionId, discount.getAmount(), price);
}

Becomes:

/**
* Applies a discount of a specified amount to a catalog.
*/
public class CatalogAmountDiscountImpl extends AbstractDiscountImpl<CatalogDiscountItemContainer> {
private static final long serialVersionUID = -4521436188919978665L;
private static final Logger LOG = LoggerFactory.getLogger(CatalogAmountDiscountImpl.class);

private static final PromotionActionParameters[] PARAMETER_KEYS = new PromotionActionParameters[] { PromotionActionParameters.DISCOUNT_AMOUNT,
PromotionActionParameters.RULE_CURRENCY_CODE};

private final String amount;
private final String ruleCurrencyCode;

/**
* Constructor which accepts a map of discount parameters.
* This constructor extracts values from the provided parameter map using predefined keys.
*
* @param discountParameters a map containing discount parameter values keyed by
* {@link PromotionActionParameters#getDataValue()} values.
*/
public CatalogAmountDiscountImpl(final Map<String, Object> discountParameters) {
super(discountParameters);
validateParameters(discountParameters);
this.amount = (String) discountParameters.get(PromotionActionParameters.DISCOUNT_AMOUNT.getDataValue());
this.ruleCurrencyCode = (String) discountParameters.get(PromotionActionParameters.RULE_CURRENCY_CODE.getDataValue());
}

@Override
protected BigDecimal doApply(final boolean actuallyApply, final CatalogDiscountItemContainer discountItemContainer) {
if (LOG.isTraceEnabled()) {
LOG.trace("applyCatalogCurrencyDiscountAmount rule {} price {} cartCurrency {} ruleCurrency {} amount {}",
getRuleGuid(), discountItemContainer.getPrice(),
discountItemContainer.getPrice().getCurrency(), ruleCurrencyCode, amount);
}
if (!discountItemContainer.getPrice().getCurrency().getCurrencyCode().equals(ruleCurrencyCode)) {
return BigDecimal.ZERO;
}

BigDecimal discountAmount = new BigDecimal(amount);

// Apply the discount to the product
for (PriceTier priceTier : discountItemContainer.getPrice().getPriceTiers().values()) {
final BigDecimal prePromotionAmount = priceTier.getPrePromotionPrice();

final BigDecimal discountedAmount = prePromotionAmount.subtract(discountAmount);

final DiscountRecord discountRecord =
new CatalogItemDiscountRecordImpl(getRuleGuid(), getActionId(), discountAmount);

if (priceTier.setComputedPriceIfLower(discountedAmount)) {
priceTier.clearDiscountRecords();
priceTier.addDiscountRecord(discountRecord);
}
}
return discountAmount;
}

/**
* Return the array of the required parameter keys for the rule.
*
* @return an array of String of the required parameter keys for the rule.
*/
@Override
public PromotionActionParameters[] getParameterKeys() {
return Stream.concat(Arrays.stream(super.getParameterKeys()), Arrays.stream(PARAMETER_KEYS))
.toArray(PromotionActionParameters[]::new);
}
}

The following methods in PromotionRuleDelegateImpl were moved into their own discount type classes:

  • applyCatalogCurrencyDiscountAmount --> CatalogAmountDiscountImpl
  • applyCatalogCurrencyDiscountPercent --> CatalogPercentDiscountImpl
  • applyShippingDiscountAmount --> ShippingAmountDiscountImpl
  • applyShippingDiscountPercent --> ShippingPercentDiscountImpl

Custom discount types​

Existing custom discount type classes (extending AbstractDiscountImpl) for shopping cart promotions, will need to be updated.

First, you will need to define a generic type parameter for the AsbtractDiscountImpl class that is being extended. The generic type parameter should either be set to ShoppingCartDiscountItemContainer (for shopping cart discounts) or CatalogDiscountItemContainer (for catalog discounts).

For example:

public class CartSkuAmountDiscountImpl extends AbstractDiscountImpl {

Becomes:

public class CartSkuAmountDiscountImpl extends AbstractDiscountImpl<ShoppingCartDiscountItemContainer> {

Secondly, the constructor parameters need to be updated to support the AbstractDiscountImpl constructor parameter changes: long ruleId was changed to String ruleGuid and long actionId was changed to String actionId.

For example:

public CartSkuAmountDiscountImpl(final String ruleElementType,
final long ruleId, final long actionId, final String skuCode, final String amount,
final String exceptions, final int availableDiscountQuantity) {
super(ruleElementType, ruleId, actionId, availableDiscountQuantity);
this.amount = amount;
this.exceptionStr = exceptions;
this.skuCode = skuCode;
}

Becomes:

public CartSkuAmountDiscountImpl(final String ruleElementType,
final String ruleGuid, final String actionId, final String skuCode, final String amount,
final String exceptions, final int availableDiscountQuantity) {
super(ruleElementType, ruleGuid, actionId, availableDiscountQuantity);
this.amount = amount;
this.exceptionStr = exceptions;
this.skuCode = skuCode;
}

Similarly, any discount type classes with a ShoppingItem shoppingItem parameter need to be updated to String shoppingItemGuid. They can then retrieve the ShoppingItem within the doApply method by calling discountItemContainer.getShoppingItemByGuid(shoppingItemGuid);.

Additionally, the doApply method needs to be updated to accept the generic type parameter instead of DiscountItemContainer.

For example:

public BigDecimal doApply(final boolean actuallyApply, final DiscountItemContainer discountItemContainer) {

Becomes:

public BigDecimal doApply(final boolean actuallyApply, final ShoppingCartDiscountItemContainer discountItemContainer) {

Also, a new getParameterKeys method needs to be defined. This should return an array of PromotionActionParameters that are required for the discount type.

Example:

@Override
public PromotionActionParameters[] getParameterKeys() {
return Stream.concat(Arrays.stream(super.getParameterKeys()), Arrays.stream(PARAMETER_KEYS))
.toArray(PromotionActionParameters[]::new);
}

Finally, for each custom discount type class, extend the DiscountImplementationType extensible enum class to define the discount type and link it to the discount type class. This will be used by your custom rule action class to determine which discount type to use.

For example:

/** Catalog amount discount ordinal. */
public static final int CATALOG_AMOUNT_DISCOUNT_ORDINAL = 1;

/** Catalog amount discount. */
public static final DiscountImplementationType CATALOG_AMOUNT_DISCOUNT = new DiscountImplementationType(
CATALOG_AMOUNT_DISCOUNT_ORDINAL, "catalogAmountDiscount", CatalogAmountDiscountImpl.class);

For more information about extensible enum classes, see Extensible Enums.

Custom rule actions​

Existing custom rule action classes (extending AbstractRuleActionImpl) will need to be updated.

The way that the getRuleCode method is implemented needs to be updated to use the new createRuleCode method from the AbstractRuleActionImpl class, instead of generating the Drools Rule Language code using a StringBuilder. As a result, these methods only need to worry about passing the correct parameters that will be injected into the XPFPromotionAction class.

For example:

@Override
@Transient
@SuppressWarnings({"PMD.ConsecutiveLiteralAppends", "PMD.AppendCharacterWithChar"})
public String getRuleCode() throws EpDomainException {
validate();
StringBuilder sbf = new StringBuilder();
sbf.append("\n\t applicableDiscounts.add(new CartAnySkuAmountDiscountImpl(\"").append(RULE_ELEMENT_TYPE).append("\", ");
sbf.append("\"").append(this.getRuleGuid()).append("\", \"");
sbf.append(this.getUidPk()).append("\", \"");
sbf.append(this.getParamValue(RuleParameter.DISCOUNT_AMOUNT_KEY));
sbf.append("\", \"").append(this.getExceptionStr());
sbf.append("\", delegate.calculateAvailableDiscountQuantity(cart, \"");
sbf.append(this.getRuleGuid()).append("\", ");
sbf.append(this.getParamValue(RuleParameter.NUM_ITEMS_KEY));
sbf.append(")));\n");
return sbf.toString();
}

Becomes:

@Override
@Transient
@SuppressWarnings({"PMD.ConsecutiveLiteralAppends", "PMD.AppendCharacterWithChar"})
public String getRuleCode() throws EpDomainException {
validate();
Map<String, String> parameters = Map.of(
PromotionActionParameters.DISCOUNT_AMOUNT.getDataValue(), addQuotes(this.getParamValue(RuleParameter.DISCOUNT_AMOUNT_KEY)),
PromotionActionParameters.EXCEPTION_STRING.getDataValue(), addQuotes(this.getExceptionStr()),
PromotionActionParameters.AVAILABLE_DISCOUNT_QUANTITY.getDataValue(), getRuleCodeForCalculateAvailableDiscountQuantity());
return createRuleCode(parameters);
}

Additionally, the AbstractRuleActionImpl abstract class now requires that extending classes implement a getDiscountImplementationType method. This method should return the DiscountImplementationType that is used by the AbstractRuleActionImpl class.

@Override
@Transient
public DiscountImplementationType getDiscountImplementationType() {
return DISCOUNT_IMPLEMENTATION_TYPE;
}

Custom rule conditions​

Existing custom rule condition classes (implementing RuleCondition), will need to be updated, as well as any extensions of the PromotionRuleDelegateImpl class.

The Drools engine context no longer has access to the discountItemContainer object. Therefore, the getRuleCode method needs to be updated to remove this parameter from any calls to delegate methods and the corresponding methods in extensions of the PromotionRuleDelegateImpl class need to be updated to remove the discountItemContainer parameter. The most common uses of the discountItemContainer parameter were to call the cartItemEligibleForPromotion or calculateSubtotalOfDiscountableItemsExcluding methods, which are not available on the PromotionRuleDelegateImpl class.

Additionally, the variables passed into the Drools engine context have been changed from domain classes to Extension Point Framework entity classes.

Specifically, for shopping cart promotion rules:

  • cart has been changed from ShoppingCart to XPFShoppingCart
  • shoppingItem has been changed from ShoppingItemImpl to XPFShoppingItem

For catalog promotion rules:

  • product has been changed from ProductImpl to XPFProduct
  • productSku has been changed from ProductSkuImpl to XPFProductSku

As a result, your extension of PromotionRuleDelegateImpl will need have its method signatures updated to accept the Extension Point Framework entity classes instead of the domain entity classes.

Finally, if your custom rule condition classes need access to fields in extended domain classes, you will need to extend the corresponding domain to Extension Point Framework entity converter class to inject these custom field values into the Extension Point Framework entity customData map. Then your extension of PromotionRuleDelegateImpl can access those fields using the getCustomData method on the corresponding Extension Point Framework entity.

Custom HTTP Request Tag Set Populator extensions​

Extensions implementing the HttpRequestTagSetPopulator interface should be modified to call XPFHttpTagSetContext#getHttpServletRequest() instead of the deprecated HttpServletRequestWrapper#getHttpRequest().

For more information, see Removed dependency on javax.servlet-api.

Custom payment plugins​

For payment plugins that specify the @BillingAddressRequired annotation, the behavior has been changed in a couple of ways.

First, Cortex will still prompt the user to enter billing address fields on the requestinstructionsform and paymentinstrumentform forms, but these fields will not be validated automatically. It is now the responsibility of the payment plugin to validate the billing address fields as required. This allows the plugin to match validation with the requirements of the payment gateway. It also allows the plugin to require the billing address fields on the requestinstructionsform but not the paymentinstrumentform, or vice-versa.

Second, Cortex no longer saves the specified billing address on the customer's profile.

Custom Camel route builders​

As previously recommended, custom Camel route builders should extend CRSCEnabledRouteBuilder instead of RouteBuilder. However, it is also critical that the configure method of these route builders invoke super.configure(), or else the core request-scoped cache will not be cleared as expected.

To ensure that this is not missed, any route builder that extends CRSCEnabledRouteBuilder but does not invoke super.configure() will now throw an exception at runtime. Example:

IllegalStateException: The configure method of com.elasticpath.eventprocessing.customerevents.messaging.CustomerEventRouteBuilder must call super.configure().

Custom batch jobs and custom catalog syndication builders​

Previously, we had complex transactional problems with the Catalog Syndication builders which use Spring Batch. To fully resolve the issues, the Spring Batch functionality in Catalog Syndication has been replaced with the simpler AbstractBatchJob functionality that is already used for batch jobs launched by Quartz. Additionally, some enhancements were made to the AbstractBatchJob class to support the functionality required by the Catalog Syndication builders, and the Spring Batch dependency has been removed from the platform.

Therefore, if your code includes custom batch jobs or custom catalog syndication builders, some changes will be required:

  • All imports and extends of com.elasticpath.batch.jobs.AbstractBatchJob need to be replaced with one of the following:
    • com.elasticpath.batch.processing.AbstractBatchJob: If the fetchBatchOfRecords and getBatchProcessor methods are being overridden.
    • com.elasticpath.batch.processing.AbstractJPANamedQueryBatchJob: If the getJPANamedQuery and getJPQNamedQueryParameters methods are being overridden.
    • com.elasticpath.batch.processing.AbstractJPANamedQueryListParameterBatchJob: If the getJPANamedQuery, getJPQNamedQueryParameters, and getJPANamedQueryListParameters methods are being overridden.
  • All imports of com.elasticpath.batch.jobs.impl.AbstractEnableJob need to be replaced with com.elasticpath.batch.processing.AbstractJPANamedQueryBatchJob.
  • All calls to AbstractBatchJob#setEnabledProvider need to be replaced with AbstractBatchJob#setEnabledProvider.
  • All calls to AbstractBatchJob#setConfigBatchSize need to be replaced with AbstractBatchJob#setBatchSizeProvider.
  • All overrides of AbstractJPANamedQueryBatchJob#getBatchJPQLQuery need to be renamed to AbstractJPANamedQueryBatchJob#getJPANamedQuery.
  • All overrides of AbstractJPANamedQueryBatchJob#getParameters need to be renamed to AbstractJPANamedQueryBatchJob#getJPANamedQueryParameters.
  • All imports of com.elasticpath.batch.jobs.AbstractBatchProcessor need to be replaced with com.elasticpath.batch.processing.AbstractBatchProcessor.
  • All imports of com.elasticpath.catalog.batch.processing.ExecuteBatchJob need to be replaced with com.elasticpath.batch.processing.ExecuteBatchJob.
  • All imports of com.elasticpath.catalog.batch.processing.OpenCloseOperations need to be replaced with com.elasticpath.batch.processing.OpenCloseOperations.
  • All imports of com.elasticpath.catalog.batch.CatalogJobRunner need to be replaced with com.elasticpath.batch.processing.BatchJobRunner.
  • All imports of org.springframework.batch.item.ItemProcessor need to be replaced with com.elasticpath.batch.processing.ItemProcessor.
  • All imports of org.springframework.batch.item.ItemWriter need to be replaced with com.elasticpath.batch.processing.ItemWriter.
  • All imports of org.springframework.batch.item.ExecutionContext need to be replaced with com.elasticpath.batch.processing.impl.ExecutionContext.

Additionally, for catalog syndication builders, the definition of the Spring Batch job needs to be refactored from a batch:job definition into a ExecuteCleanupAndBuildBatchJob class definition.

This is best explained through example. Here's an example of the original definition of the BUILD_ALL_OPTIONS job:

<batch:job id="BUILD_ALL_OPTIONS" job-repository="catalogJobRepository">
<batch:step id="cleanUpOptions" next="buildAllOptions" parent="baseStep">
<batch:tasklet>
<batch:chunk reader="jpaPagingOptionProjectionReader" writer="projectionRemover"/>
</batch:tasklet>
</batch:step>
<batch:step id="buildAllOptions" parent="baseStep">
<batch:tasklet>
<batch:chunk reader="jpaPagingOptionReader" processor="skuOptionItemProcessor" writer="projectionWriter"/>
</batch:tasklet>
</batch:step>
</batch:job>

<bean id="jpaPagingOptionProjectionReader" class="com.elasticpath.catalog.batch.job.JpaPagingItemReaderExtended" scope="step">
<property name="cleanUpDatabase" value="#{jobParameters['cleanUpDatabase']}" />
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="transacted" value="false"/>
<property name="queryString"
value="select s.guid from ProjectionEntity s where s.projectionId.type='option' and s.guid is not null order by s.guid"/>
<property name="pageSize" value="1000"/>
</bean>

<bean id="jpaPagingOptionReader" class="org.springframework.batch.item.database.JpaPagingItemReader">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="transacted" value="false"/>
<property name="queryString" value="select s from SkuOptionImpl s order by s.uidPk"/>
<property name="pageSize" value="1000"/>
</bean>

Here is an example of the refactored definition of the BUILD_ALL_OPTIONS job:

<bean id="catalogSyndicationBuildAllOptions" class="com.elasticpath.catalog.batch.processing.ExecuteCleanupAndBuildBatchJob">
<property name="cleanupJob">
<bean parent="parentCatalogSyndicationCleanupBatchJob">
<property name="jobName" value="catalogSyndicationCleanupOptions"/>
<property name="jpqlQuery" value="select s.guid from ProjectionEntity s where s.projectionId.type='option' and s.guid is not null order by s.guid"/>
</bean>
</property>
<property name="buildJob">
<bean parent="parentCatalogSyndicationBuildBatchJob">
<property name="jobName" value="catalogSyndicationBuildOptions"/>
<property name="jpqlQuery" value="select s from SkuOptionImpl s order by s.uidPk"/>
<property name="springBatchProcessor">
<bean parent="batchTxProxyTemplate">
<property name="target">
<bean parent="parentCatalogSyndicationBatchProcessor">
<property name="processor" ref="skuOptionItemProcessor"/>
<property name="writer" ref="projectionWriter"/>
</bean>
</property>
</bean>
</property>
</bean>
</property>
</bean>

Finally, the bean ID needs to be referenced in the jobs property of the catalogSyndicationBuildAllProjections bean, and the sourceMap property of the catalogSyndicationBatchJobsParent bean.

Trusted Header Authentication​

The -DenableTrustedSubjectHeaderMode system property has been removed and replaced with the -Dauth.mode system property. If you previously had the -DenableTrustedSubjectHeaderMode system property set, change it to -Dauth.mode=TRUSTED_HEADER.

Additionally, if you have the Cortex Trusted Header Mode Accelerator wired into your project, you should remove it. To remove the accelerator, review the Merge Preview and undo the changes shown.

New static analysis checks​

Additional static analysis checks have been added to the Self-Managed Commerce build process. This may cause your build to fail if your code violates these checks:

  • Enforce Upper-Bound Dependencies: Ensures that if a dependency is declared with a version that is lower than the version required by a transitive dependency, the build will fail.
  • Analyze Exclusions: Analyzes the exclusions defined on dependencies in this project and fails the build if any of them are unneeded.

For more information, see Static analysis.

Changes to FollowLocation error handling​

When Cortex POST requests are made with the FollowLocation query parameter, Cortex actually processes operations against two resources:

  • The origin resource: The resource that the Cortex POST was made against.
  • The target resource: The resource that Cortex would normally redirect to, but which actually provides the response if the FollowLocation query parameter is included.

When doing a POST to an origin resource with the FollowLocation query parameter, if the origin resource was successful but the target resource was unsuccessful, Cortex would fall back to the origin resource response (which is a 2XX response with a location header).

This behavior is summarized in the following table:

Origin Resource ResponseTarget Resource ResponseHTTP status returnedHTTP Location header returnedHTTP body returned
201200201Target resource locationTarget resource body
200200200Target resource locationTarget resource body
4XX or 5XXN/A4XX or 5XXNoneOrigin resource body
2014XX or 5XX201Target resource locationOrigin resource body
2004XX or 5XX200Target resource locationOrigin resource body

This was confusing for clients, since the HTTP status code could indicate that the operation was successful, but the body of the response was empty. This was especially confusing for resources like lookups and search, which don’t create anything but just generate a URL for a specific resource,

To reduce confusion but maintain backwards compatibility, the behavior has been changed slightly. The returned HTTP status code is still determined by the origin resource. If the origin resource succeeds but the target resource operation fails, Cortex acts as if the FollowLocation request was not present and returns the origin resource response with a location header for the target resource. Cortex also returns a X-Ep-Follow-Location-Status header so the client can determine the result of the target resource operation. Additionally, if both resources succeed, the location header is no longer returned with the response, because we’re returning the target resource body with the response and we don't want the client to redirect to the location header value.

The new behavior is summarized in the following table:

Origin Resource ResponseTarget Resource ResponseHTTP status returnedHTTP X-Ep-Follow-Location-Status status returnedHTTP Location header returnedHTTP body returned
201200201200NoneTarget resource body
201200201200NoneTarget resource body
4XX or 5XXN/A4XX or 5XXN/ANoneOrigin resource body
2014XX or 5XX2014XX or 5XXTarget resource locationOrigin resource body
2004XX or 5XX2004XX or 5XXTarget resource locationOrigin resource body
note

In the table above, the changes from the previous behavior are highlighted in bold.

For more information, see FollowLocation.

Camel dependencies in OSGi bundles​

Previously, using Apache Camel in OSGi bundles was very challenging. Simply adding org.apache.camel dependencies to a bundle would usually cause it to fail with the following error:

javax.xml.bind.JAXBException: "org.apache.camel.model.config" doesnt contain ObjectFactory.class or jaxb.index

This could be worked around by embedding Apache Camel dependencies into the bundle, but this was not ideal.

To make this easier, a new ep-camel-bundle bundle has been created that provides the necessary Camel packages automatically. If you have extension bundle modules that define org.apache.camel runtime dependencies and camel-* embed-dependency instructions, these can now be simply removed. At runtime, OSGi should import the required Camel packages from the ep-camel-bundle bundle automatically.

OSGi split package detection​

A new test has been added to the webapp-smoketests module that will fail if an OSGi split package situation is detected. If you have multiple bundles in Cortex that export the same package, this test will fail.

For more information, see Webapp Smoketests.

Updated IntelliJ code style configuration​

The IntelliJ code style configuration in devops/intellij/code-style.xml has been updated. final modifiers are now automatically applied to local variables and method parameters generated by IntelliJ. Additionally, jakarta packages are now grouped alongside java and javax packages when imports are optimized.

Ensure that all developers update their IntelliJ code style configuration by following the setup code formatting instructions.

Added support for JUnit 5​

Junit 5 (Jupiter) is now supported in all Self-Managed Commerce modules. The JUnit 4 dependencies are still available, and can be used without changing any of your existing tests. JUnit 4 and JUnit 5 dependencies can be used together in the same module without any issues.

To convert a JUnit 4 test to JUnit 5, make the following changes:

  • Remove the junit dependency from your module pom.xml. (This is optional; it can be left in place if you still have JUnit 4 tests in the module.)

  • Add the following dependencies to your module pom.xml:

    <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    </dependency>
    <dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    </dependency>
  • Within your test class, replace all org.junit.* imports with org.junit.jupiter.api.*.

  • If using Mockito for mocking, replace @RunWith(MockitoJUnitRunner.class) with @ExtendWith(MockitoExtension.class).

  • Any JUnit 4 annotations will need to be converted to JUnit 5 annotations:

    • @BeforeClass -> @BeforeAll
    • @AfterClass -> @AfterAll
    • @Before -> @BeforeEach
    • @After -> @AfterEach
    • @Ignore -> @Disabled

Commerce Engine changes​

  • The transient promotionCodes field on ShoppingCartImpl was changed from Map<String, Set<String>> (representing a map of rule GUIDs to set of coupon codes) to Set<String> (representing a simple set of coupon codes).
  • Renamed ShoppingCartPromotionImporterImpl to PromotionImporterImpl and renamed ShoppingCartPromotionExporterImpl to PromotionExporterImpl.
  • Several changes have been made to the CustomerService interface:
    • Added boolean isCustomerExistsByGuidAndCustomerType(String guid, CustomerType customerType). This allows you to determine if a customer exists using the GUID and customer type.
    • Removed boolean isCustomerGuidExists(String guid). Use boolean isCustomerExistsByGuidAndCustomerType(String guid, CustomerType customerType) instead.
    • Added boolean boolean isCustomerExistsBySharedIdAndCustomerType(String sharedId, CustomerType customerType). This allows you to determine if a customer exists using the shared ID and customer type.
    • Deprecated boolean isCustomerExistsBySharedId(String sharedId, CustomerType customerType). Use boolean isCustomerExistsBySharedIdAndCustomerType(String sharedId, CustomerType customerType) instead.
    • Removed boolean isRegisteredCustomerExistsBySharedIdAndCustomerType(Customer customer). Use boolean isCustomerExistsBySharedIdAndCustomerType(String sharedId, CustomerType customerType) instead.
    • Added String findCustomerGuidBySharedIdAndCustomerType(String sharedId, CustomerType customerType). This allows you to retrieve a customer's GUID using the shared ID and customer type.
    • Deprecated String findCustomerGuidBySharedId(String sharedId, CustomerType customerType). Use String findCustomerGuidBySharedIdAndCustomerType(String sharedId, CustomerType customerType) instead.
  • The loadingDefaultSku field (and related getter/setter) has been removed from ProductLoadTunerImpl. This field was confusing and unnecessary. Developers should use the loadingSkus field to specify that product SKUs (including the default sku) should be loaded when retrieving a product from the database.

Import/Export changes​

Moved condition rules into the promotions schema​

Previously, the promotions.xml Import/Export schema contained Drools actions and selling context conditions, but not Drools conditions. Instead, these needed to be specified in a separate condition_rules.xml file, which referenced each promotion by code. This was inconvenient and confusing.

Now, the promotions.xml Import/Export schema includes all promotion details, including Drools conditions. The conditions can be specified as in the following example:

<conditions>
<and>
<condition>
<kind>Condition</kind>
<type>productInCartCondition</type>
<parameters>
<parameter>
<key>numItems</key>
<value>1</value>
</parameter>
<parameter>
<key>numItemsQuantifier</key>
<value>AT_LEAST</value>
</parameter>
<parameter>
<key>productCode</key>
<value>triggerprodforfreeshippingpromo</value>
</parameter>
</parameters>
<exceptions/>
</condition>
</and>
</conditions>

For backwards compatibility, promotions can still be imported without condition rules, and the condition_rules.xml file is still supported. However, when exporting promotions, the condition rules will be automatically included in the promotions.xml schema.

For more information, see Promotions.