Servlets
POST and Authentication
When writing a Sling Servlet that handles POST requests, you might want to test it, by firing requests from a tool such as curl or Postman. This requires you to authenticate yourself via a cookie. The following example will demonstrate how a simple POST Servlet can be written and tested.
This Servlet listens on requests to the defined ResourceType + .action url selector.
If you place a component of the referenced ResourceType on a page, an example request url could look like this: http://localhost:4502/content/myproject/de/testpage/jcr:content/root/responsivegrid/contactform.action.json.
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_EXTENSIONS;
import static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_METHODS;
import static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES;
import static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_SELECTORS;
@Component(
service = {Servlet.class},
property = {
SLING_SERVLET_METHODS + "=POST",
SLING_SERVLET_EXTENSIONS + "=json",
SLING_SERVLET_RESOURCE_TYPES + "=myproject/components/contactform",
SLING_SERVLET_SELECTORS + "=action"
}
)
public class ContactFormServlet extends SlingAllMethodsServlet {
@Override
protected void doPost(SlingHttpServletRequest request, final SlingHttpServletResponse response) throws ServletException, IOException {
// handle POST
}
}
See also
To test this via Postman, you will need to add AEMs Login Cookie. Open your Browsers Dev Tools and find the Cookies for your AEM Instance, most likely running at localhost:4502.

You now need to add this cookie "by hand" to your Postman configuration and remember to update its value, on instance restart / new login / value change.

The added cookie with its Key Value Pair (name=value) looks like this:

I'd suggest to increase / adapt the Expires Timestamp to at least a couple of hours in the future.
When firing a POST Request to http://localhost:4502/content/myproject/de/testpage/jcr:content/root/responsivegrid/contactform.action.json your servlet will correctly handle that request and return a non 401 HTTP statuscode.

Returning a DataSource
If you need to dynamically populate an AEM Dialog Dropdown or Autocomplete, you need to generate and request a datasource. Either via JSP or Java Servlet. The following example demonstrates how this can easily be implemented. We add an autocomplete field to a dialog and reference our custom servlet.
Autocomplete Field
Not much description needed for the dialog. The only important part is the sling:resourceType="/apps/myproject/datasource/appids".
This is the resourceType that our servlet will listen to.
<customValue
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/autocomplete"
fieldLabel="Some value from the Backend"
name="./customValue"
emptyText="Set the custom value"
margin="{Boolean}true"
multiple="{Boolean}false"
required="{Boolean}true">
<datasource
jcr:primaryType="nt:unstructured"
sling:resourceType="/apps/myproject/datasource/customstrings"/>
<options
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/autocomplete/list"/>
</customValue>
Java Servlet
Our custom servlet references the resourceType from our <customValue/> autocomplete field in its @SlingServletResourceTypes annotation.
Whenever that field is loaded, it will hit the servlet and display its response.
The servlet itself can do whatever is necessary to request and generate a list of values to display. This dummy case just statically adds to strings, however, in a production app, one could for example read values from the JCR or even request another third party api. Finally, the generated list of results need to be wrapped in a DataSource object and added to the request, not the response.
import com.adobe.granite.ui.components.ds.DataSource;
import com.adobe.granite.ui.components.ds.SimpleDataSource;
import com.adobe.granite.ui.components.ds.ValueMapResource;
import com.day.cq.commons.jcr.JcrConstants;
import org.apache.commons.collections4.iterators.TransformIterator;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceMetadata;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.propertytypes.ServiceDescription;
import javax.servlet.Servlet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@Component(service = {Servlet.class})
@SlingServletResourceTypes(methods = HttpConstants.METHOD_GET, resourceTypes = "/apps/myproject/datasource/customstrings")
@ServiceDescription("Servlet that returns Strings as a SimpleDataSource.")
public class CustomDataSourceServlet extends SlingSafeMethodsServlet {
@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) {
final ResourceResolver resourceResolver = request.getResourceResolver();
final List<String> customAutocompleteValues = new ArrayList<>();
// generate / request your actual values here - e.g. look up values from the jcr.
customAutocompleteValues.add("some-value");
customAutocompleteValues.add("some-other-value");
final DataSource simpleDataSource = getDataSource(resourceResolver, customAutocompleteValues);
request.setAttribute(DataSource.class.getName(), simpleDataSource);
}
private DataSource getDataSource(ResourceResolver resourceResolver, List<String> customAutocompleteValues) {
return new SimpleDataSource(new TransformIterator<>(customAutocompleteValues.iterator(), input -> {
ValueMap vm = new ValueMapDecorator(new HashMap<>());
vm.put("value", input);
vm.put("text", input);
return new ValueMapResource(resourceResolver, new ResourceMetadata(), JcrConstants.NT_UNSTRUCTURED, vm);
}));
}
}
Quick Infos
- aemcq5tutorials.com/tutorials/sling-servlet-in-aem
- redquark.org/2018/10/day-05-working-with-sling-servlets-in_10.html
DO
@SlingServlet(
resourceTypes = "sling/servlet/default",
selectors = "selector",
extensions = "tab",
methods = HttpConstants.METHOD_GET
)
DON'T
@Component
@Service(value = javax.servlet.Servlet.class)
@Properties({ @Property(name = "sling.servlet.resourceTypes", value = { "sling/servlet/default" }),
@Property(name = "sling.servlet.selectors", value = { "selector" }),
@Property(name = "sling.servlet.extensions", value = { "tab" }),
@Property(name = "sling.servlet.methods", value = { HttpConstants.METHOD_GET }) })