State Policies
Introduction to State Policies
State Policy is a set of rules that can be used to determine the state of widgets. UI widgets in the Commerce Manager can have one of three states:
editable
- the user can view and modify the value displayed in the widgetread-only
- the user can only view the valuedisabled
- the widget is disabled and the user cannot view or modify the value
The state of a widget at a given time depends on several factors. For example, if a text widget displays product data:
- Only the users who are assigned to a catalog that contains that product can see the value.
- Only the users with the Manage Products and SKUs permission can modify that value.
If the Change Sets feature is enabled and that product is in a locked change set, only users who are assigned to the change set can see that value.
Managing the states of different widgets can become very complex when you have many controls displaying data from different sources in the same dialog. State Policies provide a way to manage the state of UI widgets in the Commerce Manager.
To understand how state policies work, it’s important to understand the different objects that interact with them.
State Policy Contribution
Maps a State Policy Target to a State Policy. There may be multiple State Policies that apply to a given State Policy Target.
For example, there may be one State Policy governing access to data from a particular catalog. There may also be a State Policy governing access to objects in a particular Change Sets. In this case, the rules of both State Policies need to be applied.
State Policy Resolver
Resolves the State Policy Contributions and creates a single State Policy that can be applied to State Policy Targets.
State Policy Governable
An object that can have a State Policy applied to it. For example:
- A Dialog
- A Wizard
- A Page
- An Editor
There are two types of Governable:
- State Policy Target
- State Policy Delegate
State Policy Target
A State Policy Governable that receives its State Policy through a direct assignment. Dialogs, Editors, and Actions are examples of State Policy Targets.
State Policy Delegate
A State Policy Governable that inherits its State Policy from its container. Pages and Sections are examples of State Policy Delegates.
State Policy Governable contains one or more Policy Action Containers.
Policy Action Container
Contains State Change Targets.
State Change Target
A UI control whose state may change depending on different factors.
Creating a State Policy
Custom State Policies should extend the AbstractStatePolicyImpl
class and implement the init and determineState
methods.
The State Policy is initialized by calling the init method. This method takes a bean factory and an Object as parameters. The bean factory parameter can be used to set a custom bean factory. The dependentObject parameter is an optional dependent object, usually the model (domain object) containing the data to be displayed in the control. Use the dependentObject
parameter to pass any objects you need to determine state.
note
If you need to pass multiple objects to the state policy, add the objects to a Collection
and pass the Collection
in the dependentObject
parameter.
The following example shows the init
method for a policy that determines the state of UI controls when creating a category in a catalog. In this case, the dependentObject
parameter contains a Catalog
object.
public void init(final BeanFactory beanFactory, final Object dependentObject) {
this.catalog = (Catalog) dependentObject;
}
The determineState
method contains the logic you want to apply to determine the state of the UI control. The PolicyActionContainer
parameter contains the State Policy Governables to which the Policy is being applied and the State Change Targets that will be affected.
The following example shows the determineState
method for a policy that determines the state of UI controls when creating a category in a catalog. If the user has the category management permission and the user has been assigned to that catalog, then determineState
returns EpState.EDITABLE
, which means the UI controls will be editable. Otherwise, the UI controls will be read-only.
public EpState determineState(final PolicyActionContainer targetContainer) {
if (this.catalog != null
&& AuthorizationService.getInstance().isAuthorized(CatalogPermissions.CATEGORY_MANAGE)
&& AuthorizationService.getInstance().isAuthorized(this.catalog)) {
return EpState.EDITABLE;
}
return EpState.READ_ONLY;
}
Enabling State Policy awareness in a UI plugin
A UI Plugin’s Dialogs, Editors, Wizards, and Actions can all be made state policies aware. The basic process for this is as follows:
- Create a State Policy extension point and add it to the
plugin.xml
of the plugin that needs to support State Policies. - Create a State Policy aware UI plugin class.
- Create a State Policy aware dialog and a State Policy aware action.
- In your dialogs, add a State Policy aware UI controls.
- Create a State Policy Contribution to map your State Policies to State Policy Targets.
Creating a State Policy extension point
In order to create a State Policy Contribution, you must add a State Policy extension point to the plugin.
To create a State Policy extension point for a plugin, do the following:
Open
plugin.xml
in Eclipse.In the Extension Points tab, click the Add button next to the All Extension Points list.
In the New Extension Point dialog, enter the following:
- Extension Point ID
- Extension Point Name
- Extension Point Schema: Leave blank and make sure Edit extension point schema when done is selected
Click Finish
A new schema file named
statePolicy.exsd
is created and is displayed automatically.In the Source tab of the
statePolicy.exsd
file, paste the following:
<?xml version='1.0' encoding='UTF-8'?>
<!-- Schema file written by PDE -->
<schema targetNamespace="com.elasticpath.cmclient.admin.gc" xmlns="http://www.w3.org/2001/XMLSchema">
<element name="extension">
<complexType>
<sequence minOccurs="1" maxOccurs="unbounded">
<element ref="statePolicyContribution"/>
</sequence>
<attribute name="point" type="string" use="required"/>
</complexType>
</element>
<element name="statePolicyContribution">
<complexType>
<attribute name="targetId" type="string" use="required">
<annotation>
<documentation>The target of the given policy</documentation>
</annotation>
</attribute>
<attribute name="statePolicy" type="string" use="required">
<annotation>
<documentation>The state policy that is being contributed</documentation>
<appInfo><meta.attribute kind="java" basedOn=":com.elasticpath.cmclient.policy.StatePolicy"/></appInfo>
</annotation>
</attribute>
<attribute name="priority" type="string" use="required">
<annotation>
<documentation>The priority of the contributed policy.
This is used by a policy resolver to combine contributed policies.</documentation>
</annotation>
</attribute>
</complexType>
</element>
</schema>
Creating a State Policy aware UI plugin class
note
You must add the com.elasticpath.cmclient.policy
plugin to the Required Plug-ins list in your plugin’s Dependencies.
UI plugins that need State Policies support must extend AbstractPolicyAwareUIPlugin
. Only one method must be implemented: getPluginId()
.
The plugin ID provided is used by the State Policy Extension Retriever to get state policies from the plugin extension point. It is responsible for ensuring that the State Policy Contributions from the Change Set plugin are included when the Change Sets feature is enabled. (If the Change Sets feature is disabled, the state policy contributions from the Change Set plugin are ignored.)
The State Policy Resolver is responsible for merging all State Policy Contributions into a single State Policy that can be applied to the relevant State Policy Targets.
AbstractPolicyAwareUIPlugin.getStatePolicyResolver()
returns a default PriorityStatePolicyResolver
object. PriorityStatePolicyResolver
uses the priority of the State Policy Contributions to determine which rules take precedence (highest priority wins). If two policies have the same priority, the policies will be combined with a logical AND
to determine editability.
note
A plugin which is State Policy aware can choose to use its own resolver, or request the default one from the Policy plugin. To use a different resolver as the default, modify the extension in the Policy plugin’s statePolicyResolver
extension point.
Creating a State Policy aware dialog
Before You Begin
Make sure that you have created a State Policy extension point in the plugin.
For a Commerce Manager dialog to be State Policies aware, it must extend AbstractPolicyAwareDialog
and implement ObjectGuidReceiver
.
The dialog must also have a zero-parameter constructor and must be able to perform all persistence operations (retrieve, update, save, etc.) by itself.
The following methods must be implemented:
getTargetIdentifier
setObjectGuid
createDialogContent
getOkButtonPolicyActionContainer
refreshLayout
getTargetIdentifier
Must return the unique identifier that will be used to map State Policy Contribution to this object.
//com.elasticpath.cmclient.catalog/src/main/java/com/elasticpath/cmclient/catalog/dialogs/catalog/VirtualCatalogDialog.java
public String getTargetIdentifier() {
if (this.virtualCatalog.isPersisted()) {
return "virtualCatalogDialogTarget"; //$NON-NLS-1$
}
return "virualCatalogDialogEditableTarget"; //$NON-NLS-1$
}
setObjectGuid
Must be implemented to retrieve the dialog’s dependent object from its GUID.
//com.elasticpath.cmclient.catalog/src/main/java/com/elasticpath/cmclient/catalog/dialogs/catalog/VirtualCatalogDialog.java
public void setObjectGuid(final String objectGuid) {
if (objectGuid == null) {
virtualCatalog = getBeanFactory().getBean(ContextIdNames.CATALOG);
virtualCatalog.setMaster(false);
} else {
virtualCatalog = catalogService.findByCode(objectGuid);
if (virtualCatalog == null) {
throw new IllegalArgumentException(
NLS.bind(CoreMessages.Given_Object_Not_Exist, new String[] {"Virtual Catalog", objectGuid})); //$NON-NLS-1$
}
}
}
createDialogContent
It is responsible for creating and laying out the dialog’s UI controls.
The first step is usually to add a Policy Action Container. Then use the IPolicyTargetLayoutComposite
's add methods to add UI controls. These methods take the PolicyActionContainer
object as their final argument. This ensures that a StateChangeTarget
is added to the PolicyActionContainer
for each UI control that is added to the dialog.
//com.elasticpath.cmclient.catalog/src/main/java/com/elasticpath/cmclient/catalog/dialogs/catalog/VirtualCatalogDialog.java
protected void createDialogContent(final IPolicyTargetLayoutComposite dialogComposite) {
// This name does not need to match anything necessarily
readOnlyVirtualCatalogPolicyContainer = addPolicyActionContainer("readOnlyVirtualCatalogDialog"); //$NON-NLS-1$
editableVirtualCatalogPolicyContainer = addPolicyActionContainer("virtualCatalogDialog"); //$NON-NLS-1$
mainPolicyComposite = dialogComposite
.addGridLayoutComposite(2, false, dialogComposite
.createLayoutData(IEpLayoutData.FILL, IEpLayoutData.BEGINNING, true, false),
editableVirtualCatalogPolicyContainer);
final IEpLayoutData mainCompositeFieldData = mainPolicyComposite
.createLayoutData(IEpLayoutData.FILL, IEpLayoutData.BEGINNING, true, true);
final IEpLayoutData mainCompositeLabelData = mainPolicyComposite
.createLayoutData(IEpLayoutData.BEGINNING, IEpLayoutData.BEGINNING, false, true);
if (isVirtualCatalogPersistent()) {
mainPolicyComposite.addLabelBold(CatalogMessages.CatalogSummarySection_CatalogCode,
mainCompositeLabelData, readOnlyVirtualCatalogPolicyContainer);
this.catalogCode = mainPolicyComposite.addTextField(mainCompositeFieldData, readOnlyVirtualCatalogPolicyContainer);
} else {
mainPolicyComposite.addLabelBoldRequired(CatalogMessages.CatalogSummarySection_CatalogCode,
mainCompositeLabelData, editableVirtualCatalogPolicyContainer);
this.catalogCode = mainPolicyComposite.addTextField(mainCompositeFieldData, editableVirtualCatalogPolicyContainer);
}
}
getOkButtonPolicyActionContainer
Returns the Policy Action Container that will control the state of the dialog’s OK
or Save
button.
//com.elasticpath.cmclient.catalog/src/main/java/com/elasticpath/cmclient/catalog/dialogs/catalog/VirtualCatalogDialog.java
protected PolicyActionContainer getOkButtonPolicyActionContainer() {
return this.editableVirtualCatalogPolicyContainer;
}
refreshLayout
Can be implemented to refresh the layout of parent composites if required.
//com.elasticpath.cmclient.catalog/src/main/java/com/elasticpath/cmclient/catalog/dialogs/catalog/VirtualCatalogDialog.java
protected void refreshLayout() {
if (mainPolicyComposite != null) {
mainPolicyComposite.getSwtComposite().layout();
mainPolicyComposite.getSwtComposite().getParent().layout();
}
}
Registering the dialog for Change Sets
In order to edit an object from within a Change Sets editor, its dialog must be registered as a Change Set supported component. To do this, edit the plugin’s plugin.xml
and add a supportedComponents
extension point (if one doesn’t already exist). Inside the extension point, add a dialog
element with a class
attribute set to the name of your dialog class and an objectType
attribute containing a display name for the dialog.
The following example shows the registration for com.elasticpath.cmclient.catalog.dialogs.catalog.VirtualCatalogDialog
.
<extension point="com.elasticpath.cmclient.changeset.supportedComponents">
...
<dialog
class="com.elasticpath.cmclient.catalog.dialogs.catalog.VirtualCatalogDialog"
objectType="Catalog">
</dialog>
</extension>
Creating a State Policy aware action
Before You Begin
Make sure that you have Creating a State Policy extension point in the plugin.
For a Commerce Manager action to be State Policies aware, it must extend AbstractPolicyAwareAction
.
The getTargetIdentifier
method must be implemented to return the unique identifier that will be used to map Creating a State Policy Contribution to this target.
public String getTargetIdentifier() {
return targetIdentifier;
}
Adding State Policy aware UI controls
Use the IPolicyTargetLayoutComposite
's add methods to add State Policies aware UI controls. These methods take a PolicyActionContainer
object as their final parameter. This ensures that a StateChangeTarget
is added to the PolicyActionContainer
for each UI control that is added to the composite
If you are Creating a State Policy aware dialog, use the IPolicyTargetLayoutComposite
in the createDialogContent
method. Otherwise, use the PolicyTargetCompositeFactory
to get an IPolicyTargetLayoutComposite
instance.
final PolicyActionContainer myPolicyContainer = addPolicyActionContainer("myPolicyContainer"); //$NON-NLS-1$
// Create a grid layout composite
final IEpLayoutComposite gridLayoutComposite =
CompositeFactory.createGridWrapLayoutComposite(parent, 2, false);
// Wrap it in a State Policy aware layout composite
final IPolicyTargetLayoutComposite policyComposite =
PolicyTargetCompositeFactory.wrapLayoutComposite(gridLayoutComposite);
// Add UI controls
policyComposite.addLabelBoldRequired("Label", labelData, myPolicyContainer);
policyComposite.addTextField(fieldData, myPolicyContainer);
Creating a State Policy Contribution
Each State Policy aware dialog, editor, section, page, and action must be mapped to a State Policies through a State Policy Contribution.
Before You Begin
Make sure that you have created a State Policy extension point in the plugin.
A State Policy should live in the plugin most appropriate for the code that the policy is using to determine state. For example, all State Policies relating to Change Sets are in the com.elasticpath.cmclient.changeset
plugin. Multiple plugins can contribute policies to a particular StatePolicyTarget
. The extension point framework enables this without any dependency coupling between plugins.
In the Commerce Manager plugin’s plugin.xml
file, add a statePolicyContribution
element for each StatePolicyTarget
that you need to map to a State Policy.
The following list shows the attributes that must be configured on the statePolicyContribution
.
targetId
The target id of the
statePolicyTarget
object as returned by it’sgetTargetIdentifier
method.statePolicy
The fully qualified class name of the State Policy.
priority
The priority. This value is used by the
StatePolicyResolver
to determine the order in which State Policy Contributions are applied
A State Policy can be applied to multiple targets and multiple State Policies can apply to the same target. If there is a conflict between policies, the behavior is determined by the plugin’s State Policy aware UI plugin class. For the DefaultStatePolicyResolver
, the contribution with the highest priority generally takes precedence.
The following example, shows State Policy Contributions mapped to a dialog, an action, and an editor.
<extension point="com.elasticpath.cmclient.catalog.statePolicy">
...
<statePolicyContribution
priority="10"
statePolicy="com.elasticpath.cmclient.catalog.policy.EditVirtualCatalogAuthorizationStatePolicy"
targetId="virualCatalogDialogEditableTarget">
</statePolicyContribution>
<statePolicyContribution
priority="10"
statePolicy="com.elasticpath.cmclient.catalog.policy.EditVirtualCatalogAuthorizationStatePolicy"
targetId="editCatalogCategoryAction">
</statePolicyContribution>
<statePolicyContribution
priority="10"
statePolicy="com.elasticpath.cmclient.catalog.policy.CategoryAuthorizationStatePolicy"
targetId="categoryEditor">
</statePolicyContribution>
<extension>
Creating a State Policy aware editor
Before You Begin
Make sure that you have created a State Policy extension point in the plugin.
For a Commerce Manager editor to be State Policies aware, it must extend AbstractPolicyAwareFormEditor
. The following methods must be implemented:
getTargetIdentifier
addPages
getTargetIdentifier
Must return the unique identifier that will be used to map a State Policy Contribution to this object.
public String getTargetIdentifier() {
return "categoryEditor"; //$NON-NLS-1$
}
addPages
It is responsible for creating and adding the editor’s pages. Before adding pages, you need to add a Policy Action Container. Then use addPage
to add pages to the editor.
addPage
takes the PolicyActionContainer
object as its second parameter. This ensures that StateChangeTarget
objects are added to the PolicyActionContainer
for the UI controls in each page that is added to the editor.
protected void addPages() {
try {
final PolicyActionContainer pageContainer = addPolicyActionContainer("categoryEditor"); //$NON-NLS-1$
addPage(new CategorySummaryPage(this), pageContainer);
addPage(new CategoryAttributePage(this), pageContainer);
addPage(new CategorySeoPage(this), pageContainer);
addPage(new CategoryFeaturedProductsPage(this), pageContainer);
} catch (final PartInitException e) {
throw new EpUiException(e);
}
}
Use `IPolicyTargetComposite`
Make sure the pages use PolicyTargetCompositeFactory
to create an IPolicyTargetComposite
and use its add methods to State Policy aware UI controls.
Registering the editor for Change Sets
In order to edit an object from within a Change Sets, its editor must be registered as a Change Set supported component. To do this, edit the plugin’s plugin.xml
and add a supportedComponents
extension point (if one doesn’t already exist). Inside the extension point, add an editor element with an editorId
attribute set to the name of your editor class and an objectType
attribute containing a display name for the editor.
The following example shows the registration for com.elasticpath.cmclient.catalog.editors.category.CategoryEditor
.
<extension point="com.elasticpath.cmclient.changeset.supportedComponents">
...
<editor
editorId="com.elasticpath.cmclient.catalog.editors.category.CategoryEditor"
objectType="Category">
</editor>
</extension>