Development Support Features
This section details information about the different development support features available.
Extension Annotations
Extension classes can have the following annotations:
@Extension
All extension classes must contain this annotation to be recognized as an extension by the Extension Point Framework.
@XPFEmbedded
This annotation indicates that the extension should be instantiated by the Spring Framework. This allows the extension to define fields with the @Autowired
annotation so it can access Spring services.
Warning
Do not add this annotation to extensions within external plugins.
@XPFAssignment
This annotation indicates that the extension should be automatically assigned to an extension point. If an extension does not contain this annotation, then it will not be assigned at startup but can still assigned at runtime using JMX methods.
This annotation can be defined multiple times on the same extension to assign the extension to multiple extension points, as in the example below:
@XPFAssignment(extensionPoint = XPFExtensionPointEnum.VALIDATE_PRODUCT_SKU_AT_ADD_TO_CART_READ, priority = 1050)
@XPFAssignment(extensionPoint = XPFExtensionPointEnum.VALIDATE_PRODUCT_SKU_AT_CHECKOUT, priority = 1040)
@XPFAssignment(extensionPoint = XPFExtensionPointEnum.VALIDATE_PRODUCT_SKU_AT_ADD_TO_CART, priority = 1040)
The extensionPoint
parameter indicates which extension point the extension should be assigned to.
The priority
parameter indicates the priority of the extension for that extension point. For extensions retrieved using XPFExtensionLookup#getSingleExtension
, the assigned extensions are ordered by priority, and the first valid extension is returned. For extensions retrieved using XPFExtensionLookup#getMultipleExtensions
, the extensions are ordered by priority and returned in that order. Therefore, extensions with lower priority numbers are returned before extensions with higher priority numbers.
note
All Elastic Path Commerce extensions that are delivered as part of the product will be assigned priorities in the range 1000 to 1999. Since we may add or remove extensions in the future, make sure to use priorities outside of that range for your custom extensions.
Extension Point Selectors
Selectors are a mechanism to ensure that extensions are only executed in certain contexts. For example, extensions can be configured to only run for certain scopes (stores) in Cortex.
To define the selector mode and the selectors for each extension, see Configuring and Deploying. To determine which selector an extension point supports, see the XPFExtensionPointEnum
documentation.
Extensions can be configured to run in one of two different selector modes:
DEFAULT_NONE
means that the extension will not be executed unless a selector context is defined that matches the current runtime context.DEFAULT_ALL
means that the extension will be executed unless a selector context is defined that matches the current runtime context.
The currently supported selectors are described below:
SELECTOR_NONE
This selector always matches the runtime context.
SELECTOR_STORE
This selector matches the runtime context if the current store matches the defined store code.
Plugin Initialization
To execute certain functionality as soon as a plugin is started, your plugin class can override the public void start()
method from the PF4j Plugin
class. Unlike the constructor, this method can access the Plugin Initialization Context.
The Plugin Initialization Context is an object available to external plugin and external plugin extensions. It provides access to support features such as plugin settings, logging, and caching. This object is not available to embedded extensions.
To access this object, classes can execute MyPlugin.getInstance().getContext()
, where MyPlugin
is replaced with the name of the external plugin class.
Extension Initialization
To execute certain functionality as soon as an extension is instantiated, your extension class can override the public void initialize(XPFExtensionInitializationContext context)
method from the XPFExtensionPointImpl
class. Unlike the constructor, this method can access the Extension Initialization Context, which is passed as a parameter.
The Extension Initialization Context provides access to support features such as extension settings.
Settings
Setting values can be passed into both external plugins and external plugin extensions. This can be useful for inputs such as endpoint URIs, credentials, or behavioural flags.
To access plugin settings, extensions can read Map<String, XPFPluginSetting> getSettings()
from the Plugin Initialization Context. To access extension settings, extensions can read Map<String, XPFPluginSetting> getSettings()
from the Extension Initialization Context.
Setting value keys are not pre-defined, so any setting values can be passed into a plugin or extension. It is up to the plugin developer to decide how to handle expected setting values that are missing, such as falling back to a default value or throwing an exception.
For more information about how to specify the setting values to pass into plugins and extensions, see Configuring and Deploying External Plugins.
Exceptions
If an extension method is unable to complete its work due to a problem, it should throw an exception. However, try to avoid throwing arbitrary runtime exceptions; there are two exceptions that Elastic Path Commerce expects extensions to throw. The choice of which exception to throw boils down to how the problem should be handled:
XPFPluginRuntimeException
should be thrown if an unexpected error occurred that the end-user cannot correct. If this is thrown by an extension that is invoked by Cortex, this will result in a500 Internal Server Error
response, and the details of the exception will be logged.XPFPluginInvalidBusinessStateException
should be thrown if there is a problem that can be corrected by the end-user. If this is thrown by an extension that is invoked by Cortex, this will result in a400 Bad Request
response, along with a structured error message containing the details. For example, if a Tax Calculation extension detects that the destination address has a postal code (zip code) that doesn’t match with the specified province (state), then aXPFPluginInvalidBusinessStateException
exception should be thrown. The extension can specify what should be returned in the structured error message by populating thestructuredErrorMessages
collection in the exception class constructor.
Logging
External plugins and external plugin extensions can write to the service logs through an SLF4j logger. Extensions can read Logger getLogger()
from the Plugin Initialization Context. The logger is automatically initialized with the logger name set to the plugin class name.
For example:
@Override
public void initialize(XPFExtensionInitializationContext context) {
super.initialize(context);
MyPlugin.getInstance().getContext().getLogger().info("Extension initialized.");
}
Additionally, the following Extension Point Framework events are logged automatically:
- When an external plugin is loaded:
INFO: XPF plugin {plugin-id} loaded.
- When an external plugin is started:
INFO: XPF plugin {plugin-id} started.
- When an external plugin is stopped:
INFO: XPF plugin {plugin-id} stopped.
- When an external plugin is unloaded:
INFO: XPF plugin {plugin-id} unloaded.
- When an extension is assigned:
DEBUG: Extension class {extension-class-name} assigned to {xpf-extension-enum} with priority {priority}.
- When an extension is unassigned:
DEBUG: Extension class {extension-class-name} unassigned from {xpf-extension-enum}.
- After an extension method is invoked:
TRACE: Extension class {extension-class-name} method {method-name} took {time} ms to execute.
- After an extension method is invoked, if it took more than 100ms:
WARN: Extension class {extension-class-name} method {method-name} took {time} ms to execute.
Caching
External plugins and external plugin extensions can create a cache that is backed by EhCache. Extensions can call XPFCache<K, V> createCache(String cacheName, int timeToLive, int timeToIdle, int maxObjects)
on the Plugin Initialization Context.
Each Elastic Path Commerce service holds a separate cache in memory; they are not shared between services. Also, the cache name is scoped to the plugin ID, meaning that if another plugin specifies the same cache name, separate caches will be created.
The cache will continue to exist even if the plugin is unloaded and reloaded. When createCache
is invoked, if an existing cache already exists for that plugin ID with the same name, the existing cache will be returned. Note that the plugin ID includes the version, so if a new plugin is loaded with a different version, then a new cache will be created.
Parameters
- The
cacheName
parameter specifies the name of the cache. - The
timeToLive
parameter specifies the maximum number of seconds an element can exist in the cache regardless of use. - The
timeToIdle
parameter specifies the maximum number of seconds an element can exist in the cache without being accessed. - The
maxObjects
parameter specifies the maximum number of objects that can exist in the cache.
For example
XPFCache<String, BigDecimal> skuCodeToPriceCache = LifecycleLoggerPlugin.getInstance().getContext().createCache("skuCodeToPriceCache", 3600, 3600, 10000);
BigDecimal price = skuCodeToPriceCache.get("alien_sku", skuCode -> getPriceFromAPI(skuCode));
Correlation ID
All extensions have access to the current correlation ID for the current thread. The XPFExtensionPointImpl
class that all extensions extend contains the following method:
String getCorrelationId();
Extensions can call this method to access the current Correlation ID so it can be optionally passed to downstream systems as part of an integration.
Performance
Long execution warning
All extension method invocations are monitored to ensure they execute within an expected execution time threshold. If an extension method takes more than 100ms to execute, a warning will be logged: Extension class {} method {} took {} ms to execute.
Lazy-loaded entity fields
Some entity class values are slow to calculate while at the same time may not be required by the configured extensions. To address this, some entity getter methods are lazy loaded when called by the extension. These methods can be identified by the existence of the @LazyLoaded
annotation on the method. Note that calling these methods may incur a performance penalty. However, if they are called more than once, a cached value will be returned after the first invocation. Additionally, there is a (small) chance that a lazy loaded method throws an exception, so make sure to add exception handling around these methods in your extension.