AEM Content Fragments in Depth. Custom Schemas. Part 2AEM Content Fragments in Depth. Custom Schemas. Part 2

Tech Tips

10 min read

AEM Content Fragments in Depth. Custom Schemas. Part 2

Tags

#AEM

#Content Fragments

#Digital Experience

#Digital Marketing Technology

Share

In our previous article, we introduced you to content fragments. Today, we let you roll up your sleeves and hard code your way through the intricacies of AEM. We’ll start by defining the Video and Event schema models described in the previous article and then explore brightcove AEM video sync.

‍

Video Schema Model

conf > site-com > settings > dam > cfm > models > video:

‍

<!--?xml version="1.0" encoding="UTF-8"?-->
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" jcr:primarytype="cq:Template" allowedpaths="[/content/entities(/.*)?]" ranking="{Long}100">
    <jcr:content cq:scaffolding="/conf/site-com/settings/dam/cfm/models/video/jcr:content/model" cq:templatetype="/libs/settings/dam/cfm/model-types/fragment" jcr:primarytype="cq:PageContent" jcr:title="Video" sling:resourcesupertype="dam/cfm/models/console/components/data/entity" sling:resourcetype="dam/cfm/models/console/components/data/entity/default">
        <model cq:targetpath="/content/entities" jcr:primarytype="cq:PageContent" sling:resourcetype="wcm/scaffolding/components/scaffolding" datatypesconfig="/mnt/overlay/settings/dam/cfm/models/formbuilderconfig/datatypes" maxgeneratedorder="20">
            <cq:dialog jcr:primarytype="nt:unstructured" sling:resourcetype="cq/gui/components/authoring/dialog">
                <content jcr:primarytype="nt:unstructured" sling:resourcetype="granite/ui/components/coral/foundation/fixedcolumns">
                    <items jcr:primarytype="nt:unstructured" maxgeneratedorder="20">
                        <brc_title jcr:primarytype="nt:unstructured" sling:resourcetype="granite/ui/components/coral/foundation/form/textfield" fieldlabel="Name" listorder="1" maxlength="255" required="on" metatype="text-single" name="brc_title" renderreadonly="false" showemptyinreadonly="true" valuetype="string">
                        <brc_poster jcr:primarytype="nt:unstructured" sling:resourcetype="dam/cfm/models/editor/components/contentreference" fieldlabel="Poster" filter="hierarchy" listorder="8" metatype="reference" name="brc_poster" namesuffix="contentReference" renderreadonly="false" rootpath="/content/dam" showemptyinreadonly="true" validation="cfm.validation.contenttype.image" valuetype="string/reference">                      
                        <brc_duration jcr:primarytype="nt:unstructured" sling:resourcetype="granite/ui/components/coral/foundation/form/textfield" disabled="{Boolean}true" fieldlabel="Duration" listorder="1" maxlength="255" metatype="text-single" name="brc_duration" renderreadonly="false" showemptyinreadonly="true" valuetype="string">
                        <brc_created_at jcr:primarytype="nt:unstructured" sling:resourcetype="granite/ui/components/coral/foundation/form/datepicker" disabled="{Boolean}true" displayedformat="YYYY-MM-DD HH:mm" emptytext="YYYY-MM-DD HH:mm" fieldlabel="Created" listorder="5" metatype="date" name="brc_created_at" renderreadonly="false" showemptyinreadonly="true" type="datetime" valueformat="YYYY-MM-DD[T]HH:mm:ss.000Z" valuetype="calendar">
                            <granite:data jcr:primarytype="nt:unstructured" typehint="Date">
                        </granite:data></brc_created_at>
                        <brc_id jcr:primarytype="nt:unstructured" sling:resourcetype="granite/ui/components/coral/foundation/form/hidden" fieldlabel="Id" listorder="1" maxlength="255" metatype="text-single" name="brc_id" renderreadonly="false" showemptyinreadonly="true" valuetype="string">
                    </brc_id></brc_duration></brc_poster></brc_title></items>
                </content>
            </cq:dialog>
        </model>
    </jcr:content>
