1. Introduction
GServlet is an open source project inspired from the Groovlets, which aims to use the Groovy language and its provided modules to simplify Servlet API web development. Groovlets are Groovy scripts executed by a servlet. They are run on request, having the whole web context (request, response, etc.) bound to the evaluation context. They are much more suitable for smaller web applications. Compared to Java Servlets, coding in Groovy can be much simpler. It has a couple of implicit variables we can use, for example, request, response to access the HttpServletRequest, and HttpServletResponse objects. We have access to the HttpSession with the session variable. If we want to output data, we can use out, sout, and html.
if (!session) {
session = request.getSession(true)
}
if (!session.counter) {
session.counter = 1
}
html.html {
head {
title('Groovy Servlet')
}
body {
p("Hello, ${request.remoteHost}: ${session.counter}! ${new Date()}")
}
}
session.counter = session.counter + 1
The same philosophy has been followed in the design of this API while maintaining a class-based programming approach.
import org.gservlet.annotation.Servlet
@Servlet("/counter")
class SessionCounterServlet {
void get() {
if (!session.counter) {
session.counter = 1
}
html.html {
head {
title('Groovy Servlet')
}
body {
p("Hello, ${request.remoteHost}: ${session.counter}! ${new Date()}")
}
}
session.counter = session.counter + 1
}
}
If you have no prior experience with the Groovy language, there are several curated lists of online resources for learning it.
2. Main Features
-
Servlet 3.1+ Support
-
Groovy Scripting and Hot Reloading
-
JSON, XML, HTML and JDBC Support
3. Requirements
-
Java 8+
-
Java IDE (Eclipse, IntelliJ IDEA, NetBeans..)
-
Java EE 7+ compliant WebServer (Tomcat, Wildfly, Glassfish, Payara..)
4. Installation
To get started with GServlet, you may want to begin by creating your first project. This section shows you how to get up and running quickly. It is highly recommended to consume the API through a dependency management tool and the artifact can be found in Maven’s central repository. It is named gservlet-api and you just need to name a dependency on it in your project.
4.1. Maven
<dependency>
<groupId>org.gservlet</groupId>
<artifactId>gservlet-api</artifactId>
<version>1.0.0</version>
</dependency>
4.2. Gradle
repositories {
mavenCentral()
}
dependencies {
compile("org.gservlet:gservlet-api:1.0.0")
}
5. Getting Started
Once your Java web server is installed and configured, you can put it to work. Five steps take you from writing your first Groovy servlet to running it. Using Maven, these steps are as follows:
-
Create a Maven webapp project
-
Create the groovy folder inside your webapp directory
-
Write the servlet source code
-
Run your Java web server
-
Call your servlet from a web browser
Below are some examples that you can try out.
import org.gservlet.annotation.Servlet
@Servlet("/projects")
class ProjectServlet {
List projects = []
void init() {
projects << [id : 1, name : "Groovy", url : "https://groovy-lang.org"]
projects << [id : 2, name : "Spring", url : "https://spring.io"]
projects << [id : 3, name : "Maven", url : "https://maven.apache.org"]
}
void get() {
json(projects)
}
void post() {
def project = request.body
projects << project
json(project)
}
void put() {
def project = request.body
int index = projects.findIndexOf { it.id == project.id }
projects[index] = project
json(project)
}
void delete() {
def project = request.body
int index = projects.findIndexOf { it.id == project.id }
json(projects.remove(index))
}
}
import org.gservlet.annotation.Filter
@Filter("/*")
class CorsFilter {
void filter() {
response.addHeader("Access-Control-Allow-Origin", "*")
response.addHeader("Access-Control-Allow-Methods","GET, OPTIONS, HEAD, PUT, POST, DELETE")
if (request.method == "OPTIONS") {
response.status = response.SC_ACCEPTED
return
}
next()
}
}
import org.gservlet.annotation.RequestListener
@RequestListener
class ServletRequestListener {
void init() {
println "request initialized"
}
void destroy() {
println "request destroyed"
}
}
Keep a note that the use of the default package is discouraged and for a hot reloading of your source code, set the GSERVLET_RELOAD environment variable to true in your IDE. |
6. Creating Servlets
A servlet is a small Java program that runs within a Web server. The Servlet interface defines methods that all servlets must implement. To implement this interface, you can write a generic servlet that extends the GenericServlet class or an HTTP servlet which extends the HttpServlet class and overrides at least one method, usually one of these:
-
doGet, for HTTP GET requests
-
doPost, for HTTP POST requests
-
doPut, for HTTP PUT requests
-
doDelete, for HTTP DELETE requests
Below is a Java class that extends the HttpServlet class:
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/index.html")
public class HelloWordServlet extends HttpServlet {
public void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<body>");
out.println("<p>Hello World!</p>");
out.println("</body>");
out.println("</html>");
}
}
We are going to write its Groovy counterpart with the GServlet API so you can perceive the difference in terms of simplicity and clarity. The name of the HTTP request method handlers are shortened to get, post and so on. They take no arguments since the request and the response are now implicit variables.
import org.gservlet.annotation.Servlet
@Servlet("/index.html")
class HelloWordServlet {
void get() {
out.println("<html>")
out.println("<body>")
out.println("<p>Hello World!</p>")
out.println("</body>")
out.println("</html>")
}
}
By default the content type of the HttpServletResponse is set to text/html and the implicit out variable used to generate the HMTL content is nothing less than a reference to its PrintWriter object. We could use as well the implicit html variable which is an instance of a Groovy MarkupBuilder, to write a better version of this servlet.
import org.gservlet.annotation.Servlet
@Servlet(value="/index.html", loadOnStartup = 1)
class HelloWordServlet {
void get() {
html.html {
body {
p("Hello World!")
}
}
}
}
The generated HTML content looks like this:
<!DOCTYPE html>
<html>
<body>
<p>Hello World!</p>
</body>
</html>
6.1. Servlet annotation attributes
There are the same as those of the @WebServlet annotation.
Name | Type | Description |
---|---|---|
name |
String |
name of the servlet |
value |
String[] |
URL patterns of the servlet |
urlPatterns |
String[] |
URL patterns of the servlet |
loadOnStartup |
Integer |
load-on-startup order of the servlet |
initParams |
InitParam[] |
init parameters of the servlet |
asyncSupported |
boolean |
Declares whether the servlet supports asynchronous operation mode |
smallIcon |
String |
small icon of the servlet |
largeIcon |
String |
large icon of the servlet |
description |
String |
description of the servlet |
displayName |
String |
display name of the servlet |
6.2. Servlet implicit variables
The implicit variables made available to a Servlet are as follows:
Variable | Description |
---|---|
logger |
Logger object |
config |
ServletConfig object |
request |
HttpServletRequest object |
response |
HttpServletResponse object |
session |
HttpSession object |
context |
ServletContext object |
sql |
Sql object |
out |
PrintWriter object |
html |
MarkupBuilder object |
xml |
MarkupBuilder object |
6.3. Servlet methods
For an exhaustive list of the supported methods, please read the Javadocs.
Method | Description |
---|---|
void init() |
handles the initialization process |
void get() |
handles the GET request |
void post() |
handles the POST request |
void put() |
handles the PUT request |
void delete() |
handles the DELETE request |
void head() |
handles the HEAD request |
void options() |
handles the OPTIONS request |
void trace() |
handles the TRACE request. |
void forward(location) |
Forwards the request to the provided location |
void redirect(location) |
Redirects the request to the provided location |
void json(object) |
Sends the response as JSON |
void destroy() |
invoked when taken out of the service |
6.4. Servlet initialization parameters
Since Servlet 3, the @WebInitParam annotation is used to specify initialization parameters for a servlet programmatically, and it takes a required name and value. You can add a description but this is rather informative. In the initialization method init(), we can get our parameters using the getInitParameter() method of the ServletConfig object. In the GServlet API, the annotation has been shorten to @InitParam and you can get an initialization parameter through the implicit config variable using just its name or as described above.
import org.gservlet.annotation.InitParam
import org.gservlet.annotation.Servlet
@Servlet( urlPatterns = "/upload",
initParams = [
@InitParam(name = "uploadDirectory", value = "/images")
] )
class UploadServlet {
void init() {
println config.uploadDirectory
println config.getInitParameter("uploadDirectory")
}
}
The attributes of the @InitParam annotation are the same as those of the @WebInitParam annotation.
Name | Type | Description |
---|---|---|
name |
String |
name of the initialization parameter |
value |
String |
value of the initialization parameter |
6.5. Servlet Multipart configuration
Supporting file uploads is a very basic and common requirement for many web applications. Prior to Servlet 3.0, implementing file upload required the use of external libraries or complex input processing. Version 3.0 of the Java Servlet specification helps to provide a viable solution to the problem in a generic and portable way. The Servlet 3.0 specification supports file upload out of the box, so any web container that implements the specification can parse multipart requests and make mime attachments available through the HttpServletRequest object. A new annotation, @MultipartConfig, is used to indicate that the servlet on which it is declared expects requests to made using the multipart/form-data MIME type. Therefore, it can retrieve the Part components of a given multipart/form-data request by calling the getPart(String name) or getParts() method of the HttpServletRequest object.
import org.gservlet.annotation.InitParam
import org.gservlet.annotation.Servlet
import javax.servlet.annotation.MultipartConfig
@Servlet( urlPatterns = "/upload",
initParams = [
@InitParam(name = "uploadDirectory", value = "/images")
] )
@MultipartConfig( fileSizeThreshold = 1048576, maxFileSize = 5242880L, maxRequestSize = 26214400L )
class UploadServlet {
String uploadPath
void init() {
uploadPath = context.getRealPath(config.uploadDirectory)
File uploadDir = new File(uploadPath)
if (!uploadDir.exists()) {
uploadDir.mkdir()
}
}
void get() {
File uploadDir = new File(uploadPath)
def files = []
uploadDir.listFiles()?.each { file ->
files << [name : file.name, length : file.length(), lastModified : file.lastModified()]
}
json(files);
}
void post() {
request.getParts().each { part ->
String file = uploadPath + File.separator + request.getFileName(part)
part.write(file)
}
redirect(context.contextPath + "/upload");
}
}
6.6. Servlet Security
The @ServletSecurity annotation is used to specify security constraints on a Java servlet. The annotations @HttpMethodConstraint and @HttpConstraint are used within that annotation to define the security constraints.
@ServletSecurity(
httpMethodConstraints = <HttpMethodConstraint[]>,
value = <HttpConstraint>
)
The httpMethodConstraints attribute specifies one or more constraints for some specific HTTP methods, whereas the value attribute specifies a constraint that applies for all other HTTP methods.
import org.gservlet.annotation.Servlet
import javax.servlet.annotation.ServletSecurity
import javax.servlet.annotation.ServletSecurity.TransportGuarantee
import javax.servlet.annotation.HttpConstraint
@Servlet(value="/projects")
@ServletSecurity(@HttpConstraint(transportGuarantee = TransportGuarantee.CONFIDENTIAL))
class ProjectServlet {
}
import org.gservlet.annotation.Servlet
import javax.servlet.annotation.ServletSecurity
import javax.servlet.annotation.HttpMethodConstraint
import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic
@Servlet(value="/projects")
@ServletSecurity(httpMethodConstraints = @HttpMethodConstraint(value = "POST",
emptyRoleSemantic = EmptyRoleSemantic.DENY))
class ProjectServlet {
}
import org.gservlet.annotation.Servlet
import javax.servlet.annotation.ServletSecurity
import javax.servlet.annotation.HttpMethodConstraint
@Servlet(value="/projects")
@ServletSecurity(
httpMethodConstraints = [
@HttpMethodConstraint(value = "GET", rolesAllowed = "admin"),
@HttpMethodConstraint(value = "POST", rolesAllowed = "admin"),
]
)
class ProjectServlet {
}
You can find more examples about how to use the @ServletSecurity annotation on the web.
7. Creating Filters
A filter is an object that performs filtering tasks on either the request to a resource (a servlet or static content), or on the response from a resource, or both. The Filter interface defines methods that all filters must implement. Filters perform filtering in the doFilter() method.
Below is a Java class that implements the Filter interface:
import javax.servlet.annotation.WebFilter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;
@WebFilter("/*")
public class MyFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
}
public void destroy() {
}
}
Its Groovy version with the GServlet API looks like this:
import org.gservlet.annotation.Filter;
@Filter("/*")
class MyFilter {
void init() {
}
void filter() {
next()
}
void destroy() {
}
}
7.1. Filter annotation attributes
There are the same as those of the @WebFilter annotation.
Name | Type | Description |
---|---|---|
filterName |
String |
name of the filter |
value |
String[] |
URL patterns of the filter |
urlPatterns |
String[] |
URL patterns of the filter |
dispatcherTypes |
DispatcherType[] |
dispatcher types to which the filter applies |
initParams |
InitParam[] |
init parameters of the filter |
servletNames |
String[] |
names of the servlets to which the filter applies |
asyncSupported |
boolean |
Declares whether the filter supports asynchronous operation mode |
smallIcon |
String |
small icon of the filter |
largeIcon |
String |
large icon of the filter |
description |
String |
description of the filter |
displayName |
String |
display name of the filter |
7.2. Filter implicit variables
The implicit variables made available to a Filter are as follows:
Variable | Description |
---|---|
logger |
Logger object |
config |
FilterConfig object |
request |
HttpServletRequest object |
response |
HttpServletResponse object |
chain |
FilterChain object |
session |
HttpSession object |
context |
ServletContext object |
sql |
Sql object |
out |
PrintWriter object |
html |
MarkupBuilder object |
xml |
MarkupBuilder object |
7.3. Filter methods
For an exhaustive list of the supported methods, please read the Javadocs.
Method | Description |
---|---|
void init() |
handles the initialization process |
void filter() |
handles the filtering tasks |
void next() |
Calls the next filter in the chain |
void json(object) |
Sends the response as JSON |
void destroy() |
invoked when taken out of the service |
7.4. Filter initialization parameters
The @WebInitParam annotation is used to specify initialization parameters for a filter programmatically. In its initialization method init(), we can get our parameters using the getInitParameter() method of the FilterConfig object. Like for a servlet, the annotation has been shorten to @InitParam and you can get an initialization parameter through the implicit config variable using just its name or as described above.
import org.gservlet.annotation.InitParam
import org.gservlet.annotation.Filter
@Filter( value = "/*",
initParams = [
@InitParam(name = "loggingDirectory", value = "/logs")
])
class LoggingFilter {
void init() {
println config.loggingDirectory
println config.getInitParameter("loggingDirectory")
}
}
8. Creating Listeners
During the lifetime of a typical Java EE web application, a number of events take place. The Servlet API provides a number of listener interfaces that we can implement to react to these events.
Interface for receiving notification events about ServletContext lifecycle changes. |
|
Interface for receiving notification events about ServletContext attribute changes. |
|
Interface for receiving notification events about a ServletRequest coming into and going out of scope of a web application. |
|
Interface for receiving notification events about ServletRequest attribute changes. |
|
Interface for receiving notification events about HttpSession lifecycle changes. |
|
Interface for receiving notification events about HttpSession attribute changes. |
|
Interface for receiving notification events when an object is bound to or unbound from a HttpSession. |
|
Interface for receiving notification events when an HttpSession is being passivated and and activated. |
|
Interface for receiving notification events about HttpSession id changes. |
8.1. ServletContextListener
This interface is for receiving notification events about ServletContext lifecycle changes. Implementations of this interface are invoked at their contextInitialized method in the order in which they have been declared, and at their contextDestroyed method in reverse order.
Below is a Java class that implements the ServletContextListener interface:
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class MyServletContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
System.out.println("context started");
}
public void contextDestroyed(ServletContextEvent event) {
System.out.println("context destroyed");
}
}
Its Groovy version with the GServlet API looks like this:
import org.gservlet.annotation.ContextListener
@ContextListener
public class MyServletContextListener {
void contextInitialized() {
println "context started"
}
void contextDestroyed() {
println "context destroyed"
}
}
The implicit variables made available to a ServletContextListener are as follows:
Variable | Description |
---|---|
logger |
Logger object |
context |
ServletContext object |
event |
ServletContextEvent object |
8.2. ServletContextAttributeListener
This interface is for receiving notification events about ServletContext attribute changes. The order in which implementations of this interface are invoked is unspecified.
Below is a Java class that implements the ServletContextAttributeListener interface:
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class MyServletContextAttributeListener implements ServletContextAttributeListener {
public void attributeAdded(ServletContextAttributeEvent event) {
System.out.println("attr " + event.getName() + " added with value " + event.getValue());
}
public void attributeRemoved(ServletContextAttributeEvent event) {
System.out.println("attr " + event.getName() + " removed with value " + event.getValue());
}
public void attributeReplaced(ServletContextAttributeEvent event) {
System.out.println("attr " + event.getName() + " replaced with value " + event.getValue());
}
}
Its Groovy version with the GServlet API looks like this:
import org.gservlet.annotation.ContextAttributeListener
@ContextAttributeListener
public class MyServletContextAttributeListener {
void attributeAdded() {
println "attr $name added with value $value"
}
void attributeRemoved() {
println "attr $name removed with value $value"
}
void attributeReplaced() {
println "attr $name replaced with value $value"
}
}
The implicit variables made available to a ServletContextAttributeListener are as follows:
Variable | Description |
---|---|
logger |
Logger object |
context |
ServletContext object |
event |
ServletContextAttributeEvent object |
name |
attribute name |
value |
attribute value |
8.3. ServletRequestListener
This interface is for receiving notification events about requests coming into and going out of scope of a web application. A request is defined as coming into scope of a web application when it is about to enter the first servlet or filter of the web application, and as going out of scope as it exits the last servlet or the first filter in the chain. Implementations of this interface are invoked at their requestInitialized method in the order in which they have been declared, and at their requestDestroyed method in reverse order.
Below is a Java class that implements the ServletRequestListener interface:
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class MyServletRequestListener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent event) {
System.out.println("request initialized");
}
public void requestDestroyed(ServletRequestEvent event) {
System.out.println("request destroyed");
}
}
Its Groovy version with the GServlet API looks like this:
import org.gservlet.annotation.RequestListener
@RequestListener
public class MyServletRequestListener {
void requestInitialized() {
println "request initialized"
}
void requestDestroyed() {
println "request destroyed"
}
}
The implicit variables made available to a ServletRequestListener are as follows:
Variable | Description |
---|---|
logger |
Logger object |
request |
HttpServletRequest object |
session |
HttpSession object |
context |
ServletContext object |
event |
ServletRequestEvent object |
8.4. ServletRequestAttributeListener
This interface is for receiving notification events about ServletRequest attribute changes. Notifications will be generated while the request is within the scope of the web application. A ServletRequest is defined as coming into scope of a web application when it is about to enter the first servlet or filter of the web application, and as going out of scope when it exits the last servlet or the first filter in the chain. The order in which implementations of this interface are invoked is unspecified.
Below is a Java class that implements the ServletRequestAttributeListener interface:
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class MyServletRequestAttributeListener implements ServletRequestAttributeListener {
public void attributeAdded(ServletRequestAttributeEvent event) {
System.out.println("attr " + event.getName() + " added with value " + event.getValue());
}
public void attributeRemoved(ServletRequestAttributeEvent event) {
System.out.println("attr " + event.getName() + " removed with value " + event.getValue());
}
public void attributeReplaced(ServletRequestAttributeEvent event) {
System.out.println("attr " + event.getName() + " replaced with value " + event.getValue());
}
}
Its Groovy version with the GServlet API looks like this:
import org.gservlet.annotation.RequestAttributeListener
@RequestAttributeListener
public class MyServletRequestAttributeListener {
void attributeAdded() {
println "attr $name added with value $value"
}
void attributeRemoved() {
println "attr $name removed with value $value"
}
void attributeReplaced() {
println "attr $name replaced with value $value"
}
}
The implicit variables made available to a ServletRequestAttributeListener are as follows:
Variable | Description |
---|---|
logger |
Logger object |
request |
HttpServletRequest object |
session |
HttpSession object |
context |
ServletContext object |
event |
ServletRequestAttributeEvent object |
name |
attribute name |
value |
attribute value |
8.5. HttpSessionListener
This interface is for receiving notification events about HttpSession lifecycle changes. Implementations of this interface are invoked at their sessionCreated method in the order in which they have been declared, and at their sessionDestroyed method in reverse order.
Below is a Java class that implements the HttpSessionListener interface:
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class MyHttpSessionListener implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent event) {
System.out.println("session created");
}
public void sessionDestroyed(HttpSessionEvent event) {
System.out.println("session destroyed");
}
}
Its Groovy version with the GServlet API looks like this:
import org.gservlet.annotation.SessionListener
@SessionListener
public class MyHttpSessionListener {
void sessionCreated() {
println "session created"
}
void sessionDestroyed() {
println "session destroyed"
}
}
The implicit variables made available to a HttpSessionListener are as follows:
Variable | Description |
---|---|
logger |
Logger object |
session |
HttpSession object |
event |
HttpSessionEvent object |
8.6. HttpSessionAttributeListener
This interface is for receiving notification events about HttpSession attribute changes. The order in which implementations of this interface are invoked is unspecified.
Below is a Java class that implements the HttpSessionAttributeListener interface:
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener {
public void attributeAdded(HttpSessionBindingEvent event) {
System.out.println("attr " + event.getName() + " added with value " + event.getValue());
}
public void attributeRemoved(HttpSessionBindingEvent event) {
System.out.println("attr " + event.getName() + " removed with value " + event.getValue());
}
public void attributeReplaced(HttpSessionBindingEvent event) {
System.out.println("attr " + event.getName() + " replaced with value " + event.getValue());
}
}
Its Groovy version with the GServlet API looks like this:
import org.gservlet.annotation.SessionAttributeListener
@SessionAttributeListener
public class MyHttpSessionAttributeListener {
void attributeAdded() {
println "attr $name added with value $value"
}
void attributeRemoved() {
println "attr $name removed with value $value"
}
void attributeReplaced() {
println "attr $name replaced with value $value"
}
}
The implicit variables made available to a HttpSessionAttributeListener are as follows:
Variable | Description |
---|---|
logger |
Logger object |
session |
HttpSession object |
event |
HttpSessionBindingEvent object |
name |
attribute name |
value |
attribute value |
8.7. HttpSessionBindingListener
This interface is for receiving notification events about when an object is bound to or unbound from a session. This may be as a result of a servlet programmer explicitly unbinding an attribute from a session, due to a session being invalidated, or due to a session timing out.
Below is a Java class that implements the HttpSessionBindingListener interface:
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
public class MyHttpSessionBindingListener implements HttpSessionBindingListener {
public void valueBound(HttpSessionBindingEvent event) {
System.out.println("attr " + event.getName() + " bounded with value " + event.getValue());
}
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println("attr " + event.getName() + " unbounded with value " + event.getValue());
}
}
Its Groovy version with the GServlet API looks like this:
import org.gservlet.annotation.SessionBindingListener
@SessionBindingListener
public class MyHttpSessionBindingListener {
void valueBound() {
println "attr $name bounded with value $value"
}
void valueUnbound() {
println "attr $name unbounded with value $value"
}
}
The implicit variables made available to a HttpSessionBindingListener are as follows:
Variable | Description |
---|---|
logger |
Logger object |
session |
HttpSession object |
event |
HttpSessionBindingEvent object |
name |
attribute name |
value |
attribute value |
8.8. HttpSessionActivationListener
Objects that are bound to a session may listen to container events notifying them that sessions will be passivated and activated. A container that migrates session between VMs or persists sessions is required to notify all attributes bound to sessions implementing this interface.
Below is a Java class that implements the HttpSessionActivationListener interface:
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionActivationListener;
public class MyHttpSessionActivationListener implements HttpSessionActivationListener {
public void sessionDidActivate(HttpSessionEvent event) {
System.out.println("session activated");
}
public void sessionWillPassivate(HttpSessionEvent event) {
System.out.println("session passivated");
}
}
Its Groovy version with the GServlet API looks like this:
import org.gservlet.annotation.SessionActivationListener
@SessionActivationListener
public class MyHttpSessionActivationListener {
void sessionDidActivate() {
println "session activated"
}
void sessionWillPassivate() {
println "session passivated"
}
}
The implicit variables made available to a HttpSessionActivationListener are as follows:
Variable | Description |
---|---|
logger |
Logger object |
session |
HttpSession object |
event |
HttpSessionEvent object |
8.9. HttpSessionIdListener
This interface is for receiving notification events about HttpSession id changes. The order in which implementations of this interface are invoked is unspecified.
Below is a Java class that implements the HttpSessionIdListener interface:
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionIdListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class MyHttpSessionIdListener implements HttpSessionIdListener {
public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) {
System.out.println("the session id was "+oldSessionId);
}
}
Its Groovy version with the GServlet API looks like this:
import org.servlet.annotation.SessionIdListener
@SessionIdListener
public class MyHttpSessionIdListener {
void sessionIdChanged() {
println "the session id was $oldSessionId"
}
}
The implicit variables made available to a HttpSessionIdListener are as follows:
Variable | Description |
---|---|
logger |
Logger object |
session |
HttpSession object |
event |
HttpSessionEvent object |
oldSessionId |
old HttpSession Id |
9. Handling Request Parameters
The request parameters are sent along with the request. You can send them as part of the URL or as part of the body of an HTTP request. The request.getParameter() method is used to get the value of a request parameter from the request and returns a string. We can also get an array of parameter values with the request.getParameterValues() method which returns an array of strings.
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;
@WebServlet("/parameters")
public class ServletParameters extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String param1 = request.getParameter("param1");
String param2 = request.getParameter("param2");
String[] paramArray = request.getParameterValues("paramArray");
}
}
You can still use the approach above but handling request parameters with the GServlet API is a simple as this:
import org.gservlet.annotation.Servlet
@Servlet("/parameters")
class ServletParameters {
void get() {
String param1 = request.param1
String param2 = request.param2
String[] paramArray = request.paramArray
}
}
10. Managing Attributes
An attribute is an object that is used to share information in a web application. Attributes can be SET and GET from one of the following scopes : request, session, application.
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.io.IOException;
@WebServlet("/attributes")
public class ServletAttributes extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setAttribute("attribute1","Attribute 1");
System.out.println(request.getAttribute("attribute1"));
HttpSession session = request.getSession()
session.setAttribute("attribute2","Attribute 2");
System.out.println(session.getAttribute("attribute2"));
ServletContext context = request.getServletContext();
context.setAttribute("attribute3","Attribute 3");
System.out.println(context.getAttribute("attribute3"));
}
}
You can still use the approach above but managing attributes with the GServlet API is a simple as this:
import org.gservlet.annotation.Servlet
@Servlet("/attributes")
class ServletAttributes {
void get() {
request.attribute1 = "Attribute 1"
println request.attribute1
session.attribute2 = "Attribute 2"
println session.attribute2
context.attribute3 = "Attribute 3"
context.attribute3
}
}
11. Working With JSON
Groovy comes with integrated support for converting between Groovy objects and JSON. The classes dedicated to JSON serialisation and parsing are found in the groovy.json package. You can get an insight of how to use them in the Groovy documentation. In the GServlet API, we have simplified the process of parsing and producing JSON in your servlets and filters as below:
import org.gservlet.annotation.Servlet
@Servlet("/projects")
class ProjectServlet {
List projects = []
void init() {
projects << [id : 1, name : "Groovy", url : "https://groovy-lang.org"]
projects << [id : 2, name : "Spring", url : "https://spring.io"]
projects << [id : 3, name : "Maven", url : "https://maven.apache.org"]
}
void get() {
json(projects)
}
void post() {
def project = request.body
projects << project
json(project)
}
}
Whenever, the content type of the request is set to application/json, you can use its body property to get the payload as Groovy object. Your servlet or filter can use as well the built-in json() method to send JSON data as a response.
12. Working With HTML and XML
The most commonly used approach for creating HTML and XML content with Groovy is to use a builder. Two implicit variables, html and xml, which are bound to an instance of a MarkupBuilder have been made available to your servlets and filters for that purpose.
import org.gservlet.annotation.Servlet
@Servlet("/index.html")
class HtmlServlet {
void get() {
html.html {
body {
p("Hello World!")
}
}
}
}
import org.gservlet.annotation.Servlet
@Servlet("/books")
class XmlServlet {
void get() {
xml.books {
book(id: "1", name : "Groovy in Action")
book(id: "2", name : "Spring in Action")
book(id: "3", name : "Maven in Action")
}
}
}
13. Working With RDBMS
The groovy-sql module provides a higher-level abstraction over the current Java’s JDBC technology and it supports a wide variety of databases. To set up a database with the GServlet API, you have to create in the webapp directory, a file named gservlet.properties.
db.driver : com.mysql.cj.jdbc.Driver
db.url : jdbc:mysql://localhost:3306/gservlet_examples
db.user : root
db.password : changeit
db.minPoolSize : 5
db.maxPoolSize : 10
For each request, an Sql connection is automatically created from a data source and made available to your servlets and filters through the implicit sql variable as below:
import org.gservlet.annotation.Servlet
@Servlet("/projects")
class ProjectServlet {
void post() {
def params = [1, 'Groovy', 'https://groovy-lang.org']
sql.execute 'insert into projects (id, name, url) values (?, ?, ?)', params
}
}
import org.gservlet.annotation.Filter;
@Filter("/*")
class ProjectFilter {
void filter() {
sql.eachRow('select * from projects') { project ->
println "${project.name.padRight(10)} ($project.url)"
}
next()
}
}
After each request, the close() method of the Sql object is automatically invoked to bring it back to the connection pool. Below is a summary of the database configuration properties:
Name | Required | Description |
---|---|---|
db.driver |
true |
database driver class name |
db.url |
true |
database url |
db.user |
true |
database user |
db.password |
true |
database password |
db.minPoolSize |
false |
database minimum pool size. The default value is 1 |
db.maxPoolSize |
false |
database maximum pool size. The default value is 3 |
14. Unit Testing
Unit testing servlets is difficult, therefore it is recommended to move the main business logic in the servlet into a separate class which has no dependencies on the Servlet API. The Groovy programming language comes with great support for writing tests. In addition to the language features and test integration with state-of-the-art testing libraries and frameworks, the Groovy ecosystem has born a rich set of testing libraries and frameworks. In its testing guide, you can take at a closer look at JUnit integration, Spock for specifications, and Geb for functional tests.
15. Groovydocs Generation
Groovydoc was introduced in 2007 to provide for Groovy what Javadoc provides for Java. It is used to generate the API documentation for the Groovy and Java classes that compose your project.
import org.gservlet.annotation.Servlet
/**
* This servlet handles the management of projects
*
* @author John Doe
*/
@Servlet(value="/projects")
class ProjectServlet {
/**
* the projects list
*/
List projects = []
/**
* the init method
*/
void init() {
projects << [id : 1, name : "Groovy", url : "https://groovy-lang.org"]
projects << [id : 2, name : "Spring", url : "https://spring.io"]
projects << [id : 3, name : "Maven", url : "https://maven.apache.org"]
projects << [id : 4, name : "JMeter", url : "https://jmeter.apache.org"]
}
/**
* handles the GET method
*/
void get() {
json(projects)
}
/**
* handles the POST method
*/
void post() {
def project = request.body
projects << project
json(project)
}
}
15.1. Maven
GMavenPlus is a rewrite of GMaven, a plugin that allows you to integrate Groovy into your Maven projects. This is the basic configuration to generate Groovydocs with this plugin.
<project>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>3.0.6</version>
<type>pom</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.11.0</version>
<executions>
<execution>
<goals>
<goal>groovydoc</goal>
</goals>
</execution>
</executions>
<configuration>
<sources>
<source>
<directory>${project.basedir}/src/main/webapp/groovy</directory>
<includes>
<include>**/*.groovy</include>
</includes>
</source>
</sources>
<links>
<link>
<href>https://docs.oracle.com/javase/7/docs/api/</href>
<packages>java.,javax.,org.xml.</packages>
</link>
<link>
<href>http://docs.groovy-lang.org/latest/html/gapi/</href>
<packages>groovy.,org.codehaus.groovy.</packages>
</link>
</links>
</configuration>
</plugin>
</plugins>
</build>
</project>
Run mvn gplus:groovydoc and your documentation will be generated in the target/gapidocs directory.
15.2. Gradle
With Gradle, You can use the Groovydoc tool to generate your project documentation, and the version that is used, is the one from the Groovy dependency defined in the build script.
repositories {
mavenCentral()
}
dependencies {
compile "org.codehaus.groovy:groovy-all:3.0.6"
}
apply plugin: 'groovy'
sourceSets {
main {
groovy {
srcDirs = ['src/main/webapp/groovy']
}
}
}
groovydoc {
use = true
groovyClasspath = configurations.compile
source = sourceSets.main.groovy
link 'https://docs.oracle.com/javase/7/docs/api/', 'java.'
link 'http://docs.groovy-lang.org/latest/html/gapi/', 'groovy.', 'org.codehaus.groovy.'
}
Run gradle groovydoc and your documentation will be generated in the build/docs/groovydoc directory.
16. IDE Support
16.1. Eclipse
To augment the editor features such as content assist, the GServlet API ships with a DSL Descriptor (DSLD) to describe the editing semantics in a way that can be interpreted by the Groovy Development Tools (GDT) which provides Eclipse support for the Groovy programming language.
contribute(currentType(annos: annotatedBy(Servlet))) {
property name : 'logger', type : java.util.Logger, provider : 'org.gservlet.AbstractServlet'
property name : 'config', type : javax.servlet.ServletConfig, provider : 'org.gservlet.AbstractServlet'
property name : 'request', type : javax.servlet.http.HttpServletRequest, provider : 'org.gservlet.AbstractServlet'
property name : 'response', type : javax.servlet.http.HttpServletResponse, provider : 'org.gservlet.AbstractServlet'
property name : 'session', type : javax.servlet.http.HttpSession, provider : 'org.gservlet.AbstractServlet'
property name : 'context', type : javax.servlet.ServletContext, provider : 'org.gservlet.AbstractServlet'
property name : 'sql', type : groovy.sql.Sql, provider : 'org.gservlet.AbstractServlet'
property name : 'out', type : java.io.PrintWriter, provider : 'org.gservlet.AbstractServlet'
property name : 'html', type : groovy.xml.MarkupBuilder, provider : 'org.gservlet.AbstractServlet'
}
contribute(currentType(annos: annotatedBy(Servlet))) {
delegatesTo type : org.gservlet.AbstractServlet, except : [
'service',
'doGet',
'doPost',
'doHead',
'doPut',
'doTrace',
'doOptions',
'doDelete'
]
}
contribute(currentType(annos: annotatedBy(Filter))) {
property name : 'logger', type : java.util.Logger, provider : 'org.gservlet.AbstractFilter'
property name : 'config', type : javax.servlet.FilterConfig, provider : 'org.gservlet.AbstractFilter'
property name : 'request', type : javax.servlet.http.HttpServletRequest, provider : 'org.gservlet.AbstractFilter'
property name : 'response', type : javax.servlet.http.HttpServletResponse, provider : 'org.gservlet.AbstractFilter'
property name : 'chain', type : javax.servlet.FilterChain, provider : 'org.gservlet.AbstractFilter'
property name : 'session', type : javax.servlet.http.HttpSession, provider : 'org.gservlet.AbstractFilter'
property name : 'context', type : javax.servlet.ServletContext, provider : 'org.gservlet.AbstractFilter'
property name : 'sql', type : groovy.sql.Sql, provider : 'org.gservlet.AbstractFilter'
property name : 'out', type : java.io.PrintWriter, provider : 'org.gservlet.AbstractFilter'
property name : 'html', type : groovy.xml.MarkupBuilder, provider : 'org.gservlet.AbstractFilter'
}
contribute(currentType(annos: annotatedBy(Filter))) {
delegatesTo type : org.gservlet.AbstractFilter, except : ['init', 'doFilter']
}
Make sure your Eclipse project has the Groovy nature and that the Groovy DSL support is activated. To leverage the full power of the Groovy Development Tools (GDT), it is highly recommended to add the groovy folder as source folder, so you can use its wizards to create your artifacts.
The use of the default package is discouraged and the example below illustrates a simple packaging practice to follow throughout your development for a cohesive structure of your project. |
package dao
class ProjectDao {
List projects = []
ProjectDao() {
projects << [id : 1, name : "Groovy", url : "https://groovy-lang.org"]
projects << [id : 2, name : "Spring", url : "https://spring.io"]
projects << [id : 3, name : "Maven", url : "https://maven.apache.org"]
}
}
package servlet
import org.gservlet.annotation.Servlet
import dao.ProjectDao
@Servlet("/projects")
class ProjectServlet {
ProjectDao projectDao
void init() {
projectDao = new ProjectDao()
}
void get() {
json(projectDao.projects)
}
}
16.2. IntelliJ IDEA
In the other hand, in IntelliJ IDEA, we can write GDSL files to have code completion on the injected methods/properties and closures. GroovyDSL is a scripting framework with a domain-specific language designed to define the behavior of end-user DSLs as script files which are executed by the IDE on the fly, bringing new reference resolution and code completion logic into the scope of a project.
def classContext = context(scope: classScope())
contributor(classContext) {
if (hasAnnotation('org.gservlet.annotation.Servlet')) {
property name: 'logger', type: 'java.util.logging.Logger'
property name: 'config', type: 'javax.servlet.ServletConfig'
property name: 'request', type: 'javax.servlet.http.HttpServletRequest'
property name: 'response',type: 'javax.servlet.http.HttpServletResponse'
property name: 'session', type: 'javax.servlet.http.HttpSession'
property name: 'context', type: 'javax.servlet.ServletContext'
property name: 'sql', type: 'groovy.sql.Sql'
property name: 'out', type: 'java.io.PrintWriter'
property name: 'html', type: 'groovy.xml.MarkupBuilder'
delegatesTo(findClass('org.gservlet.AbstractServlet'))
}
}
contributor(classContext) {
if (hasAnnotation('org.gservlet.annotation.Filter')) {
property name: 'logger', type: 'java.util.logging.Logger'
property name: 'config', type: 'javax.servlet.FilterConfig'
property name: 'request', type: 'javax.servlet.http.HttpServletRequest'
property name: 'response', type: 'javax.servlet.http.HttpServletResponse'
property name: 'chain', type: 'javax.servlet.FilterChain'
property name: 'session', type: 'javax.servlet.http.HttpSession'
property name: 'context', type: 'javax.servlet.ServletContext'
property name: 'sql', type: 'groovy.sql.Sql'
property name: 'out', type: 'java.io.PrintWriter'
property name: 'html', type: 'groovy.xml.MarkupBuilder'
delegatesTo(findClass('org.gservlet.AbstractFilter'))
}
}
17. Framework Support
17.1. Spring Boot
You just need this configuration below to leverage the GServlet API in your Spring Boot application.
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.gservlet.GServletApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
@Configuration
public class SpringConfiguration implements ServletContextInitializer {
private GServletApplication application;
@Override
public void onStartup(ServletContext context) throws ServletException {
application = new GServletApplication(context);
application.startOnSpringBoot();
}
@Bean(destroyMethod = "stop")
public GServletApplication servletApplication() {
return application;
}
}
If you want to reuse the data source configured by Spring, this is how you must proceed:
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.sql.DataSource;
import org.gservlet.GServletApplication;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfiguration implements ServletContextInitializer {
private GServletApplication application;
@Override
public void onStartup(ServletContext context) throws ServletException {
application = new GServletApplication(context);
application.startOnSpringBoot();
}
@Bean(destroyMethod = "stop")
public GServletApplication servletApplication(DataSource dataSource) {
application.setDataSource(dataSource);
return application;
}
}
To enable Spring autowiring, you can use an implementation of the ScriptListener interface like this:
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.sql.DataSource;
import org.gservlet.GServletApplication;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
@Configuration
public class SpringConfiguration implements ServletContextInitializer {
private GServletApplication application;
@Override
public void onStartup(ServletContext context) throws ServletException {
application = new GServletApplication(context);
application.addScriptListener(bean -> {
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(bean, context);
});
application.startOnSpringBoot();
}
@Bean(destroyMethod = "stop")
public GServletApplication servletApplication(DataSource dataSource) {
application.setDataSource(dataSource);
return application;
}
}
Your Groovy scripts must be created in the src/main/resources/groovy folder if your application is packaged as a jar. There is a minor issue as the gservlet.properties file is required for the Spring Boot application to start successfully. It will be fixed in the next releases. Just create it empty in the src/main/resources folder if you have not the requirement to set up a database connection. |
17.2. Other Java web frameworks
If the target framework supports the @WebListener annotation of the Servlet API, no configuration is required since we ship with a ServletContextListener which starts and stops the application. This statement is valid for frameworks like JSF, Apache Struts.
package org.gservlet;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class StartupListener implements ServletContextListener {
private GServletApplication application;
@Override
public void contextInitialized(ServletContextEvent event) {
application = new GServletApplication(event.getServletContext());
application.start();
}
@Override
public void contextDestroyed(ServletContextEvent event) {
application.stop();
}
}
18. Code Examples
We have created several code examples on GitHub to help beginners to learn and gain expertise at GServlet. Checkout the appropriate branch for the version that you are using.
19. Copyright and License
Copyright @2020. Free use of this software is granted under the terms of the Apache 2.0 License.
20. Authors
GServlet was created by Mamadou Lamine Ba.
21. Contributing
Contributions of any type or any scope, drive the project forward. There are lot of ways to contribute, not just code. We provide more information about how to get involved in our contribute page.
22. Reporting or Fixing Issues
You can search for existing issues in order to fix them or to raise a new one. We use the GitHub’s issue tracker to keep a track of all our current issues.