April 25, 2020
Estimated Post Reading Time ~

Custom Annotations in Sling Model

On Adobe Experience Manager, the developer can use the Java API or the Javascript API to integrate the component with the JCR tree.

In this article, let's create an annotation to get the Multifield authored information and put the data into a List variable.

As can be seen on the Multifield documentation, when the component is using it into the Authoring Dialog, the values are stored into child nodes. The annotation will get it and iterate over the children. In this example, we will provide into the Annotation argument, the properties from the multifield node we would like to get.

Below you can see the structure of the component multifield content on the JCR tree:
my-component-node
    multifield-property
        item-0
        item-1
        item-2
        item-3

The "my-component-node" node has a sling:resourceType property as /apps/rheck/components/my-component, with the following HTML (HTL):

<sly data-sly-use.component="com.rheck.aem.models.MyComponent">
    <ul>
        <sly data-sly-list="${component.listItems}">
            <li data-sly-test="${item.link}">
                <a href="${item.link @ extension='html'}">
                    <span>${item.label}</span>
                </a>
            </li>
        </sly>
    </ul>
</sly>

The component is calling a Sling Model, as can be seen above. The model MyComponent will be responsible to get the multifield data. Below the code is using an annotation called @Multifield which we will be creating afterward.

package com.rheck.aem.models;
import ...;

@Model(
    adaptables = SlingHttpServletRequest.class,
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class MyComponent {

    @Multifield(name = "multifield-property", values = {"label", "link"})
    private List<HashMap> listItems;

    public List<HashMap> getListItems() {
        return listItems;
    }

}

There're two variables to the Multifield annotation. The first is the name, which corresponds to the name of the fieldset, as can be checked into the tree structure. The second one is an array of the properties we would like to get from the content node.

With all set, let's create the annotation and the injector, to make the annotation work.

package com.rheck.aem.provider;
import ...;

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@InjectAnnotation
@Source("page-multifield-provider-injector")
public @interface Multifield {

    String[] values() default {};

    String name() default "";

}

Now, create a file — can be into the same package — which will be the Injector for the annotation.

package com.rheck.aem.provider;
import ...;

@Component(
    immediate = true, service = {Injector.class, StaticInjectAnnotationProcessorFactory.class},
    property = {Constants.SERVICE_RANKING + ":Integer=7000"})
public class MultifieldInjector implements Injector, StaticInjectAnnotationProcessorFactory {

    @Override
    public Object getValue(Object adaptable, String fieldName, Type type, AnnotatedElement annotatedElement, DisposalCallbackRegistry disposalCallbackRegistry) {
        if (adaptable instanceof SlingHttpServletRequest && annotatedElement.isAnnotationPresent(Multifield.class)) {

            Multifield annotation = annotatedElement.getAnnotation(Multifield.class);

            try {
                Resource resource = ((SlingHttpServletRequest) adaptable).getResource();

                Node multifieldNode = resource.adaptTo(Node.class);

                if (multifieldNode == null) {
                    return null;
                }

                if (!multifieldNode.hasNode(annotation.name())) {
                    return new ArrayList<HashMap>();
                }

                return getMultifield(resource.getChild(annotation.name()), annotation.values());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return null;
    }
    
    public void getMultifield(Resource resource, String[] fields) {
        Node resourceNode = resource.adaptTo(Node.class);
        NodeIterator ni = resourceNode.getNodes();

        List<HashMap> multifield = new ArrayList<>();

        while (ni.hasNext()) {
            HashMap<String, String> values = new HashMap<>();

            Node currentNode = (Node) ni.nextNode();

            for (String field : fields) {
                if (currentNode.hasProperty(field)) {
                    values.put(field, currentNode.getProperty(field).getString());
                }
            }

            multifield.add(values);
        }

        return multifield;
    }
    
    @Override
    public String getName() {
        return "page-multifield-provider-injector";
    }

    @Override
    public InjectAnnotationProcessor2 createAnnotationProcessor(AnnotatedElement element) {
        MultiFieldProvider annotation = element.getAnnotation(Multifield.class);
        if (annotation != null) {
            return new RequestedPageMetadataProviderAnnotationProcessor();
        }
        return null;
    }

    private static class RequestedPageMetadataProviderAnnotationProcessor extends AbstractInjectAnnotationProcessor2 {
        @Override
        public InjectionStrategy getInjectionStrategy() {
            return InjectionStrategy.DEFAULT;
        }

        @Override
        public Boolean isOptional() {
            return Boolean.FALSE;
        }
    }
}

Note: the method getName should return a string with the same value of the Source annotation from the annotation interface.

All done. The annotation has been created and the injector will work executing the needed job.


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.