DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • How to Merge HTML Documents in Java
  • Designing a Java Connector for Software Integrations
  • Jakarta NoSQL 1.0: A Way To Bring Java and NoSQL Together
  • How to Get Word Document Form Values Using Java

Trending

  • Intro to RAG: Foundations of Retrieval Augmented Generation, Part 2
  • Next Evolution in Integration: Architecting With Intent Using Model Context Protocol
  • Building Reliable LLM-Powered Microservices With Kubernetes on AWS
  • AI-Driven Root Cause Analysis in SRE: Enhancing Incident Resolution
  1. DZone
  2. Coding
  3. Languages
  4. Groovify Your Java Servlets (Part 1) : Getting Started

Groovify Your Java Servlets (Part 1) : Getting Started

Get Groovy with it!

By 
Mamadou Lamine Ba user avatar
Mamadou Lamine Ba
·
Updated Oct. 28, 19 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
10.9K Views

Join the DZone community and get the full member experience.

Join For Free

Groovify

Groovify your Java Servlets!

This article is not about Groovlets, which 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. Groovlets are much more suitable for smaller web applications. Compared to Java Servlets, coding in Groovy can be much simpler.

You may also like: From Java to Groovy in a Few Easy Steps

This post provides a simple example demonstrating the kinds of things you can do with a Groovlet. 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. This is more like a script as it does not have a class wrapper. 

Groovlet

if (!session) {
    session = request.getSession(true)
}

if (!session.counter) {
    session.counter = 1
}

html.html { // html is implicitly bound to new MarkupBuilder(out)
  head {
      title('Groovy Servlet')
  }
  body {
    p("Hello, ${request.remoteHost}: ${session.counter}! ${new Date()}")
  }
}
session.counter = session.counter + 1


In this first part of this series, we are going to set the building blocks of how to bring the same logic to enhance the Servlet API. The goal is to write our artifacts (servlets, filters, listeners) with the Groovy language, which is in constant evolution in the Tiobe Index of language popularity, coming in this month at No. 11. 

Next, we will register them in the ServletContext class. We will consider a live development as a mandatory feature, even if it is well-known that the ServletContext class will throw an IllegalStateException when adding a new Servlet, Filter, or Listener after its initialization. Also, updating the artifacts at runtime is another challenging task.

To lay things down, I will first uncover the final design. In the next articles, I will cover more ground to show you, in detail, how things are achieved technically. The main vision is to use the Groovy language and its provided modules (JSON, SQL, etc.) to simplify Servlet API web development while waiting to apply the same principles to the JAX-RS API.

It is worth it to mention that we are going to build a non-intrusive API that will not affect the current structure of your Java EE project, and you are free to groovify your existing Java Servlets over time. Let's get started!


Image title


New Servlet Signature

import org.gservlet.annotation.Servlet

@Servlet("/customers")
class CustomerServlet {

    void get() {
      def customers = []
      customers << [firstName : "John", lastName : "Doe"]
      customers << [firstName : "Kate", lastName : "Martinez"]
      customers << [firstName : "Allisson", lastName : "Becker"]
      json(customers)
    }

    void post() {
      def customer = request.body // get the json request payload as object 
      json(customer)
    }

    void put() {
      def customer = request.body // get the json request payload as object
      json(customer)
    }

    void delete() {
      def param = request.param // shortcut to request.getParameter("param")
      def attribute = request.attribute // shortcut to request.getAttribute("attribute")
    }

    void head() {
    }

    void trace() {
    }

    void options() {
    }

}


The @WebServlet annotation, which is used to define a Servlet component in a web application, will be reduced to our own @Servlet annotation with the same attributes. In the same spirit, and as read above, the name of the HTTP request method handlers (doGet, doPost, doPut, and so on) will be shortened and they will take no arguments since the request and the response are now implicit variables.

package org.gservlet.annotation;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Servlet {

    String name() default "";
    String[] value() default {};
    String[] urlPatterns() default {};
    int loadOnStartup() default -1;
    WebInitParam [] initParams() default {};
    boolean asyncSupported() default false;
    String smallIcon() default "";
    String largeIcon() default "";
    String description() default "";
    String displayName() default "";

}


