Adobe Experience Manager Blog

AEM concepts, snippets and implementation

Coral 3 – Granite UI components

1

There are few modifications to the way Granite UI components are configured for Coral 3 components. Example: radio, checkbox, multifield etc.

Sharing snippets for frequently used Granite UI components (verified on AEM 6.3):

Alert:

<readonlySelectedAlert
   granite:class="cmp-form-textfield-readonlyselected-alert"
   jcr:primaryType="nt:unstructured"
   sling:resourceType="granite/ui/components/coral/foundation/alert"
   size="S"
   text="Alert text"
   variant="warning"/>

Accordian:

<accordion
    granite:class="js-cq-IPEPlugin-container"
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/accordion"
    variant="quiet">
    <items jcr:primaryType="nt:unstructured">
        <source
           jcr:primaryType="nt:unstructured"
           jcr:title="Source"
           sling:resourceType="granite/ui/components/coral/foundation/container"
           maximized="{Boolean}true">
            <items jcr:primaryType="nt:unstructured">
                <allowupload
                    granite:class="js-cq-ImageEditor-allowUpload"
                    jcr:primaryType="nt:unstructured"
                    sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
                    checked="{Boolean}true"
                    deleteHint="{Boolean}false"
                    name="./allowUpload"
                    text="Allow asset upload from file system"
                    uncheckedValue="{Boolean}false"
                    value="{Boolean}true"/>
            </items>
           <parentConfig
               jcr:primaryType="nt:unstructured"
               active="{Boolean}true"/>
       </source>
       <orientation
               granite:class="js-cq-IPEPlugin-group"
               jcr:primaryType="nt:unstructured"
               jcr:title="Orientation"
               sling:resourceType="granite/ui/components/coral/foundation/container"
               maximized="{Boolean}true">
           <items jcr:primaryType="nt:unstructured">
               <rotate
                   jcr:primaryType="nt:unstructured"
                   jcr:title="Rotate"
                   sling:resourceType="cq/gui/components/authoring/dialog/inplaceediting/configuration/plugin"
                   features="right"
                   name="rotate"/>
               <flip
                   jcr:primaryType="nt:unstructured"
                   jcr:title="Flip"
                   sling:resourceType="cq/gui/components/authoring/dialog/inplaceediting/configuration/plugin"
                   features="horizontal,vertical"
                   name="flip"/>
           </items>
       </orientation>
       <crop
           granite:class="js-cq-IPEPlugin-group"
           jcr:primaryType="nt:unstructured"
           jcr:title="Cropping"
           sling:resourceType="granite/ui/components/coral/foundation/container"
           maximized="{Boolean}true">
           <items jcr:primaryType="nt:unstructured">
               <crop
                   jcr:primaryType="nt:unstructured"
                   jcr:title="Allow crop"
                   sling:resourceType="cq/gui/components/authoring/dialog/inplaceediting/configuration/plugin"
                   features="*"
                   name="crop"/>
                   <configWrapper
                       jcr:primaryType="nt:unstructured"
                       sling:resourceType="cq/gui/components/authoring/dialog/inplaceediting/configuration/wrapper"
                       configPath="./plugins/crop/aspectRatios">
                       <aspectratios
                           granite:class="cq-AspectRatio"
                           jcr:primaryType="nt:unstructured"
                           sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
                           composite="{Boolean}true"
                           fieldLabel="Aspect ratios">
                           <field
                               granite:class="cq-AspectRatio-field"
                               jcr:primaryType="nt:unstructured"
                               sling:resourceType="granite/ui/components/coral/foundation/container"
                               name="./plugins/crop/aspectRatios">
                               <items jcr:primaryType="nt:unstructured">
                                   <name
                                       granite:class="cq-AspectRatio-name"
                                       jcr:primaryType="nt:unstructured"
                                       sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                       fieldLabel="Name"
                                       name="name"/>
                                   <ratio
                                       granite:class="cq-AspectRatio-ratio"
                                       jcr:primaryType="nt:unstructured"
                                       sling:resourceType="granite/ui/components/coral/foundation/form/numberfield"
                                       fieldLabel="Ratio"
                                       min="0"
                                       name="ratio"
                                       step="0.0001"/>
                               </items>
                           </field>
                       </aspectratios>
                </configWrapper>
            </items>
        </crop>
    </items>
