Typed Entities
Typed entities can be used to group data objects based on their predetermined purpose. For instance, since there is a large amount of overlap between products and product bundles, we decided to turn Product
objects into typed entities. Using this type indicator, we can create distinct Admin sections for products and product bundles, and manage them in separate contexts.
In this case, we could have a normal "Product" type and a "Bundle" type. Other than the type of the product, there are only minor differences between the two. For example, a "Bundle" typed products can maintain a list of child products included within it, whereas a normal "Product" typed products may not.
By modeling Product
as a typed entity, we also save on database complexity. Each type is no longer represented as a separate database table, avoiding the potential of numerous, expensive joins.
Creating a typed entity requires a few steps:
- Create a new "type"
- Implement the
TypedEntity
interface - Add the associated permissions
- Ensure the filter chain is configured correctly
Type
Types within Broadleaf are extensions of BroadleafEnumerationType
. See ProductType for an example of how to set one up.
TypedEntity Interface
To make an existing entity "typed", simply implement the TypedEntity
interface and each of the following methods:
BroadleafEnumerationType getType() {...}
- Returns the type of the Entity.
void setType(BroadleafEnumerationType type) {...}
- Sets the type of the Entity.
String getTypeFieldName() {...}
- Returns the persisted type field name.
- This is the actual name of the field that identifies the entity as its type. For example on
ProductImpl
this method might return the string"productType"
.
String getDefaultType() {...}
- Returns the default type to be used for this entity.
Permissions
By mapping the different types to individual admin sections, we can controll access based off user permissions. The following is an example of adding "Product Bundles" as a new section, and setting up the appropriate permissions.
-- --------------------------------------------
-- Add Admin Section for Product Bundles
-- --------------------------------------------
INSERT INTO BLC_ADMIN_SECTION (ADMIN_SECTION_ID, CEILING_ENTITY, DISPLAY_ORDER, TEMPLATE_SITE_VISIBILITY, NAME, SECTION_KEY, URL, ADMIN_MODULE_ID) VALUES (1, 'org.broadleafcommerce.core.catalog.domain.Product', 3001, 'VISIBLE', 'AddonProduct', 'Product Bundles', '/product:bundle', -1);
As far as the URL is concerned, ensure that you follow the pattern of /product:{type}
where type
matches your new BroadleafEnumerationType
extension's type value.
-- --------------------------------------------
-- Add Admin Permission for Product Bundles
-- --------------------------------------------
INSERT INTO blc_admin_permission (ADMIN_PERMISSION_ID, DESCRIPTION, IS_FRIENDLY, NAME, PERMISSION_TYPE) VALUES (-36002, 'View Product Bundles', 1, 'PERMISSION_PRODUCT_BUNDLE', 'READ');
INSERT INTO blc_admin_permission (ADMIN_PERMISSION_ID, DESCRIPTION, IS_FRIENDLY, NAME, PERMISSION_TYPE) VALUES (-36003, 'Maintain Product Bundles', 1, 'PERMISSION_PRODUCT_BUNDLE', 'ALL');
-- --------------------------------------------
-- Map Admin Permissions to Friendly versions (Bundles)
-- --------------------------------------------
INSERT INTO BLC_ADMIN_PERMISSION_XREF (CHILD_PERMISSION_ID, ADMIN_PERMISSION_ID) VALUES (-102, -36002);
INSERT INTO BLC_ADMIN_PERMISSION_XREF (CHILD_PERMISSION_ID, ADMIN_PERMISSION_ID) VALUES (-103, -36003);
-- --------------------------------------------
-- Set the Permissions for the new Admin Section (Bundles)
-- --------------------------------------------
INSERT INTO BLC_ADMIN_SEC_PERM_XREF (ADMIN_SECTION_ID, ADMIN_PERMISSION_ID) VALUES (1, -36003);
-- Give the existing 'Maintain Products' permission access to this new section
INSERT INTO BLC_ADMIN_SEC_PERM_XREF (ADMIN_SECTION_ID, ADMIN_PERMISSION_ID) VALUES (1, -103);
The important thing to note above, is that we're mapping our new product bundle permissions to the existing standard product permissions. This is necessary because if we skipped this step, our new admin section would not have access to any existing product controllers and persistence handlers.
Filter Chain
The blAdminTypedEntityRequestFilter
filter is required in your admin module to facilitate the management of typed entities. This filter is responsible for routing typed entites to the correct controllers and maintaining any additional model information required by the admin interface.
In the file, applicationContext-admin-filter.xml
, ensure that this filter is placed last in the blPostSecurityFilterChain
filter chain:
<bean id="blPostSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map request-matcher="ant">
<sec:filter-chain pattern="/**" filters="
...,
blAdminTypedEntityRequestFilter"/>
</sec:filter-chain-map>
</bean>
New AdminPresentation Concepts
To support Typed Entities, a new AdminPresentation annotation has been added:
showIfFieldEquals
This annotation allows you to specify a fieldName
and a list of fieldValues
. After the entityForm is populated, the FormBuilderService
will iterate through the fieldName
s and check if their current value equals any of the values provided in the fieldValues
list. This is treated as an OR
and once it finds a field that matches a single value, it will consider it as true
and show the field.
An example of its usage is located in AdvancedProductImpl.java
:
@AdminPresentationCollection(
friendlyName = "AdvancedProduct_ProductAddOns",
sortProperty = "displayOrder",
showIfFieldEquals = { @FieldValueConfiguration(
fieldName = "embeddableAdvancedProduct.type",
fieldValues = { "PRODUCT", "BUNDLE" })
})
protected List<ProductAddOnXref> childProductAddOns = new ArrayList<ProductAddOnXref>();
Here the childProductAddOns
listgrid will be shown if the embeddableAdvancedProduct.type
is equal to "PRODUCT"
or "BUNDLE"
.
This can also be accomplished from xml overrides:
<mo:override id="blMetadataOverrides">
<mo:overrideItem ceilingEntity="org.broadleafcommerce.core.catalog.domain.Product">
<mo:field name="childProductAddOns">
<mo:showIfFieldEquals fieldName="embeddableAdvancedProduct.type">
<mo:property value="PRODUCT"/>
<mo:property value="BUNDLE"/>
</mo:showIfFieldEquals>
</mo:field>
</mo:overrideItem>
</mo:override>