Expand my Community achievements bar.

Guidelines for the Responsible Use of Generative AI in the Experience Cloud Community.
SOLVED

How to create a custom mail servlet for AEM Core Form Container Mail

Avatar

Level 2

How do you create a custom mail servlet to be called when using AEM Core Form Container's "Mail" action type?

I need to alter the Out-of-the-Box functionality of the mail action type so that the 'From' field contains placeholder text in the authoring dialog.

I tried copying the mail folder from libs/foundation/components/form/actions/mail and putting it into my app, and added the placeholder property,

but it is not behaving properly since my copied action type folder uses the same jcr property names as the ones in the out-of-the-box mail action type (ex: ./from, ./mailto, etc.)

 

I've tried changing these property names to custom ones so they differ from the OOTB ones, but the day cq mail servlet that handles the mail action type does not recognize my custom attribute names.

 

dms879090_0-1680709126686.png

 

1 Accepted Solution

Avatar

Correct answer by
Community Advisor

Hi @dms879090,

If you want to use OOTB mechanism for sending email via Core Forms in your custom action, you do not need a custom servlet. You can utilize OOTB one. The main case here, will be to pass values from your custom properties once OOTB mechanism is trying to retrieve original, e.g. if OOTB mechanism is getting value of from prop, it should be mapped to our custom property. This can be easily achieved wrapping original valueMap and control what is returned.

Here is crx structure created using OOTB Mail action:

original-action.png

Here is crx structure created using Custom Mail action:

custom-action.png

Here is a sample code that is doing proper mapping:

 

package com.mysite.core.filters;

import com.day.cq.commons.ValueMapWrapper;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceWrapper;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
import org.apache.sling.servlets.annotations.SlingServletFilter;
import org.apache.sling.servlets.annotations.SlingServletFilterScope;
import org.osgi.service.component.annotations.Component;

import javax.servlet.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Component
@SlingServletFilter(
        scope = { SlingServletFilterScope.COMPONENT},
        methods = {"POST"},
        selectors = {"form"},
        extensions = {"html"},
        pattern = "/content/.*"
)
public class MailFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        SlingHttpServletRequest slingHttpServletRequest = (SlingHttpServletRequest) servletRequest;
        // wrapping should be done only for custom mail action
        if (isMailAction(slingHttpServletRequest.getResource())) {
            slingHttpServletRequest = new MailRequestWrapper(slingHttpServletRequest);
        }
        filterChain.doFilter(slingHttpServletRequest, servletResponse);
    }

    @Override
    public void destroy() {
    }

    private boolean isMailAction(Resource resource) {
        if (resource != null) {
            ValueMap valueMap = resource.getValueMap();
            String actionType = valueMap.get("actionType", String.class);
            if (actionType != null) {
                // set value of your custom actionType
                return actionType.equals("weretail/components/form/actions/mail");
            }
        }
        return false;
    }

    private class MailRequestWrapper extends SlingHttpServletRequestWrapper {

        public MailRequestWrapper(SlingHttpServletRequest wrappedRequest) {
            super(wrappedRequest);
        }

        @Override
        public Resource getResource() {
            return new MailResourceWrapper(super.getResource());
        }

        private class MailResourceWrapper extends ResourceWrapper {

            public MailResourceWrapper(Resource resource) {
                super(resource);
            }

            @Override
            public ValueMap getValueMap() {
                return new MailValueMapWrapper(super.getValueMap());
            }

            private class MailValueMapWrapper extends ValueMapWrapper {

                // mapping of OOTB field names with custom ones
                private Map<String, String> propertiesMapping = new HashMap<String, String>() {{
                    put("cc", "custom-cc");
                    put("from", "custom-from");
                    put("mailto", "custom-mailto");
                    put("subject", "custom-subject");
                }};

                public MailValueMapWrapper(ValueMap map) {
                    super(map);
                }

                @Override
                public <T> T get(String name, Class<T> type) {
                    return super.get(getNameFromMapping(name), type);
                }

                @Override
                public <T> T get(String name, T defaultValue) {
                    return super.get(getNameFromMapping(name), defaultValue);
                }
                
                private String getNameFromMapping(String name) {
                    return propertiesMapping.containsKey(name) ? propertiesMapping.get(name) : name;
                }
            }
        }
    }
}

 