</accordion>

Checkbox:

  • value: The submit value of the field when it is checked.
  • uncheckedValue: The submit value of the field when it is unchecked.
  • defaultChecked: Indicates if the checkbox is checked.
    warning:: When setting “defaultChecked” = “true”, you have to set the value of “uncheckedValue” so that the form values will be always populated.
    Otherwise Sling Post Servlet will remove the property from the form values, which makes the checkbox to be always checked.
<showDateFilter
    granite:class="cmp-form-option-item-active"
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/foundation/form/checkbox"
    name="./selected"
    text="Show Date Filter"
    uncheckedValue="{Boolean}false"
    defaultChecked="{Boolean}true"
    value="{Boolean}true"/>

Text:

<inputgroup
    jcr:primarytype="nt:unstructured"
    sling:resourcetype="granite/ui/components/coral/foundation/text"
    text="Some text"/>

Textfield:

<filterSectionText
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
    fieldLabel="Some Text"
    name="./someText"/>

TextArea:

<pathbrowser
    jcr:primarytype="nt:unstructured"
    sling:resourcetype="granite/ui/components/coral/foundation/form/pathbrowser"
fieldlabel="Path Browser"
name="./searchPath"
rootpath="/etc/tags">

Fileupload:

<upload granite:class="cq-wcm-fileupload"
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/fileupload"     async="{Boolean}true"     autoStart="{Boolean}false"     multiple="{Boolean}false"     name="./image/file.sftmp"     sizeLimit="100000000"     text="Upload Image"     uploadUrl="will_be_replaced">
    <granite:data
        jcr:primaryType="nt:unstructured"
        cq-msm-lockable="./image"/>
</upload>

Pathbrowser:

<pathBrowser
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/pathbrowser"
    fieldLabel="Path Browser"
    name="./searchPath"
    rootPath="/etc/tags"/>

Pathfield:

 <buttonLinkTo
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
    fieldLabel="Link to"
    name="./buttonLinkTo"
    rootPath="/content"
    suffix=".html"/>

Datepicker:

<datepicker
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/datepicker"
    displayedFormat="MM-DD-YYYY HH:mm"
    fieldLabel="datepicker"
    name="./datepicker"
    type="datetime"
    typeHint="Date"/>

Userpicker:

<userpicker
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/userpicker"
    fieldLabel="User Picker"
    hideServiceUsers="{Boolean}true"
    impersonatesOnly="{Boolean}false"
    name="./user"/>

Select List:

					<listFrom
    granite:class="cmp-options--editor-type-v1"
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/select"
    fieldLabel="List Item"
    name="./listItem">
    <items jcr:primaryType="nt:unstructured">
        <children
            jcr:primaryType="nt:unstructured"
            text="Child pages"
            value="children"/>
        <static
            jcr:primaryType="nt:unstructured"
            text="Fixed list"
            value="static"/>
        <search
            jcr:primaryType="nt:unstructured"
            text="Search"
            value="search"/>
        <tags
            jcr:primaryType="nt:unstructured"
            text="Tags"
            value="tags"/>
    </items>
 </listFrom>

Radio:

<radio
   jcr:primaryType="nt:unstructured"
   sling:resourceType="granite/ui/components/coral/foundation/form/radiogroup"
   name="./color"
   vertical="{Boolean}true"
   fieldLabel="Radio">
     <items jcr:primaryType="nt:unstructured">
         <op1
             jcr:primaryType="nt:unstructured"
             text="OP 1"
             value="op1"/>
         <op2
            jcr:primaryType="nt:unstructured"
            text="OP 2"
            value="op2"/>
     </items>
 </radio>

Number field:

<radio jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/radiogroup"
    name="./color"
    vertical="{Boolean}true"
    fieldLabel="Radio">
    <items jcr:primaryType="nt:unstructured">
        <op1  jcr:primaryType="nt:unstructured"
            text="OP 1"
            value="op1"/>
        <op2  jcr:primaryType="nt:unstructured"
            text="OP 2"
           value="op2"/>
   </items>
