Documentation

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:

  1. Create a new "type"
  2. Implement the TypedEntity interface
  3. Add the associated permissions
  4. 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 fieldNames 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>