Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Using Your Favorite Dynamic Language With Apache Struts 2

DZone's Guide to

Using Your Favorite Dynamic Language With Apache Struts 2

Struts has gotten a bad rap this year. In this article, we take a look at the positive side of struts, and how it can be used to create a web app.

· Web Dev Zone
Free Resource

Learn how to build modern digital experience apps with Crafter CMS. Download this eBook now. Brought to you in partnership with Crafter Software

If you think that Apache Struts is not relevant nowadays, then you haven't yet read the story of how one can make it hard to beat. We all agree that we are living in a modern web where the trends are SPA, PWA, and the like and that the way people are still using it today does not fit at all into that mindset. But the real strength of a framework lies in its capacity not to be resilient, but to be able to embrace change, and Apache Struts is doing well by remaining flexible and customizable. Then for now, and while waiting for my next articles for a deep view, let us dive into the process of embedding your favorite dynamic language in your web application to write your actions without the use of the deprecated struts-scripting plugin.

Startup

At startup, if you don't exclude it, a base configuration file named struts-default.xml provided in the struts2-core jar file is automatically included to provide the standard configuration settings without having to copy them, and providing your own custom file is the way to make Apache Struts more lightweight or to exclude the Jakarta Multipart parser that is used for file uploads and which was subject to a vulnerability, if, like me, you can't upgrade yet.

<dependency>
  <groupId>org.apache.struts</groupId>
  <artifactId>struts2-core</artifactId>
  <version>2.3.16</version>
</dependency>
<filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.FilterDispatcher </filter-class>
    <init-param>
      <param-name>config</param-name>
      <param-value>struts-custom.xml,struts-plugin.xml,struts.xml</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
@WebListener
public class StartupListener implements ServletContextListener {

  @Override
  public void contextInitialized(ServletContextEvent event) {
    ServletContext context = event.getServletContext();
    context.setAttribute("path",context.getContextPath()+"/");
    FilterRegistration struts2 = context.addFilter("struts2", org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.class);
    struts2.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST,DispatcherType.FORWARD),true, "/*");
    struts2.setInitParameter("config","struts-custom.xml,struts-plugin.xml,struts.xml");
  }

  @Override
  public void contextDestroyed(ServletContextEvent event) {
  }

}

This XML line of code extracted from it:

 <bean type="com.opensymphony.xwork2.factory.ActionFactory" name="struts" class="com.opensymphony.xwork2.factory.DefaultActionFactory" />

means that for a basic struts configuration defined like this :

<struts>

   <package name="root" namespace="/" extends="tiles-default">

      <action name="index" class="WelcomeAction">
        <result type="tiles">index</result>
     </action>

     <action name="login" class="LoginAction">
        <result type="tiles">login</result>
     </action>

   </package>

</struts>

the DefaultActionFactory class which implements the ActionFactory interface is used to instantiate the corresponding Java classes.

public interface ActionFactory {

public Object buildAction(String url, String namespace, ActionConfig config,Map<String, Object> extraContext) throws Exception;

}

And creating your custom ActionFactory to take control over the process is really easy.

Myactionfactory

public class MyActionFactory extends DefaultActionFactory {

    @Override
    public Object buildAction(String url, String namespace, ActionConfig config,Map<String, Object> extraContext) throws Exception {
      Object object = buildAction(url);
      return object!=null ? object : super.buildAction(url, namespace, config, extraContext);
    }

    private Object buildAction(url) {
       if(url.equals('index') || url.equals('login')) {

       }
       return null;
    }
}
<struts>

   <bean type="com.opensymphony.xwork2.factory.ActionFactory" class="MyActionFactory" />

   <package name="root" namespace="/" extends="tiles-default">

     <action name="index" method="welcome">
        <result type="tiles">index</result>
     </action>

     <action name="login" method="login">
        <result type="tiles">login</result>
     </action>

   </package>

</struts>

And if there were a script attribute, we would have even a way to map an action to a scripting file. But can its name attribute be renamed to the URL?

 <action url="index" script="scripts/myscript.groovy" method="welcome">
       <result type="tiles">index</result>
 </action>