</radio>

Heading

<MainHeading
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/heading"
    level="{Long}3"
    text="Main"/>

Multifield:

The attached snippet has been verified for:

  • Checkbox
  • Textfield
  • Pathbrowser
  • Pathfield
  • Datepicker
  • Select list
 <pages
     jcr:primaryType="nt:unstructured"
     sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
     composite="{Boolean}true"
     fieldDescription="Click '+' to add a new page"
     fieldLabel="URLs">
     <field
         granite:clas="cmp-options--editor-item-multifield-composite-item coral-Well"
         jcr:primaryType="nt:unstructured"
         sling:resourceType="granite/ui/components/coral/foundation/container"
         name="./items">
         <items jcr:primaryType="nt:unstructured">
             <column
                 granite:clas="cmp-options--editor-item-multifield-composite-item coral-Well"
                 jcr:primaryType="nt:unstructured"
                 sling:resourceType="granite/ui/components/foundation/container">
                 <items jcr:primaryType="nt:unstructured">
                     <showDateFilter
                        granite:class="cmp-form-option-item-active"
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/foundation/form/checkbox"
                        name="./selected"
                        text="Show Date Filter"
                        uncheckedValue="{Boolean}false"
                        value="{Boolean}true"/>
                     <filterSectionText
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                        fieldLabel="Some Text"
                        name="./someText"/>
                     <pathBrowser
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/coral/foundation/form/pathbrowser"
                        fieldLabel="Path Browser"
                        name="./searchPath"
                        rootPath="/etc/tags"/>
                     <buttonLinkTo
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
                        fieldLabel="Link to"
                        name="./buttonLinkTo"
                        rootPath="/content"
                        suffix=".html"/>
                     <datepicker
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/coral/foundation/form/datepicker"
                        displayedFormat="MM-DD-YYYY HH:mm"
                        fieldLabel="datepicker"
                        name="./datepicker"
                        type="datetime"
                        typeHint="Date"/>
                    						<listFrom
                            granite:class="cmp-options--editor-type-v1"
                            jcr:primaryType="nt:unstructured"
                            sling:resourceType="granite/ui/components/coral/foundation/form/select"
                            fieldLabel="List Item"
                            name="./listItem">
                          <items jcr:primaryType="nt:unstructured">
                              <children
                                  jcr:primaryType="nt:unstructured"
                                  text="Child pages"
                                  value="children"/>
                              <static
                                  jcr:primaryType="nt:unstructured"
                                  text="Fixed list"
                                  value="static"/>
                        </items>
                    </listFrom>
                </items>
             </column>
         </items>
     </field>
 </pages>

More Granite UI components for Coral 3 are available at /libs/granite/ui/components/coral/foundation

Errors and resolution:

Error 1: Executing maven command, throws following error:

“The prefix “granite” for attribute “granite:class” associated with an element type “background-color-palette” is not bound.”

  • Resolution: Add following xml namespace to your content.xml

xmlns:granite=”http://www.adobe.com/jcr/granite/1.0&#8243;

Advertisements

AEM – Fetch product info via API

0

Here is a code snippet that can be used to fetch AEM’s product information via APIs.

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.granite.license.ProductInfo;
import com.adobe.granite.license.ProductInfoProvider;

@Component(immediate = true)
@Service(ProductInfoImpl.class)
public class ProductInfoImpl {
   private static final Logger LOG = LoggerFactory.getLogger(ProductInfoImpl.class);

   @Reference
   private ProductInfoProvider piProvider;

   @Activate
   @Modified
   protected void activate(ComponentContext cc) {
       if (piProvider != null) {
           ProductInfo pi = piProvider.getProductInfo();

           //Logging product information
           LOG.info("Product name: " + pi.getName());
           LOG.info("Product short name: " + pi.getShortName());
           LOG.info("Product version: " + pi.getVersion());
           LOG.info("Product short version: " + pi.getShortVersion());
       }
   }
}

 

Information printed in logs:

Product name: Adobe Experience Manager
Product short name: AEM
Product version: 6.3.0
Product short version: 6.3

 

 

 

 

OSGi – Accessing Bundle/Service information from BundleContext

