July 22, 2020
Howdy fellow developers π! After learning OSGi services and components, we will now dive into Servlets.
A Servlet is a class used to extend the capabilities of servers that host applications accessed by means of a request-response programming model. For such applications, Servlet technology defines HTTP-specific servlet classes.
You must be thinking whatβs all this jibber-jabber, right? But fret not, in this post, we will try to understand many aspects of Servlets in detail π.
A servlet in its very core is a Java class, which can handle network requests (for e.g. HTTP requests). Servlets are usually used to implement web applications. This java class does not have any main() method, only some callback methods.
Servlets run on the java enabled web-server or the application server. They handle requests from the web server, process it, produce the response, then send the response back to the server. This means servlet lives and dies within a web container. A web container is responsible for invoking methods in a servlet. It knows what callback methods a servlet has.
Servlets run in a servlet container which handles the networking side (e.g. parsing an HTTP request, connection handling etc.). One of the best known open source servlet container is Tomcat.
All servlets must implement the Servlet interface, which defines life-cycle methods. When implementing a generic service, we can use or extend the GenericServlet class provided with the Java Servlet API. The HttpServlet class provides methods, such as doGet() and doPost(), for handling HTTP-specific services.
It involves eight basic steps -
Sling servlets are a special type of servlets which are registered as OSGi service of type javax.servlet.Servlet
.
A SlingServletResolver listens for Servlet services and - given the correct service registration properties - provides the servlets as resources in the (virtual) resource tree. Such servlets are provided as ServletResource instances which adapt to the javax.servlet.Servlet
class.
For a Servlet registered as an OSGi service to be used by the Sling Servlet Resolver, either one or both of the sling.servlet.paths
or the sling.servlet.resourceTypes
service reference properties must be set. If neither is set, the Servlet service is ignored.
A Sling servlet can be registered in two ways -
sling:resourceType
property of the node. For this, we need to hit the path in the browser for which the sling:resourceType is the given one.Registering a servlet via a path is relatively easier than registering it via a resource type but it is still recommended to use resource types instead of paths. Below are the reasons why -
There are two types of servlets in Sling which are nothing but the classes we need to extend while creating our servlet.
HttpServlet
class which accounts for extensibility. So extensions of this class have great control over what methods to overwrite. It supports GET, HEAD, OPTIONS etc methods.SlingSafeMethodsServlet
by support for the POST, PUT and DELETE methods.Phew π too much theory! But it was important to understand the concepts of Servlets. Now we have gotten to know about the nitty gritty details of servlets, itβs time for us to do the fun part - CODING!!! π
Letβs say we want to get nodes and properties of certain node type and under a specified path. We can achieve this by writing a simple servlet which can be called as an API with nodeType
and path
as the two parameters.
org.redquark.aem.tutorials.core.servlets.ListNodesServlet
and paste the following code in it - package org.redquark.aem.tutorials.core.servlets;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.json.JSONObject;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.Session;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
import javax.jcr.query.Row;
import javax.jcr.query.RowIterator;
import javax.servlet.Servlet;
import java.util.Objects;
@Component(
service = Servlet.class,
property = {
"sling.servlet.methods=" + HttpConstants.METHOD_GET,
"sling.servlet.paths=" + "/bin/aemtutorials/listassets"
}
)
public class ListNodesServlet extends SlingSafeMethodsServlet {
private static final long serialVersionUID = 7762806638577908286L;
private static final String TAG = ListNodesServlet.class.getSimpleName();
private static final Logger LOGGER = LoggerFactory.getLogger(ListNodesServlet.class);
@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) {
// JCR Session
Session session = null;
try {
// Get path from the request object
String path = request.getParameter("path");
// Get node type
String nodeType = request.getParameter("nodeType");
// Getting the ResourceResolver from the current request
ResourceResolver resourceResolver = request.getResourceResolver();
// Getting the session instance by adapting ResourceResolver
session = resourceResolver.adaptTo(Session.class);
// Get the instance of QueryManager from the JCR workspace
QueryManager queryManager = Objects.requireNonNull(session).getWorkspace().getQueryManager();
// This query will look for all the assets under the given path
String queryString = "SELECT * FROM [" + nodeType + "] WHERE ISDESCENDANTNODE('" + path + "')";
// Converting the String query into an executable query object
Query query = queryManager.createQuery(queryString, "JCR-SQL2");
// Executing the query
QueryResult queryResult = query.execute();
// This will behave as a cursor pointing to the current row of results
RowIterator rowIterator = queryResult.getRows();
JSONObject jsonObject = new JSONObject();
int count = 0;
// Loop for all the rows in the result and return them as json
while (rowIterator.hasNext()) {
Row row = rowIterator.nextRow();
PropertyIterator propertyIterator = row.getNode().getProperties();
JSONObject properties = new JSONObject();
while (propertyIterator.hasNext()) {
Property property = propertyIterator.nextProperty();
properties.put(property.getName(), property.getValue());
}
jsonObject.put("todo-" + (++count), properties);
}
// Printing the response to the browser window
response.getWriter().println(jsonObject.toString());
} catch (Exception e) {
LOGGER.error("{}: Exception occurred: {}", TAG, e.getMessage());
} finally {
if (session != null) {
session.logout();
}
}
}
}
This code is pretty simple. Since we are making only GET
request, we are extending from SlingSafeMethodsServlet
class for the reasons discussed above. If you notice, the @Component
annotation has the service
property (which is similar as the normal OSGi service). This means a Servlet is also a service of type javax.servlet.Servlet
. We are registering this servlet via path hence, we are using sling.servlet.paths
property.
In the doGet()
method, we are getting nodeType
and path
parameters from the request object which is later being passed in query string. The QueryManager
API will query the JCR based on the query string.
The results we are obtaining from executing the query are being put in JSON object which is then being printed on the browser window.
nodeType
and path
) - {
"todo-40": {
"jcr:primaryType": "nt:unstructured",
"completed": "false",
"id": "66",
"title": "rerum eum molestias autem voluptatum sit optio",
"userId": "4"
},
"todo-42": {
"jcr:primaryType": "nt:unstructured",
"completed": "true",
"id": "76",
"title": "sequi dolorem sed",
"userId": "4"
},
.
.
.
}
Congratulations!! π we have created our first servlet. So, this is Part 1. We will continue our discussion in our next post and will see other details specifically how can we create a servlet using resourceType
.
As always, you can find the complete code of this project on my GitHub. Feel free to fork or open issues, if any.
I would love to hear your thoughts on this and would like to have suggestions from you to make it better.
Happy coding and Namaste π.