Using the JSR 223 and Apache Groovy

The goal of the JSR 223 (Scripting for the Java Platform) which has been withdrawn, is to let you use the power and the flexibility of scripting languages like PHP, Ruby, and Python, and in this article, we are going to use the Apache Groovy language.

<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-jsr223</artifactId>
<version>2.4.9</version>
</dependency>

Myscript.groovy

import com.opensymphony.xwork2.ActionSupport

class MyAction extends ActionSupport {

  def welcome()  {
   SUCCESS
  }

  def login() {
   SUCCESS
  }

}

new MyAction()

With the script written, our custom action factory must now look like this :

public class MyActionFactory extends DefaultActionFactory {

    @Override
    public Object buildAction(String url, String namespace, ActionConfig config,Map<String, Object> extraContext) throws Exception {
      Object object = buildAction(url);
      return object!=null ? object : super.buildAction(url, namespace, config, extraContext);
    }

    private Object buildAction(url) {
       String path = ServletActionContext.getServletContext().getRealPath("/");
       if(url.equals('index') || url.equals('login')) {
         File script = new File(path + "/scripts/myscript.groovy");
         if(script.exists()) {
          String name = script.getName();
          String extension = name.substring(name.indexOf(".") + 1);
          ScriptEngine engine = new ScriptEngineManager().getEngineByExtension(extension);
          return engine.eval(new FileReader(script));
         }
       }
       return null;
    }
}

And with a hot reloading of the configuration file and with this skeleton that we can refine with a clean routing system in place, we are enjoying the real pleasure to not restart our web server to save months of development and with the use of the groovy.json package. We can even say farewell to the struts-json plugin and the rest is much greater.

<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-json</artifactId>
    <version>2.4.9</version>
</dependency>
import static groovy.json.JsonOutput.toJson as json

class MyAction extends ActionSupport {

  def test()  {
    response.writer.write(json([status: 1]))
  }

}

new MyAction()
<action name="test" method="test"/>
$.ajax({
  type: "GET",
  url: "test",
  success: function(response) {
    console.log(response.status);
  },
  dataType: "json"
});

Inheriting from a custom ActionSupport class has also made the code short and elegant and it is much better than having to implement the ServletRequestAware or the ServletResponseAware interface in order to get the request or the response object

public class ActionSupport extends com.opensymphony.xwork2.ActionSupport {

  public HttpServletRequest getRequest() {
    return ServletActionContext.getRequest();
  }

  public HttpSession getSession() {
    return getRequest().getSession(true);
  }

  public HttpServletResponse getResponse() {
    return ServletActionContext.getResponse();
  }

}

And what does this HTML line of code mean to you

<a href='<s:url action="index" />'>Home</a>

when this way much better?

<a href='index'>Home</a>

And I wonder how many of us know the role of the HTML base tag?

<!DOCTYPE html>
<html>
 <head>
   <base href="${path}">
 </head>
 <body>
 </body>
</html>

You can check here for a demo of a progressive, single page e-commerce web application with an offline support, as we can start to see Responsive Web Design as the new norm and to be true, PWA is a hot topic and Apache Tomcat 9 is so fast and its support for HTTP/2 so neat. This is how one can enable its GZIP compression feature:

<Connector port="8080" protocol="HTTP/1.1"
    connectionTimeout="20000"
    redirectPort="443" 
    compression="on"
    compressionMinSize="1024"
    compressableMimeType="text/html,text/xml,text/plain,text/css,application/javascript"/>

And with no HTTPS redirection yet enabled, my sole failed PWA audit, 91 is my Google Lighthouse score and according to WebPagetest, the web application served in 1 request with its fonts encoded as Base64 and its critical assets inlined, is rendered in less than 1s on mobile devices, even if its service worker is not yet installed and activated. It is all installable with its manifest generated by this Web App Manifest Generator and if it feels like a native application then it is only because it is well-designed.