0

Here are few code snippets compiled in a java program, which would help you fetch bundle and service information from BundleContext. The snippets cover:

  • Fetching bundle information
  • Fetching ServiceReference from bundle
  • Fetching Service from bundleContext
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Service;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import blog.techrevel.service.BundleInfo;

@Component(immediate = true)
@Service(BundleInfo.class)
public class BundleInfoImpl implements BundleInfo {
   private static final Logger LOG = LoggerFactory.getLogger(BundleInfoImpl.class);
   private static BundleContext bundleContext;

   @Override
   public void getBundleInfo(BundleContext bundleContext) {
       //Fetching bundles
       Bundle[] bundles = bundleContext.getBundles();

       for (Bundle bundle : bundles) {
           //Bundle details
           LOG.info("State: " + bundle.getState());
           LOG.info("Symbolic name: " + bundle.getSymbolicName());
           LOG.info("Version:" + bundle.getHeaders().get(Constants.BUNDLE_VERSION).toString());
           LOG.info("Imported Packages:" + bundle.getHeaders().get(Constants.IMPORT_PACKAGE));

           extractServicesInfo(bundle);
       }
   }

   // Fetching ServiceReferernces exposed by the bundle
   public static void extractServicesInfo(Bundle bundle) {
       ServiceReference[] registeredServices = bundle.getRegisteredServices();
       if (registeredServices != null) {
           for (ServiceReference registeredService : bundle.getRegisteredServices()) {
               // Fetching any property of the Service
               LOG.info("service.pid: " + registeredService.getProperty("service.pid"));

               // Fetch Service from ServiceReference
               LOG.info("Service: " + bundleContext.getService(registeredService));
           }
       }
   }

   @Activate
   @Modified
   protected void activate(ComponentContext cc) {
       bundleContext = cc.getBundleContext();
       getBundleInfo(bundleContext);
   }
}

More API details can be retrived from:

 

 

Apache Felix – Filtering service references

1

Via an earlier blog Apache Felix – Multiple implementation of Service, we had dicussed:

  1. How to create multiple implementation of a Service
  2. Get references of the registered service implementations as a List.

Here we would be discussing, on how to filter the references and get ONLY pre-defined specific implementation

Scenario:

Consider an online course website. A lecturer can publish his content by submitting a course bundle comprising of html pages, videos and assessment questions.

The lecturer had his/her course live for an year & is appreciated by many curious students. The lecturer now wishes to update the assessment questionnaire, to challenge students to dig deeper.

He/She now submit the latest questionnaire to the onlint course portal for updates.

At this point, the course website needs to assure, that only the “Assessment Import” service implementation is called amogst all available Import implementation (Page, Videos and Assessment)

Implementation details:

Specific service references can be fetched by utilizing the “target” attribute of @Reference annotation. It would comprise of 2 simple steps

Step 1: Add property to identify each implementation

You can add any custom property to uniquely identify the service. For example, in the below sample code, I have added “type” property to uniquely identify my each implementation of CourseImport Service

Page Import Implementation
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import blog.techrevel.service.CourseImport;

@Component(name = "Import HTML Pages")
@Service
@Properties({ @Property(name = "type", value = "page")})
public class PageImport implements CourseImport {

    private static final Logger LOGGER = LoggerFactory.getLogger(PageImport.class);

    @Override
    public void importContent() {
       LOGGER.info("Business Logic to import HTML Pages");
    }

    @Override
    public boolean canProcess(String fileName) {
       return StringUtils.endsWith(fileName, ".html");
    }
}
Video Import Implementation
@Component(name="Import course videos")
@Service
@Properties({ @Property(name = "type", value = "video")})
public class VideoImport implements CourseImport{

 private static final Logger LOGGER = LoggerFactory.getLogger(VideoImport.class);

    @Override
    public void importContent() {
        LOGGER.info("Business Logic to import course videos");
    }

    @Override
    public boolean canProcess(String fileName) {
        return StringUtils.endsWith(fileName, ".mp4");
    }
}
Assessment Import Implementation
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import blog.techrevel.service.CourseImport;