</jcr:root>

‍

Event Schema Model

This schema will serve both for upcoming Event and On-Demand webinars conf > site-com > settings > dam > cfm > models > event:

‍

<!--?xml version="1.0" encoding="UTF-8"?-->
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" jcr:primarytype="cq:Template" allowedpaths="[/content/entities(/.*)?]" ranking="{Long}100">
    <jcr:content cq:scaffolding="/conf/site-com/settings/dam/cfm/models/event/jcr:content/model" cq:templatetype="/libs/settings/dam/cfm/model-types/fragment" jcr:primarytype="cq:PageContent" jcr:title="Event" sling:resourcesupertype="dam/cfm/models/console/components/data/entity" sling:resourcetype="dam/cfm/models/console/components/data/entity/default">
        <model cq:targetpath="/content/entities" jcr:primarytype="cq:PageContent" sling:resourcetype="wcm/scaffolding/components/scaffolding" datatypesconfig="/mnt/overlay/settings/dam/cfm/models/formbuilderconfig/datatypes" maxgeneratedorder="20">
            <cq:dialog jcr:primarytype="nt:unstructured" sling:resourcetype="cq/gui/components/authoring/dialog">
                <content jcr:primarytype="nt:unstructured" sling:resourcetype="granite/ui/components/coral/foundation/fixedcolumns">
                    <items jcr:primarytype="nt:unstructured" maxgeneratedorder="20">
                        <name jcr:primarytype="nt:unstructured" sling:resourcetype="granite/ui/components/coral/foundation/form/textfield" fieldlabel="Event Name" listorder="1" maxlength="255" metatype="text-single" name="eventName" renderreadonly="false" required="on" showemptyinreadonly="true" valuetype="string">
                        <start_date jcr:primarytype="nt:unstructured" sling:resourcetype="granite/ui/components/coral/foundation/form/datepicker" displayedformat="YYYY-MM-DD HH:mm" displaytimezonemessage="{Boolean}true" emptytext="YYYY-MM-DD HH:mm" fieldlabel="Start Date" listorder="5" metatype="date" name="startDate" renderreadonly="false" required="on" showemptyinreadonly="true" type="[datetime,datetime]" valueformat="YYYY-MM-DD[T]HH:mm:ss.000Z" valuetype="calendar/datetime">
                            <granite:data jcr:primarytype="nt:unstructured" typehint="Date">
                        </granite:data></start_date>
                        <end_date jcr:primarytype="nt:unstructured" sling:resourcetype="granite/ui/components/coral/foundation/form/datepicker" displayedformat="YYYY-MM-DD HH:mm" displaytimezonemessage="{Boolean}true" emptytext="YYYY-MM-DD HH:mm" fieldlabel="”End" date"="" listorder="5" metatype="date" name="endDate" renderreadonly="false" showemptyinreadonly="true" type="[datetime,datetime]" valueformat="YYYY-MM-DD[T]HH:mm:ss.000Z" valuetype="calendar/datetime">
                            <granite:data jcr:primarytype="nt:unstructured" typehint="Date">
                        </granite:data></end_date>
                        <duration jcr:primarytype="nt:unstructured" sling:resourcetype="granite/ui/components/coral/foundation/form/textfield" fielddescription="Please provide event duration in minutes." fieldlabel="Duration, minutes" listorder="1" maxlength="255" metatype="text-single" name="duration" renderreadonly="false" showemptyinreadonly="true" valuetype="string">
                        <form_id jcr:primarytype="nt:unstructured" sling:resourcetype="granite/ui/components/coral/foundation/form/textfield" fieldlabel="FORM - ID" listorder="1" maxlength="255" metatype="text-single" name="formID" renderreadonly="false" showemptyinreadonly="true" valuetype="string">      
                        <webinarvideoreference jcr:primarytype="nt:unstructured" sling:resourcetype="dam/cfm/models/editor/components/fragmentreference" allownew="{Boolean}true" fieldlabel="Webinar Video" filter="hierarchy" fragmentmodelreference="/conf/site-com/settings/dam/cfm/models/video" listorder="21" metatype="fragment-reference" name="webinarVideoReference" namesuffix="contentReference" renderreadonly="false" rootpath="/content/dam/site-com/content-fragments/${psCFRegion}/webinars" showemptyinreadonly="true" subtype="content-fragment" valuetype="string/content-fragment">
                            <field jcr:primarytype="nt:unstructured" rootpath="/content/dam/site-com/content-fragments/${psCFRegion}/webinars">
                            <granite:data jcr:primarytype="nt:unstructured">
                        </granite:data></field></webinarvideoreference>
                    </form_id></duration></name></items>
                </content>
            </cq:dialog>
        </model>
    </jcr:content>