We will no longer explicitly extend the HttpServlet class. When a script is loaded by the GroovyScriptEngine, we will use javassist, a library for dealing with Java bytecode, to extend dynamically our own derived HttpServlet class within a BytecodeProcessor instance, which is set anonymously to the Groovy CompilerConfiguration object.  

Servlet Base Class

package org.gservlet;

import java.lang.reflect.Method;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public abstract class HttpServlet extends javax.servlet.http.HttpServlet {

  protected HttpServletRequest request;
  protected HttpServletResponse response;
  protected final Logger logger = Logger.getLogger(HttpServlet.class.getName());

  public void doGet(HttpServletRequest request, HttpServletResponse response) {
     this.request = request;
     this.response = response;
     invoke("get");
  }

  public void doPost(HttpServletRequest request, HttpServletResponse response) {
     this.request = request;
     this.response = response;
     invoke("post");
  }

  public void doPut(HttpServletRequest request, HttpServletResponse response) {
     this.request = request;
     this.response = response;
     invoke("put");
  }

  public void doDelete(HttpServletRequest request, HttpServletResponse response) {
     this.request = request;
     this.response = response;
     invoke("delete");
  }

  public void doHead(HttpServletRequest request, HttpServletResponse response) {
     this.request = request;
     this.response = response;
     invoke("head");
  }

  public void doTrace(HttpServletRequest request, HttpServletResponse response) {
     this.request = request;
     this.response = response;
     invoke("trace");
  }

  public void doOptions(HttpServletRequest request, HttpServletResponse response) {
     this.request = request;
     this.response = response;
     invoke("options");
  }

  private void invoke(String methodName) {
    try {
       Method method = getClass().getDeclaredMethod(methodName);
       method.invoke(this);
    } catch (NoSuchMethodException e) {
         logger.info("no method " + methodName + " has been declared for the servlet " + this.getClass().getName());
    } catch (Exception e) {
         e.printStackTrace();
    }
  }

  public void json(Object object) throws IOException {
     response.setHeader("Content-Type", "application/json");
     write(groovy.json.JsonOutput.toJson(object));
  }

  public void write(String content) throws IOException {
     response.getWriter().write(content);
  }

}


The code of the HttpServlet base class above is not complete. I'm going to wait for the next articles to show you how we are going to make the HttpServletRequest, the HttpServletResponse, and so on, to become implicit variables.

As stated in the style guide, in Groovy, a getter and a setter form what we call a property. Instead of the Java-way of calling getters/setters, we can use a field-like access notation for accessing and setting such properties, but there is more to write about.

Bytecode Processing

protected GroovyScriptEngine createScriptEngine(File folder) throws Exception {
  URL[] urls = { folder.toURI().toURL()};
  GroovyScriptEngine engine = new GroovyScriptEngine(urls);
  CompilerConfiguration configuration = new CompilerConfiguration();
  final ClassPool classPool = ClassPool.getDefault();
  classPool.insertClassPath(new LoaderClassPath(engine.getParentClassLoader()));
  configuration.setBytecodePostprocessor(new BytecodeProcessor() {
    public byte[] processBytecode(String name, byte[] original) {
      ByteArrayInputStream stream = new ByteArrayInputStream(original);
      try {
         CtClass clazz = classPool.makeClass(stream);
         clazz.detach();
         Object[] annotations = clazz.getAnnotations();
         for (Object annotation : annotations) {
            String value = annotation.toString();
            if (value.indexOf("Servlet") != -1) {
              clazz.setSuperclass(classPool.get(HttpServlet.class.getName()));
              return clazz.toBytecode();
            }
          }
       } catch (Exception e) {
           e.printStackTrace();
       }
      return original;
    }
  });
  engine.setConfig(configuration);
  return engine;
}


To route an HTTP request to our new handler methods (get, post, put, and so on), we will register our servlets in the ServletContext as dynamic proxies with an invocation handler instance. A dynamic proxy can be thought of as a kind of Facade as it allows one single class with one single method to service multiple method calls to arbitrary classes with an arbitrary number of methods. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler. 

InvocationHandler class 