@Component(name="Import assessment questions")
@Service
@Properties({ @Property(name = "type", value = "assessment")})
public class AssessmentImport implements CourseImport{

    private static final Logger LOGGER = LoggerFactory.getLogger(AssessmentImport.class);

    @Override
    public void importContent() {
        LOGGER.info("Business Logic to import assessment questions");
    }

    @Override
    public boolean canProcess(String fileName) {
       return StringUtils.equals(fileName, "assessment.xls");
    }
}

 

Step 2: Use target attribute of @Refernce annotation to filter implementation

The target attribute filters services based on a property available in their ComponentContext

Following sample code, filters only PageImport service by matching ‘page’ value against type property of each implementation.

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import blog.techrevel.service.CourseImport;
import blog.techrevel.service.CourseImportHandler;

@Component(immediate = true)
@Service
public class AssessmentUpdateHandler implements CourseImportHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(CourseImportHandler.class);

    @Reference(target="(type=page)")
    private CourseImport courseImport;

    @Override
    public void importContent() {
        courseImport.importContent();
    }
}

 

Using patterns for reference filtering

The target attribute of @Reference annotation also accepts regex (ldap) patterns. For example:

@Reference(target="(type=*age)")
@Reference(target="(|(type=p*)(type=generic))")

 

 

Apache Felix – Multiple implementation of Service

1

Multiple Service Implementation can be used to define multiple implementations of a Service, and then execute all/specific implementation depending on the use-case.

A close example would be Replication Agents. Here a specific replication implementation (Static Agent, Default Agent, Custom Transport Handler/Builders etc) is called, depending on the configuration.

Lets construct a multiple service implementation via an Example.

Scenario:

Consider an online course website. A lecturer can publish his content by submitting a course bundle comprising of html pages, videos and assessment questions.

While the bundle is processed, each content type is to be dealt with specific import-service implementation:

  1. Page import
  2. Video import
  3. Assessment import

Lets get started with the implementation !!!

Create multiple service implementation

The implementation can be divided into 3 simple steps:

  1. Create an interface which each of Service implementation would implement.
  2. Implement concrete service implementations
  3. A handler which would hold list of service implementation & execute relevent implementation.

Step 1: Interface which each service implementation would implement

The interface would comprise of abstract methods which:

  • would execute business logic for each service.
  • allow identification of best service to process current use-case.

Sample interface for the course import scenario:

package blog.techrevel.service;
public interface CourseImport {
    //Import pages, videos and assessment
    public abstract void importContent();

    //Identify the service which would be able to process current file
    public abstract boolean canProcess(String fileName);
}

Step 2: Implement concrete service implementations

Here we would be create multiple content import implementation to deal specifically with each content type. Example:

package blog.techrevel.service.impl;

import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import blog.techrevel.service.CourseImport;

@Component(name="Import HTML Pages")
@Service
public class PageImport implements CourseImport{

    private static final Logger LOGGER = LoggerFactory.getLogger(PageImport.class);

    @Override
    public void importContent() {
        LOGGER.info("Business Logic to import HTML Pages");
    }

    @Override
    public boolean canProcess(String fileName) {
        return StringUtils.endsWith(fileName, ".html");
    }
}

Step 3: Handler to reference service and trigger execution of appropriate implementation.

Step 3.1: To aggregate references for all implementations in a collection, use @Reference annotation with following attributes:

  • referenceInterface=”name of the service interface/class”
  • cardinality=”The cardinality of the service reference. This must be one of value from the enumeration ReferenceCardinality“. For Multiple service implementation, use one of the following enum values:
    • OPTIONAL_MULTIPLE: (“0..n”) Optional, multiple reference: No service required to be available for the reference to be satisfied. All matching services are available through this reference.
    • MANDATORY_MULTIPLE: (“1..n”) Mandatory, multiple reference: At least one service must be available for the reference to be satisfied. All matching services are available through this reference.
  • policy=”ReferencePolicy.DYNAMIC Or ReferencePolicy.STATIC”
    • If dynamic the service will be made available to the component as it comes and goes. If static the component will be deactivated and re-activated if the service comes and/or goes away
  • bind=”name of the method to be called when the service is to be bound to the component”
    • The default value is the name created by appending the reference name to the string bind.
    • bind method will be called for each service implementation that would be registered
  • unbind=”name of the method to be called when the service is to be unbound from the component”
    • The default value is the name created by appending the reference name to the string unbind.
    • unbind method will be called for each service implementation that would be unregistered
