Configuring and Deploying Extensions and Plugins
This section details how to configure and deploy extensions and plugins.
Extension Configuration
Extension Configuration can be used for multiple purposes:
- To disable extensions that are normally activated by an
@XPFAssignmentannotation. - To override the priority of an extension.
- To assign extensions to extension points, optionally based on criteria specified in selectors.
- To pass setting values to extensions.
Add the extensions file
To add an extensions.json file to extensions/plugins/ext-plugin-config/src/main/resources, use the following example:
{
"extensions": [
{
"identifier": {
"pluginId": "elasticpath-collectipinorderdata-*",
"extensionClass": "com.elasticpath.plugins.collectipinorderdata.extensions.IPOrderDataPopulator",
"extensionPointKey": "ORDER_DATA_POPULATOR"
},
"enabled": true,
"priority": 50,
"defaultSelectorMode": "DEFAULT_NONE",
"selectors": [
{
"type": "XPFExtensionSelectorByStoreCode",
"value": "mobee"
}
],
"settings": [
{
"settingKey": "IP_ADDRESS_HEADER",
"dataType": "SHORT_TEXT",
"collectionType": "SINGLE",
"settingValues": [
{
"value": "X-Forwarded-For"
}
]
},
{
"settingKey": "SAMPLE_MAP",
"dataType": "INTEGER",
"collectionType": "MAP",
"settingValues": [
{
"key": "mobee",
"value": 10
},
{
"key": "kobee",
"value": 20
}
]
}
]
}
]
}
Fields
| Field | Purpose | Required? |
|---|---|---|
extensions[*].identifier.pluginId | Specifies the plugin ID of the plugin that contains the extension class being configured. The plugin ID is defined as ${plugin.vendor.id}-${plugin.name.id}-${project.version}. The * character can be used as a wildcard to allow the configuration to match all plugin versions. If not specified, then the Extension Point Framework assumes that you are configuring an embedded extension. | No |
extensions[*].identifier.extensionClass | Specifies the fully-qualified name of the extension class being configured. If not specified, then the Extension Point Framework assumes that you are configuring an embedded extension. | Yes |
extensions[*].identifier.extensionPointKey | Specifies the name of the XPFExtensionPointEnum enum that the extension class is (or should be) assigned to. | Yes |
extensions[*].enabled | If set to false, the extension class will not be assigned to the Extension Point even if it contains an @XPFAssignment annotation. If not defined, the value will only be enabled if there is an @XPFAssignment annotation on the extension class. | No |
extensions[*].priority | Specifies the priority for the extension. If the extension point supports multiple extensions, extensions are ordered from lowest to highest priority. If the extension point only supports a single extension, the extension with the lowest priority value will override extensions with a higher priority value. Setting this value overrides any value specified by the @XPFAssignment annotation. If not defined, the value will default based on the priority value from the @XPFAssignment annotation on the extension, or Integer.MAX_INT (the lowest priority) if no annotation exists. | No |
extensions[*].defaultSelectorMode | If set to DEFAULT_ALL, then the extension is automatically assigned to all selectors EXCEPT the ones specified in the selectors list. If set to DEFAULT_NONE, then the extension is only assigned to the selectors specified in the selectors list. | Yes |
extensions[*].selectors[*].type | The type of selector to use to determine if the extension should be invoked. Elastic Path Commerce supports XPFExtensionSelectorByStoreCode, which uses the current Cortex scope to determine which extensions to activate, and XPFExtensionSelectorAny, which matches every context. See Selectors. | Yes |
extensions[*].selectors[*].value | The selector value to use to determine if the extension should be invoked. If the type is set to XPFExtensionSelectorByStoreCode, then this represents the Cortex scope (aka store code), and the value is required. If the type is set to XPFExtensionSelectorAny, omit this field. | Sometimes |
extensions[*].settings[*].settingKey | The identifier for a setting value that the extension expects to receive. | Yes |
extensions[*].settings[*].dataType | The data type of the setting value. The following data types are supported: SHORT_TEXT, INTEGER, DECIMAL, BOOLEAN, DATE | Yes |
extensions[*].settings[*].collectionType | The collection type (cardinality) of the setting value. The following data types are supported: SINGLE, LIST, SET, MAP | Yes |
extensions[*].settings[*].settingValues[*].key | The key for the setting value. Only supported for settings with collection type MAP. | No |
extensions[*].settings[*].settingValues[*].value | The setting value. | Yes |
Selectors
Selectors decide which runtime contexts an assigned extension runs in. For the concepts, including the two selector modes, see Extension Point Selectors. This section covers what happens when you declare selectors in extensions.json.
Two selector types can be declared:
| Type | value | Matches |
|---|---|---|
XPFExtensionSelectorByStoreCode | Required | Only the Cortex scope (store code) named in value. |
XPFExtensionSelectorAny | Omit | Every context. |
One entry, one instance
Each entry in extensions.json creates a separate instance of the extension class, and each instance receives only the settings declared in its own entry. See Extension Lifecycle.
This is what makes per-selector configuration possible: to give an extension a different setting value per store, declare one entry per store, each with its own selector and its own settings block. The extension reads those values from the Extension Initialization Context in its initialize method.
Because the instances are separate objects, any per-instance resource the extension creates in initialize is created once per entry.
Declaring an entry replaces the annotation assignment
If an extension class carries an @XPFAssignment annotation and there is no matching entry in extensions.json, the framework assigns the extension to every context.
As soon as you declare any entry in extensions.json for that extension class, the annotation no longer contributes an assignment. Only the declared entries apply. The annotation still supplies the default values for priority and enabled unless the extensions.json entry specifies these.
Catch-all entries
To customize some stores and keep the original behavior everywhere else, declare an additional catch-all entry alongside the store-specific ones. Set its defaultSelectorMode to DEFAULT_ALL and omit the selectors field: DEFAULT_ALL means "every context except the ones listed", and nothing is listed, so the entry matches everything.
Give the catch-all entry a higher priority number than the store-specific entries. For an extension point that supports a single extension, the framework returns the match with the lowest priority number, so the store-specific entry wins in its own store and the catch-all serves the rest:
{
"extensions": [
{
"identifier": {
"pluginId": "elasticpath-collectipinorderdata-*",
"extensionClass": "com.elasticpath.plugins.collectipinorderdata.extensions.IPOrderDataPopulator",
"extensionPointKey": "ORDER_DATA_POPULATOR"
},
"enabled": true,
"priority": 100,
"defaultSelectorMode": "DEFAULT_NONE",
"selectors": [
{
"type": "XPFExtensionSelectorByStoreCode",
"value": "mobee"
}
],
"settings": [
{
"settingKey": "IP_ADDRESS_HEADER",
"dataType": "SHORT_TEXT",
"collectionType": "SINGLE",
"settingValues": [
{
"value": "X-Mobee-Forwarded-For"
}
]
}
]
},
{
"identifier": {
"pluginId": "elasticpath-collectipinorderdata-*",
"extensionClass": "com.elasticpath.plugins.collectipinorderdata.extensions.IPOrderDataPopulator",
"extensionPointKey": "ORDER_DATA_POPULATOR"
},
"enabled": true,
"priority": 200,
"defaultSelectorMode": "DEFAULT_ALL",
"settings": [
{
"settingKey": "IP_ADDRESS_HEADER",
"dataType": "SHORT_TEXT",
"collectionType": "SINGLE",
"settingValues": [
{
"value": "X-Forwarded-For"
}
]
}
]
}
]
}
note
For an extension point that supports multiple extensions, priority determines execution order rather than which extension wins. A catch-all entry there runs in addition to the store-specific entry, not instead of it. To check whether an extension point supports one extension or many, see the XPFExtensionPointEnum documentation.
Plugin Configuration
Plugin Configuration is typically used to pass setting values to extensions. Any plugins found in the plugin folder will be loaded regardless of whether a plugins.json folder exists or if an entry exists for the plugin.
Add the plugins file
To add a plugins.json file to extensions/plugins/ext-plugin-config/src/main/resources, use the following example:
{
"plugins": [
{
"identifier": {
"pluginId": "elasticpath-collectipinorderdata-*"
},
"settings": [
{
"settingKey": "IP_ADDRESS_HEADER",
"dataType": "SHORT_TEXT",
"collectionType": "SINGLE",
"settingValues": [
{
"value": "X-Forwarded-For"
}
]
},
{
"settingKey": "SAMPLE_MAP",
"dataType": "INTEGER",
"collectionType": "MAP",
"settingValues": [
{
"key": "mobee",
"value": 10
},
{
"key": "kobee",
"value": 20
}
]
}
]
}
]
}
Fields
| Field | Purpose | Required? |
|---|---|---|
plugins[*].identifier.pluginId | Specifies the plugin ID of the plugin that you are configuring. Note: The * character can be used as a wildcard to allow the configuration to match all plugin versions. | No |
plugins[*].settings[*].settingKey | The identifier for a setting value that the plugin expects to receive. | Yes |
plugins[*].settings[*].dataType | The data type of the setting value. The following data types are supported: SHORT_TEXT, INTEGER, DECIMAL, BOOLEAN, DATE | Yes |
plugins[*].settings[*].collectionType | The collection type (cardinality) of the setting value. The following data types are supported: SINGLE, LIST, SET, MAP | Yes |
plugins[*].settings[*].settingValues[*].key | The key for the setting value. Only supported for settings with collection type MAP. | No |
plugins[*].settings[*].settingValues[*].value | The setting value. | Yes |
Placeholders
The value field for plugin and extension settings can be specified as a placeholder. This can be useful if the values are environment-specific or if the values are sensitive. If the values are sensitive so we don’t want them included in the configuration files directly.
For these circumstances, the value can be replaced with a placeholder key that is specified by enclosing the key with ${ and }, as in the following example:
{
"settingKey": "PAYMENT_USERNAME",
"dataType": "SHORT_TEXT",
"collectionType": "SINGLE",
"settingValues": [
{
"value": "${payment.username}"
}
]
}
You can resolve placeholder values through any of the following means:
JVMparameter, such as-Dpayment.username=bob.- System environment variable, such as
export payment.username=bob. ep.properties, in any of the locations documented here.
note
For local development, one convenient place to store placeholders is in extensions/database/ext-data/src/main/resources/environments/local/files/conf/ep.properties. Ensure that you run mvn install -Preset-conf from the extensions/database module to copy this file to your local ${user.home}/ep/conf/ep.properties.
If a placeholder key can’t be resolved, the Elastic Path Commerce service will throw an exception at startup.
Plugin Deployments
To add new external plugins to your project, add a Maven dependency to the ext-plugin-config module.
note
This dependency could come from a public repository hosted by Elastic Path or another vendor, or it could come from a custom plugin build that publishes to your project Nexus.
The following is an example of this plugin definition:
<dependencies>
<dependency>
<groupId>com.elasticpath.plugins</groupId>
<artifactId>vip-only-products</artifactId>
<version>0.0.1</version>
</dependency>
</dependencies>
Advanced Deployment Details
At startup, all Elastic Path Commerce services scan a specific folder for plugin JARs, extension configuration, such as extensions.json and plugin configuration, such as plugins.json. Any JARs found in this folder that contain the expected manifest entries will be loaded and started automatically. The plugin folder is determined by the value of the ep.plugins.folder JVM parameter. If this parameter is not set, ${user.home}/ep/plugins will be used by default.
The ext-plugin-config project is a convenient mechanism for ensuring that both project developers starting services locally and deployed Docker images have access to the same plugins and configuration. All of the plugin JARs and configuration files are collected into the target folder when mvn install is executed. When the deployment package is generated, the contents of the ext-plugin-config module assembly are included in a plugins folder within the deployment package assembly.
For local services started with mvn tomcat8:run-war, the ep.plugins.folder parameter is set to extensions/core/ext-plugin-config/target/plugins by default. This causes the local services to automatically load all artifacts from that folder at startup.
For CloudOps deployments, the Deployment Package assembly is downloaded from Nexus during the Docker Image Build step, and the contents of the plugins folder is copied into the /ep/plugins folder of the Docker image. CloudOps also sets the ep.plugins.folder JVM parameter to the same folder. This causes the service within the Docker image to find and load the External Plugins at startup. To rollback to a previous state without the plugin or with a prior version of the plugin, simply deploy the previous version of the service Docker images.