</jcr:root>

‍

Video Sync Process: Using AEM Brightcove Connector

We’re using Brightcove AEM for videos, so now we’ll set a video sync-up using Adobe AEM Brightcove Connector (the Adobe-AEM-Brightcove Connector allows you to manage Brightcove Video Cloud videos and players within AEM and easily embed videos in AEM pages).

‍

We’ll start by setting up the sync into ”/content/dam/brightcove/en” and then create a process that will create video content fragments based on synced videos.

‍

First of all, we add a pom.xml configuration:

‍

<dependency>
  <groupid>com.coresecure.brightcove.cq5</groupid>
  <artifactid>brightcove_connector</artifactid>
  <type>zip</type>
  <version>5.6</version>
</dependency>

‍

And the sync config apps > site-com > osgiconfig > config.author > com.coresecure.brightcove.wrapper.sling.BrcServiceImpl~b1d15603-3b49-459c-844d-a15120ddebd8.cfg.json:

‍

{
  "client_secret": "{Enter your Brightcove client Secret API}",
  "accountAlias": "{Enter a name for the account to be displayed in the Connector}",
  "client_id": "{Enter your Brightcove client ID from the Brightcove API Authentication page }",
  "allowed_groups": [
    "{Specify the group that will access the Connector (required) be sure that it is a group you are included in}"
  ],
  "key": "{Default Video Player Key}",
  "asset_integration_path": "/content/dam/brightcove/en"
}

‍

And apps > site-com > osgiconfig > config.author > com.coresecure.brightcove.wrapper.webservices.BrcReplicationHandler.cfg.json:

‍

{ 
  "target_directory":"/content/dam/brightcove/en"
}

‍

The Brightcove Import Listener to create or update a video content fragment based on video assets from Brightcove AEM (a simplified code with all non-null checks skipped):

‍

.....
    @Activate
    public void activate() {
        try {
            session = repository.loginService(SERVICE_NAME, null);
 
            JackrabbitEventFilter ef = new JackrabbitEventFilter()
                    .setAbsPath("/content/dam/brightcove/en")
                    .setNodeTypes(new String[]{JcrConstants.NT_UNSTRUCTURED})
                    .setEventTypes(Event.PROPERTY_CHANGED | Event.PROPERTY_ADDED)
                    .setIsDeep(true)
                    .setNoExternal(true);
            JackrabbitObservationManager om = (JackrabbitObservationManager) session.getWorkspace().getObservationManager();
            om.addEventListener (this, ef);
 
        } catch (RepositoryException e) {
            LOG.error("Unable to register session", e);
        }
    }
.......
 
    @Override
    public void onEvent(EventIterator events) {
        try (ResourceResolver resourceResolver = getResourceResolverFromSession(session, resourceResolverFactory)) {      
            while (events.hasNext()) {
                Event event = events.nextEvent();
                String path = event.getPath();
                if (!path.endsWith(ConnectorField.LAST_SYNC.getFieldName())) {
                    continue;
                }
                String metadataRelPath = joinAsPath(JCR_CONTENT, METADATA_FOLDER);
                String targetPath = StringUtils.substringBeforeLast(path, "/" + metadataRelPath);
 
                WorkflowSession workflowSession = resourceResolver.adaptTo(WorkflowSession.class);
                WorkflowModel workflowModel = workflowSession.getModel(“{brightcove sync metadata workflow path}”);
 
                Map<string, object=""> properties = new HashMap<>();
                properties.put(JOB_PARAMETER_PATH, targetPath);
                properties.put(JOB_PARAMETER_WF_ID, WF_MODEL_AUTHOR_ID);
                jobManager.addJob(TOPIC, properties);
            }
 
        } catch (WorkflowException | RepositoryException | RuntimeException ex) {
            LOG.error("Exception", ex);
        }
    }