@Reference(cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, referenceInterface = CourseImport.class, policy = ReferencePolicy.DYNAMIC)
private List courseImportImplList;

protected void bind(CourseImport courseImportImpl) {
    if (courseImportImplList == null) {
        courseImportImplList = new ArrayList();
    }
    courseImportImplList.add(courseImportImpl);
}

protected void unbind(CourseImport courseImportImpl) {
    courseImportImplList.remove(courseImportImpl);
}

Step 3.2 Trigger execution of appropriate implementation

Use the ‘collection created in Step-3.1’ to shortlist the best implementation for current use-case. The shortlisted implementation reference can then be used to trigger corresponding business logic.

In the below sample code, canProcess() identifies the right implementation to deal with current content type. Corresponding importContent() is then executed to import html as per business requirement.

package blog.techrevel.service.impl;

import java.util.ArrayList;
import java.util.List;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import blog.techrevel.service.CourseImport;
import blog.techrevel.service.CourseImportHandler;

@Component(name = "Course Import Handler", immediate = true)
@Service
public class CourseImportHandlerImpl implements CourseImportHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(CourseImportHandler.class);

    @Reference(cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, referenceInterface = CourseImport.class, policy = ReferencePolicy.DYNAMIC)
    private List courseImportImplList;

    protected void bind(CourseImport courseImportImpl) {
        if (courseImportImplList == null) {
            courseImportImplList = new ArrayList();
        }
        courseImportImplList.add(courseImportImpl);
    }

    protected void unbind(CourseImport courseImportImpl) {
        courseImportImplList.remove(courseImportImpl);
    }

    @Override
    public void importContent() {
        for (CourseImport courseImportImpl : courseImportImplList) {
            if (courseImportImpl.canProcess("introduction.html")) {
                courseImportImpl.importContent();
                break;
            }
        }
    }
}

 

If you always need reference to a specific implementation of Service, then the same can be achieved as per the details on link

Apache Felix – Configuration factory

0

OSGi provides many modular features which enhances a developer’s implementation experience. Current blog focuses on one such feature: Configuration Factories via Apache Felix annotations.

If you looking for Configuration factories via new OSGi Annotations, then please visit link

The concept is similar to logging configuration in AEM, where we define multiple configurations for different loggers, but the service implementation stays the same. Thus, multiple configuration, single implementation.

Create and access a configuration factory

It takes just two steps to create and use a Configuration factory.

Step 1: Create Configuration factory

This is achieved by adding ‘configurationFactory=true’ attribute to the Component annotation.

import java.util.Dictionary;

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;

import org.apache.sling.commons.osgi.PropertiesUtil;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(label = "Techrevel configuration factory", metatype = true, configurationFactory = true)
@Service(ConfigurationFactory.class)
public class ConfigurationFactory {
    private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationFactory.class);

    @Property(label = "Content Consumer URL", value = "http://localhost:8081")
    private static final String CONTENT_CONSUMER_URL = "content.consumer.url";
    private String contentConsumerUrl;

    @Activate
    public void activate(ComponentContext componentContext) {
        Dictionary properties = componentContext.getProperties();
        contentConsumerUrl = PropertiesUtil.toString(properties.get(CONTENT_CONSUMER_URL), "");
        LOGGER.info("Read the content Consumer Url : " + contentConsumerUrl);
    }

    public String getContentConsumerUrl() {
        return contentConsumerUrl;
    }
}

Step 2: Consume factory’s configurations

This is the service which would act based on values configured via factory.