package org.gservlet;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicInvocationHandler implements InvocationHandler {

    protected Object target;

    public DynamicInvocationHandler(Object target) {
      this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      return method.invoke(target, args);
    }

    public Object getTarget() {
      return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

}


Servlet Registration 

protected void addServlet(ServletContext context, Servlet annotation, Object object) {
  String name = annotation.name().trim().equals("") ? object.getClass().getName() : annotation.name();
  ServletRegistration registration = context.getServletRegistration(name);
  if (registration == null) {
    DynamicInvocationHandler handler = new DynamicInvocationHandler(object);
    handlers.put(name, handler);
    Object servlet = Proxy.newProxyInstance(this.getClass().getClassLoader(),
      new Class[] { javax.servlet.Servlet.class }, handler);
    registration = context.addServlet(name, (javax.servlet.Servlet) servlet);
    if (annotation.value().length > 0) {
        registration.addMapping(annotation.value());
    }
    if (annotation.urlPatterns().length > 0) {
        registration.addMapping(annotation.urlPatterns());
    }
  } else {
    String message = "The servlet with the name " + name
    + " has already been registered. Please use a different name or package";
    throw new RuntimeException(message);
  }
}


To reload an artifact means to update the corresponding target object of the proxy's invocation handler once our file watcher can detect a file change. The java.nio.file package provides a file change notification API, called the Watch Service API. This API enables us to register a directory (or directories) with the watch service.

When registering, we tell the service which types of events we are interested in: file creation, file deletion, or file modification. When the service detects an event of interest, it is forwarded to the registered process and handled as needed. The WatchService API is fairly low level, allowing us to customize it. We can use it as is, or we can choose to create a high-level API on top of this mechanism.

public void loadScripts(File folder) throws Exception {
  if (folder.exists()) {
     File[] files = folder.listFiles();
     if (files != null) {
       for (File file : files) {
         if (file.isFile()) {
           loadScript(file);
         } else {
           loadScripts(file);
         }
       }
     }
    watch(folder);
  }
}

public void loadScript(File script) throws Exception {
  Object object = scriptManager.loadScript(script);
  register(object);
}

public void register(Object object) throws Exception {
  Annotation[] annotations = object.getClass().getAnnotations();
  for (Annotation annotation : annotations) {
    if (annotation instanceof Servlet) {
     addServlet(context, (Servlet) annotation, object);
    }
    if (annotation instanceof Filter) {
     addFilter(context, (Filter) annotation, object);
    }
    if (annotation instanceof ContextListener) {
     addListener(context, annotation, object);
    }
    if (annotation instanceof RequestListener) {
     addListener(context, annotation, object);
    }
    if (annotation instanceof ContextAttributeListener) {
     addListener(context, annotation, object);
    }
    if (annotation instanceof RequestAttributeListener) {
     addListener(context, annotation, object);
    }
    if (annotation instanceof SessionListener) {
     addListener(context, annotation, object);
    }
    if (annotation instanceof SessionAttributeListener) {
     addListener(context, annotation, object);
    }
 }
}

protected void watch(File folder) {
  boolean reload = Boolean.parseBoolean(System.getenv(Constants.RELOAD));
  if (reload) {
    new FileWatcher().addListener(new FileAdapter() {
      public void onCreated(String fileName) {
         reload(new File(folder + "/" + fileName));
       }
    }).watch(folder);
  }
}


New Filter Signature

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

}


Like for a Listener, the same design is applied to a Filter and we will extend another base class dynamically at runtime. To perceive the difference in terms of simplicity and clarity, the Groovy class above is a complete rewriting of the Java CORS Filter example published at the HowToDoInJava website. Below is the Java Filter base class used to achieve such transformation through inheritance.

Filter Base Class