Of course you will need to set actionType that is correlated with your custom mail action implementation, as well as put name of custom fields that corresponds to original ones.

View solution in original post

1 Reply

Avatar

Correct answer by
Community Advisor

Hi @dms879090,

If you want to use OOTB mechanism for sending email via Core Forms in your custom action, you do not need a custom servlet. You can utilize OOTB one. The main case here, will be to pass values from your custom properties once OOTB mechanism is trying to retrieve original, e.g. if OOTB mechanism is getting value of from prop, it should be mapped to our custom property. This can be easily achieved wrapping original valueMap and control what is returned.

Here is crx structure created using OOTB Mail action:

original-action.png

Here is crx structure created using Custom Mail action:

custom-action.png

Here is a sample code that is doing proper mapping:

 

package com.mysite.core.filters;

import com.day.cq.commons.ValueMapWrapper;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceWrapper;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
import org.apache.sling.servlets.annotations.SlingServletFilter;
import org.apache.sling.servlets.annotations.SlingServletFilterScope;
import org.osgi.service.component.annotations.Component;

import javax.servlet.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Component
@SlingServletFilter(
        scope = { SlingServletFilterScope.COMPONENT},
        methods = {"POST"},
        selectors = {"form"},
        extensions = {"html"},
        pattern = "/content/.*"
)
public class MailFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        SlingHttpServletRequest slingHttpServletRequest = (SlingHttpServletRequest) servletRequest;
        // wrapping should be done only for custom mail action
        if (isMailAction(slingHttpServletRequest.getResource())) {
            slingHttpServletRequest = new MailRequestWrapper(slingHttpServletRequest);
        }
        filterChain.doFilter(slingHttpServletRequest, servletResponse);
    }

    @Override
    public void destroy() {
    }

    private boolean isMailAction(Resource resource) {
        if (resource != null) {
            ValueMap valueMap = resource.getValueMap();
            String actionType = valueMap.get("actionType", String.class);
            if (actionType != null) {
                // set value of your custom actionType
                return actionType.equals("weretail/components/form/actions/mail");
            }
        }
        return false;
    }

    private class MailRequestWrapper extends SlingHttpServletRequestWrapper {

        public MailRequestWrapper(SlingHttpServletRequest wrappedRequest) {
            super(wrappedRequest);
        }

        @Override
        public Resource getResource() {
            return new MailResourceWrapper(super.getResource());
        }

        private class MailResourceWrapper extends ResourceWrapper {

            public MailResourceWrapper(Resource resource) {
                super(resource);
            }

            @Override
            public ValueMap getValueMap() {
                return new MailValueMapWrapper(super.getValueMap());
            }

            private class MailValueMapWrapper extends ValueMapWrapper {

                // mapping of OOTB field names with custom ones
                private Map<String, String> propertiesMapping = new HashMap<String, String>() {{
                    put("cc", "custom-cc");
                    put("from", "custom-from");
                    put("mailto", "custom-mailto");
                    put("subject", "custom-subject");
                }};

                public MailValueMapWrapper(ValueMap map) {
                    super(map);
                }

                @Override
                public <T> T get(String name, Class<T> type) {
                    return super.get(getNameFromMapping(name), type);
                }

                @Override
                public <T> T get(String name, T defaultValue) {
                    return super.get(getNameFromMapping(name), defaultValue);
                }
                
                private String getNameFromMapping(String name) {
                    return propertiesMapping.containsKey(name) ? propertiesMapping.get(name) : name;
                }
            }
        }
    }
}

 

Of course you will need to set actionType that is correlated with your custom mail action implementation, as well as put name of custom fields that corresponds to original ones.