</string,>

‍

And a simple enum for Brightcove AEM field mapping:

‍

package com.wcm.site.model.brightcove;
 
import com.adobe.cq.dam.cfm.ContentFragment;
import com.wcm.site.util.CFMUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.ValueMap;
 
import java.util.Optional;
 
public enum ConnectorField {
    ID("brc_id"),
    TITLE("brc_title"),
    TEXT_TRACKS("brc_text_tracks"),
    CUSTOM_FIELDS("brc_custom_fields"),
    BRC_TAGS("brc_tags"),
    TAGS("tags"),
    PAGE_NAME_HINT("pageNameHint"),
    DETAILS("details"),
    IS_AEM_EXPOSED("isaemexposed"),
    TYPE("type"),
    DESCRIPTION("brc_description"),
    LONG_DESCRIPTION("brc_long_description"),
    CREATED_AT("brc_created_at"),
    UPDATED_AT("brc_updated_at"),
    DURATION("brc_duration"),
    TEXT("text"),
    POSTER("brc_poster"),
    STAGE("stage"),
    LAST_SYNC("brc_lastsync"),
    THUMBNAIL("brc_thumbnail");
 
 
    private String fieldName;
 
    ConnectorField(String fieldName) {
        this.fieldName = fieldName;
    }
 
    public String getContent(Optional<contentfragment> contentFragment) {
        return CFMUtils.getContent(contentFragment, this.fieldName);
    }
 
    public <t> T getValue(Optional<contentfragment> contentFragment, Class<t> type) {
        return CFMUtils.getValue(contentFragment, this.fieldName, type);
    }
 
    public boolean getBooleanValue(Optional<contentfragment> contentFragment) {
        return BooleanUtils.toBoolean(CFMUtils.getValue(contentFragment, this.fieldName));
    }
 
    public <t> T getValue(ValueMap valueMap, Class<t> type) {
        return valueMap.get(this.getFieldName(), type);
    }
 
    public String getValue(Optional<contentfragment> contentFragment) {
        return Optional.ofNullable(getValue(contentFragment, String.class))
                .orElse(StringUtils.EMPTY);
    }
 
    public String getValue(ValueMap valueMap) {
        return Optional.ofNullable(getValue(valueMap, String.class))
                .orElse(StringUtils.EMPTY);
    }
 
    public boolean getBooleanValue(ValueMap valueMap) {
        return BooleanUtils.toBoolean(this.getValue(valueMap));
    }
 
    public String getFieldName() {
        return fieldName;
    }
}
</contentfragment></t></t></contentfragment></t></contentfragment></t></contentfragment>

‍

Stay tuned for the next articles on this subject. We’ll be showing how to manage Event content fragments and corresponding pages next. Should you have any questions or need AEM development services, reach out to us. We work wonders with AEM!

Resource Hub

Our Latest Stories & Industry Insights

View Resource Hub

How AI and Data Analytics Boost Travel and Transportation Performance

14 min read

October 28, 2026

AI Maturity Assessment: How to Evaluate Your Organization's AI Capability and Where to Start

14 min read

October 7, 2026

Agentic SDLC: How to Modernize Your Software Development Lifecycle with Autonomous AI Agents

12 min read

October 5, 2026

Exadel Becomes a Preferred Partner in the Claude Partner Network Services Track

5 min read

October 5, 2026

Jev and the Decision Layer Between Your Agents

10 min read

October 2, 2026

What Are AI Consulting Services? A Guide For Enterprise Leaders

16 min read

September 29, 2026
Two people sitting at a table with a laptop.

Let’s make your next project faster, safer, smarter.

Get In Touch