Step 2.1: To aggregate configurations values in a collection use @Reference annotation with following attributes:

  • referenceInterface=”name of the service interface/class”
  • cardinality=”The cardinality of the service reference. This must be one of value from the enumeration ReferenceCardinality
    • OPTIONAL_UNARY: (“0..1”) Optional, unary reference: No service required to be available for the reference to be satisfied. Only a single service is available through this reference.
    • MANDATORY_UNARY: (“1..1”) Mandatory, unary reference: At least one service must be available for the reference to be satisfied. Only a single service is available through this reference.
    • OPTIONAL_MULTIPLE: (“0..n”) Optional, multiple reference: No service required to be available for the reference to be satisfied. All matching services are available through this reference.
    • MANDATORY_MULTIPLE: (“1..n”) Mandatory, multiple reference: At least one service must be available for the reference to be satisfied. All matching services are available through this reference.
  • policy=”ReferencePolicy.DYNAMIC Or ReferencePolicy.STATIC”
    • If dynamic the service will be made available to the component as it comes and goes. If static the component will be deactivated and re-activated if the service comes and/or goes away
  • bind=”name of the method to be called when the service is to be bound to the component”
    • The default value is the name created by appending the reference name to the string bind.
  • unbind=”name of the method to be called when the service is to be unbound from the component”
    • The default value is the name created by appending the reference name to the string unbind.
import java.util.ArrayList;
import java.util.List;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(immediate = true)
@Service(ConfigurationFactoryConsumer.class)
public class ConfigurationFactoryConsumer {
    private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationFactoryConsumer.class);

    @Reference(referenceInterface = ConfigurationFactory.class, cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC)
    private List configurationList;

    //Aggregating new configutaion values in the collection
    protected synchronized void bindConfigurationFactory(final ConfigurationFactory config) {
        if (configurationList == null) {
            configurationList = new ArrayList();
        }
        configurationList.add(config);
    }

    //Removing deleted configuration values from the collection
    protected synchronized void unbindConfigurationFactory(final ConfigurationFactory config) {
        configurationList.remove(config);
    }
}

Step 2.2: Consume the configuration collection

The configurationList collection created in Step 2.1 can be accessed through various functions of ConfigurationFactoryConsumer class.

Access the collection and execute the business logic as needed. Happy Coding !

To access a specific configuration only, please refer to link.

Custom Responsive grid/Layout container

0

A custom responsive layout container can be created by extending the OOTB Responsive Grid (Layout Container).

Please follow the following steps:

Step 1: Copy OOTB Responsive Grid component

OOTB responsive grid’s path: /libs/wcm/foundation/components/responsivegrid

  • Copy the component to the target project
  • Update name for the component.
    • Since, component name would be added by default in the decoration tags, it might help add specific CSS for the container.
  • Update jcr:title & componentGroup to uniquely identify the component.

 

Step 2: Extend OOTB responsive grid

Update sling:resourceSuperType of the custom container as:

sling:resourceSuperType=”wcm/foundation/components/responsivegrid”

Custom Container.PNG

Step 3: Make it responsive 

At this point, the component can added to the pages, but will not be responsive in the Layouting Mode (i.e. resizing the container / containing elements would have no visual effect).

Responsiveness can be achieved by overriding ns.responsive.isResponsiveGrid() of  /libs/cq/gui/components/authoring/clientlibs/editor/js/responsive/responsive.js. Please find the steps below:

  • Create clientlibs (cq:ClientLibraryFolder) below your component.
  • Add a javascript file. It can be added to source sub-folder (type=nt:folder)  below the clientlibs. Add following snippet to the file:
 //Fix to declare custom container as responsive
(function ($, ns, channel, window, undefined) {
    ns.responsive.isResponsiveGrid = function (editable) { 
        return editable.type === 'wcm/foundation/components/responsivegrid' || '/apps/<app_name>/<path_to_custom_container>'; 
    } 
}
(jQuery, Granite.author, jQuery(document), this)); 
  • Add js.txt below clientlibs. Update js.txt with the path to the javascript file.
  • Add ‘cq.authoring.editor’ category to component’s clientlibs

 

Step 4: Update the component with the desired behavior

The component is both available for use & responsive. You can update the component script as per the business requirement. Also, delete the scripts that are not being overwritten. The deleted scripts would be inherited via Sling resource merger

 

Adding responsive CSS for the Custom Responsive Grid

A custom CSS file for various devices can be created similar to OOTB responsive Grid.

 

Configuring breakpoints

To configure breakpoints for the site, follow the steps detailed at Adobe Site