Handle form submission using AdapterFactory in Adobe CQ (AEM) OSGi Component

Handle form submission using AdapterFactory in Adobe CQ (AEM) OSGi Component

In this article, I will be addressing 2 issues: how to efficiently handle form submission map data in Java and how the AdapterFactory get implemented in Adobe CQ (AEM).

How to efficiently handle form submission map data in Java

  1. You need 1 servlet, 1 service, and 2 classes: FormSubmissionHandlerServlet, FormSubmissionHandlerService, MyCustomClass, and MyCustomClassAdapterFactory
  2. FormSubmissionHandlerServlet is the main service that handles the HTTP request (AJAX request) and dispatches the work for the right candidate class that designed to handle individual form.

    Sameple codes of FormSubmissionHandlerServlet
    package ...
    
    // developer comments...
    // all the necessary imports
    
    import ...
    
    @SlingServlet(paths = "/apps/mycompany/forms", metatype = false)
    @Properties({
        @Property(name = "service.pid", value = "mycompany.forms", propertyPrivate = false),
        @Property(name = "service.description", value = "Handles Form Submission", propertyPrivate = false),
        @Property(name = "service.vendor", value = "My Company", propertyPrivate = false)
    })
    
    public class FormSubmissionHandlerServlet extends SlingAllMethodsServlet {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(FormProcessorServlet.class);
        private static final FormSubmissionHandlerService formSubmissionHandler = new FormSubmissionHandler();
        
        // some more local variables here...
        
        @Override
        protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
            handleRequestData(request, response);
        }
    
        @Override
        protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
            handleRequestData(request, response);
        }
        
        private void handleRequestData(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
            //grab form type so we can call correct form process
            String formType = request.getParameter(params.FORM_FORMTYPE_FIELDNAME);
    MyCustomClass myCustomObj = request.adaptTo(MyCustomClass.class);
    formSubmissionHandler.processFormSubmission(myCustomObj, response); } }

    For those who are interested in building servlet that is defined by resource type, please check out this article on 6D labs: Servlets in Sling - the case of the disappearing Servlet path.

  3. FormSubmissionHandlerService is invoked by the servlet (FormSubmissionHandlerServlet) and is responsible for sending the response back to user.

    Sample codes for FormSubmissionHandlerService
    package ...
    
    // developer comments
    // all necessary imports
    
    import ...
    
    @Component(label = "Form Submission Service", immediate = true, metatype = false)
    @Properties({
        @Property(name = Constants.SERVICE_DESCRIPTION, value = "Form Submission Service"),
        @Property(name = Constants.SERVICE_VENDOR, value = "My Companyh")
    })
    @Service(value = {FormSubmissionService.class })
    
    public class FormSubmissionHandlerService {
        private static final String RESPONSE_CTYPE = "application/json";
        // some local variables and methods here
        public void processFormSubmission(MyCustomClass myCustomObj, SlingHttpServletResponse response) throws IOException
        {
            response.setContentType(RESPONSE_CTYPE);
            response.getWriter().write(some_output_string_goes_here);
        }
    }
  4. MyCustomClass is one of the classes that is responsible for processing form submission. It is invoked by FormSubmissionHandlerService and FormSubmissionHandlerServlet.

    Sample codes of MyCustomClass
    package ...
    
    // developer comments...
    // all necessary imports
    
    imports ...
    
    public class MyCustomClass
    {
        private HashMap<String, String> mainDataProperties = new HashMap();
        // some more local variables and methods here...
        
        public void setMainDataProperties(String key, String val)
        {
            testKitProperties.put(key, val);
        }
        
        public String getMainDataProperties(String key)
        {
            return !mainDataProperties.isEmpty() && mainDataProperties.containsKey(key) ? mainDataProperties.get(key) : "";
        }
        
        public void setSubDataProperties(String key, String val)
        {
            subDataProperties.put(key, val);
        }
    }
  5. MyCustomClassAdapterFactory is the AdapterFactory for MyCustomClass object. Its main task here is to adapt SlingHttpServletRequest to MyCustomClass. It gets invoked by Sling Adapter.

    Sample codes of MyCustomClassAdapterFactory
    package ...
    
    // developer comments...
    // all necessary imports
    
    import ...
    
    @Component(metatype=false)
    @Properties({
        @Property(name = Constants.SERVICE_DESCRIPTION, value = "My Custom Class Adapter Factory"),
        @Property(name = Constants.SERVICE_VENDOR, value = "My Company"),
        @Property(name = "adapters", classValue = {MyCustomClass.class}),
        @Property(name = "adaptables", classValue = {SlingHttpServletRequest.class, Resource.class})
    })
    @Service(value = AdapterFactory.class)
    
    public class MyCustomClassAdapterFactory implements AdapterFactory {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(TestKitsFormSubmissionAdapterFactory.class);
        // FORM FIELDS
        public static final String[] FIELDS = {
            "name", "email", "address", "tel", "shortbio", "skills"
        };
        
        @Override
        public <AdapterType> AdapterType getAdapter(Object o, Class<AdapterType> adapterTypeClass) {
            
            if (o instanceof SlingHttpServletRequest)
            {
                return (AdapterType) buildMyCustomObj((SlingHttpServletRequest) o);
            }
            
            return null;
        }
        
        private MyCustomClass buildMyCustomObject(SlingHttpServletRequest request) {
            
            MyCustomClass myCustomObj = new MyCustomClass();
            
            /**
             * FORM FIELD STRUCTURE
             * data[name]
             * data[email]
             * data[address]
             * data[tel]
             * data[shortbio]
             * data[skills]
             */
            
            Map dataMap = request.getParameterMap();
            
            for (String key : FIELDS) {
                if (dataMap.containsKey("data[" + key + "]")) {
                    String[] arrayStr = (String[]) dataMap.get("data[" + key + "]");
                    testKitsFormSubmission.setTestKitProperties(key, arrayStr[0]);
                }
            }
            
            return myCustomObj;
        }
    }
  6. Sample HTML/JS Form Markups

    HTML
    <form id="myformid" method="post" action="/apps/mycompany/forms" onsubmit="return myCustomAjaxSubmit(this);">
    <p><label>Name: <input type="text" name="data[name]" id="data-name" /></lable></p>
    <p><label>Email: <input type="text" name="data[email]" id="data-email" /></lable></p>
    <p><label>Address: <input type="text" name="data[address]" id="data-address" /></lable></p>
    <p><label>Tel: <input type="text" name="data[tel]" id="data-tel" /></lable></p>
    <p><label>Sort bio: <textarea name="data[shortbio]" id="data-shortbio"></textarea></lable></p>
    <p><label>skills: <textarea name="data[skills]" id="data-skills"></textarea></lable></p>
    </form>


    JS
    function myCustomAjaxSubmit(e)
    {
    // do something here...
    }

How the AdapterFactory get implemented in Adobe CQ (AEM)

  • Examine sample codes for MyCustomClassAdapterFactory above (#5):

    @Property(name = "adapters", classValue = {MyCustomClass.class})

    and

    @Property(name = "adaptables", classValue = {SlingHttpServletRequest.class, Resource.class}) are required.

    Property adapters. The service registration property listing the fully qualified names of classes to which this factory can adapt adaptables.
    Property adaptables. The service name to use when registering implementations of this interface as services.

    More reference on AdapterFactory, go to: Interface AdapterFactory.
  • You can verify if the AdapterFactory exists by going to: http://{host}:{port}/system/console/adapters. For more information on this, go to: New in CQ 5.5: Sling Adapters Console.

Share this post

0 Comments

comments powered by Disqus