package org.gservlet;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.logging.Logger;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public abstract class Filter implements javax.servlet.Filter {

   protected FilterConfig config;
   protected FilterChain chain;
   protected ServletRequest request;
   protected ServletResponse response;
   protected Logger logger = Logger.getLogger(Filter.class.getName());

    @Override
    public void init(FilterConfig config) throws ServletException {
      this.config = config;
       try {
          Method method = getClass().getDeclaredMethod("init");
          method.invoke(this);
        } catch (NoSuchMethodException e) {
        } catch (Exception e) {
           e.printStackTrace();
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
        this.request = request;
        this.response = response;
        this.chain = chain;
        try {
          Method method = getClass().getDeclaredMethod("filter");
          method.invoke(this);
         } catch (NoSuchMethodException e) {
            logger.info("no method filter has been declared for the filter " + this.getClass().getName());
         } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void next() throws IOException, ServletException {
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
    }

}


Filter Registration 

protected void addFilter(ServletContext context, Filter annotation, Object object) {
   String name = object.getClass().getName();
   FilterRegistration registration = context.getFilterRegistration(name);
   if (registration == null) {
      DynamicInvocationHandler handler = new DynamicInvocationHandler(object);
      handlers.put(name, handler);
      Object filter = Proxy.newProxyInstance(this.getClass().getClassLoader(),
        new Class[] { javax.servlet.Filter.class }, handler);
      registration = context.addFilter(name, (javax.servlet.Filter) filter);
      if (annotation.value().length > 0) {
          registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD), true,
            annotation.value());
      }
      if (annotation.urlPatterns().length > 0) {
         registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD), true,
            annotation.urlPatterns());
      }
   } else {
      String message = "The filter with the name " + name
      + " has already been registered. Please use a different name or package";
      throw new RuntimeException(message);
    }
}


Without an explicit inheritance, we might think that we will lose the benefits of code completion in our IDEs but such is not the case since the Groovy language is also an excellent platform for the easy creation of domain-specific languages (DSLs). Using a DSLD, which is a DSL descriptor, it is possible to teach the editor some of the semantics behind these custom DSLs. For a while now, it has been possible to write an Eclipse plugin to extend Groovy-Eclipse, which requires specific knowledge of the Eclipse APIs. This is no longer necessary.

DSLD (DSL Descriptor)

// this is a Custom DSL Descriptor for the GServlet API

import org.gservlet.annotation.Servlet
import org.gservlet.annotation.Filter

contribute(currentType(annos: annotatedBy(Servlet))) { 
    property name : 'logger', type : java.util.Logger, provider : 'org.gservlet.HttpServlet', doc : 'the logger'
    property name : 'request', type : javax.servlet.http.HttpServletRequest, provider : 'org.gservlet.HttpServlet', doc : 'the request'
    property name : 'response', type : javax.servlet.http.HttpServletResponse, provider : 'org.gservlet.HttpServlet', doc : 'the response'
    property name : 'session', type : javax.servlet.http.HttpSession, provider : 'org.gservlet.HttpServlet', doc : 'the session'
    property name : 'context', type : javax.servlet.ServletContext, provider : 'org.gservlet.HttpServlet', doc : 'the context'
}

contribute(currentType(annos: annotatedBy(Servlet))) { 
    delegatesTo type : org.gservlet.HttpServlet, except : ['doGet','doPost','doHead','doPut','doTrace','doOptions','doDelete']
}


contribute(currentType(annos: annotatedBy(Filter))) { 
    property name : 'logger', type : java.util.Logger, provider : 'org.gservlet.Filter', doc : 'the logger'
    property name : 'request', type : javax.servlet.http.HttpServletRequest, provider : 'org.gservlet.Filter', doc : 'the request'
    property name : 'response', type : javax.servlet.http.HttpServletResponse, provider : 'org.gservlet.Filter', doc : 'the response'
    property name : 'session', type : javax.servlet.http.HttpSession, provider : 'org.gservlet.Filter', doc : 'the session'
    property name : 'context', type : javax.servlet.ServletContext, provider : 'org.gservlet.Filter', doc : 'the context'
    property name : 'config', type : javax.servlet.FilterConfig, provider : 'org.gservlet.Filter', doc : 'the filter config'
    property name : 'chain', type : javax.servlet.FilterChain, provider : 'org.gservlet.Filter', doc : 'the filter chain'
}

contribute(currentType(annos: annotatedBy(Filter))) { 
    delegatesTo type : org.gservlet.Filter, except : ['init','doFilter']
}


Stay tuned for the next articles and for this upcoming GServlet open source project. 

Further Reading

From Java to Groovy in a Few Easy Steps

How to Make Groovy as Fast as Java

Java (programming language) Groovy (programming language) API

Opinions expressed by DZone contributors are their own.

Related

  • How to Merge HTML Documents in Java
  • Designing a Java Connector for Software Integrations
  • Jakarta NoSQL 1.0: A Way To Bring Java and NoSQL Together
  • How to Get Word Document Form Values Using Java

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!