<!DOCTYPE html>
<html>
 <head>
   <base href="${path}">
   <title>Darou Salam Fashion</title>
   <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <meta name="theme-color" content="#ff9b05">
   <link rel="manifest" href="manifest.json">
   <style type="text/css">
    <%@include file="/templates/fashion/css/template.css"%>
   </style>
 </head>
 <body>
   <script>
     <%@include file="/templates/fashion/js/template.js"%>
   </script>
 </body>
</html>
{
  "name": "Darou Salam Fashion",
  "short_name": "DSF",
  "theme_color": "#ff9b05",
  "background_color": "#ff9b05",
  "orientation": "portrait",
  "display": "standalone",
  "start_url": ".",
  "icons": [
    {
      "src": "images/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

The template rendered by Apache Tiles has been initially designed by w3layouts and it is such a pain to shop on several pages. Is it too much to be able to change the look and feel of a web application at runtime or to have a template for the front-end and another for the back-end? Can we parse their metadata with Apache Commons Digester and after 5 years, is JSF still looking for a modular Multi-Templating system?

<template>
  <name>fashion</name>
  <type>front-end></type>
  <selected>true</selected>
</template>
<module>
  <name>commerce</name>
  <url>commerce</url>
  <type>front-end</type>
  <main>true</main>
  <actions>
   <action url="order"  method="order"/>
   <action url="search" method="search"/>
  </actions>
</module>

Image title

Later, when we will talk about how to create a fast PWA, we will see in depth how to use our lovely Apache Maven plugins to minify our JSP, CSS, and JavaScript files when deploying our web applications to the cloud.

<plugin>
  <groupId>com.samaxes.maven</groupId>
  <artifactId>minify-maven-plugin</artifactId>
  <version>1.7.2</version>
  <executions>
   <execution>
    <phase>prepare-package</phase>
     <goals>
      <goal>minify</goal>
     </goals>
   </execution>
  </executions>
  <configuration>
   <charset>ISO-8859-1</charset>
   <skipMerge>true</skipMerge>
   <webappSourceDir>WebContent</webappSourceDir>
   <cssSourceDir>.</cssSourceDir>
   <cssSourceFiles>
    <cssSourceFile>templates/fashion/css/template.css</cssSourceFile>
    <cssSourceFile>modules/commerce/css/wizard.css</cssSourceFile>
   </cssSourceFiles>
   <jsSourceDir>.</jsSourceDir>
   <jsSourceFiles>
    <jsSourceFile>templates/fashion/js/template.js</jsSourceFile>
    <jsSourceFile>modules/commerce/js/wizard.js</jsSourceFile>
    <jsSourceFile>modules/commerce/js/paypal.js</jsSourceFile>
    <jsSourceFile>modules/commerce/js/visa.js</jsSourceFile>
   </jsSourceFiles>
   <nosuffix>true</nosuffix>
   <webappTargetDir>${project.build.directory}/minify</webappTargetDir>
  </configuration>
</plugin>
<plugin>
  <groupId>com.tunyk.mvn.plugins.htmlcompressor</groupId>
  <artifactId>htmlcompressor-maven-plugin</artifactId>
  <version>1.3</version>
  <executions>
   <execution>
    <phase>prepare-package</phase>
    <goals>
     <goal>html</goal>
   </goals>
  </execution>
  </executions>
  <configuration>
   <srcFolder>WebContent</srcFolder>
   <targetFolder>${project.build.directory}/minify</targetFolder>
   <javascriptHtmlSprite>false</javascriptHtmlSprite>
   <encoding>ISO-8859-1</encoding>
   <fileExt>
    <fileext>jsp</fileext>
   </fileExt>
  </configuration>
</plugin>
<plugin>
  <artifactId>maven-war-plugin</artifactId>
  <version>2.6</version>
  <configuration>
   <warSourceDirectory>WebContent</warSourceDirectory>
   <failOnMissingWebXml>false</failOnMissingWebXml>
   <webResources>
    <resource>
     <directory>${project.build.directory}/minify</directory>
    </resource>
   </webResources>
  </configuration>
</plugin>

Crafter is a modern CMS platform for building modern websites and content-rich digital experiences. Download this eBook now. Brought to you in partnership with Crafter Software.

Topics:
apache struts 2 ,web dev ,dynamic languages

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}