Skip to main content

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.

TestFormServlet.java
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
}
}

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.

CustomDataSourceServlet.java
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

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 }) })