This chapter covers
Chapter 2 introduced Gradle’s feature set and showed how it compared to other JVM build tools. Some simple examples gave you a first impression of the tool’s expressive build language. By running your first build script, you saw how easy it is to become productive on the command line. Now it’s time to strengthen this newly acquired knowledge by building a real-world Java project.
When starting a brand-new application, Java doesn’t guide you toward a standardized project structure. You may ask yourself where to put source, configuration, and library files. What if you want to separate your application code from your test source files? Gradle provides a build-by-convention approach for certain domains like Java projects by introducing predefined project layouts with sensible defaults. Stuck with a legacy application that has a different directory structure? No problem! Gradle allows for adapting its conventions to your needs.
In this chapter, you’ll explore the inner workings of Gradle’s standardization paradigm by building a Java project and learning how to tailor it to nonconventional use cases. In the next step, you’ll extend your application by a web component and introduce productivity tools for fast development turnarounds. Then we’ll round out this chapter by looking at the Gradle wrapper, which allows you to create transferable and reproducible builds without having to install the Gradle runtime.
Introducing the case study
This section introduces a simple application to illustrate the use of Gradle: a To Do application. Throughout the book, we’ll apply the content to demonstrate Gradle’s features in each phase of the build pipeline. The use case starts out as a plain Java application without a GUI, simply controlled through console input. Over the course of this chapter, you’ll extend this application by adding components to learn more advanced concepts.
The To Do application will act as a vehicle to help you gain a broad knowledge of Gradle’s capabilities. You’ll learn how to apply Gradle’s standard plugins to bootstrap, configure, and run your application. By the end of this chapter, you’ll have a basic understanding of how Gradle works that you can apply to building your own webbased Java projects with Gradle.
The To Do application
Today’s world is busy. Many of us manage multiple projects simultaneously, both in our professional and private lives. Often, you may find yourself in situations where you feel overwhelmed and out of control. The key to staying organized and focused on priorities is a well-maintained to-do list. Sure, you could always write down your tasks on a piece of paper, but wouldn’t it be convenient to be able to access your action items everywhere you go? Access to the internet is almost omnipresent, either through your mobile phone or publicly available access points. You’re going to build your own web-based and visually appealing application, as shown in figure 3.1.
Task management use cases
Now that you know your end goal, let’s identify the use cases the application needs to fulfill. Every task management system consists of an ordered list of action items or tasks. A task has a title to represent the action needed to complete it. Tasks can be added to the list and removed from the list, and marked active or completed to indicate their status. The list should also allow for modifying a task’s title in case you want to make the description more accurate. Changes to a task should automatically get persisted to a data store.
To bring order to your list of tasks, you’ll include the option to filter tasks by their status: active or completed. For now, we’ll stick with this minimal set of features. Figure 3.2 shows a screenshot of the user interface rendered in a browser.
Let’s take a step back from the user interface aspect and build the application from the ground up. In its first version, you’ll lay out its foundation by implementing the basic functionality controlled through the command line. In the next section, we’re going to focus on the application’s components and the interactions between them.
Examining the component interaction
We found that a To Do application implements the typical create, read, update, and delete (CRUD) functionality. For data to be persisted, you need to represent it by a model. You’ll create a new Java class called ToDoItem, a plain old Java object (POJO) acting as a model. To keep the first iteration of the solution as simple as possible, we won’t introduce a traditional data store like a database to store the model data. Instead, you’ll keep it in memory, which is easy to implement. The class implementing the persistence contract is called InMemoryToDoRepository. The drawback is that you can’t persist the data after shutting down the application. Later in the book, we’ll pick up this idea and show how to write a better implementation for it.
Every standalone Java program is required to implement a main class, the application’s entry point. Your main class will be called ToDoApp and will run until the user decides to exit the program. You’ll present users with a menu of commands through which they can manage their to-do list by typing in a letter triggering a specific action. Each action command is mapped to an enum called CommandLineInput. The class CommandLineInputHandler represents the glue between user interaction and command execution. Figure 3.3 illustrates the object interaction arranged in a time sequence for the use case of listing all available tasks.
Now you’re ready to implement the application’s functionality. In the next section, we’ll dive right into the code.
Building the application’s functionality
In the last section, we identified the classes, their functions, and the interaction between them. Now it’s time to fill them with life. First, let’s look at the model of a todo action item.
THE TO DO MODEL CLASS
Each instance of the ToDoItem class represents an action item in your to-do list. The attribute id defines the item’s unique identity, enabling you to store it in the in-memory data structure and read it again if you want to display it in the user interface. Additionally, the model class exposes the fields name and completed. For brevity, the getter and setter methods as well as the compareTo method are excluded from the snippet:
IN-MEMORY PERSISTENCE OF THE MODEL
Storing data in memory is convenient and simplifies the implementation. Later in the book, you may want to provide more sophisticated implementations like database or file persistence. To be able to swap out the implementation, you’ll create an interface, the ToDoRepository, as shown in the following listing.
- Listing 3.1 The repository interface
- Listing 3.2 In-memory persistence of to-do items
THE APPLICATION’S ENTRY POINT
The class ToDoApp prints the application’s options on the console, reads the user’s input from the prompt, translates the one-letter input into a command object, and handles it accordingly, as shown in the next listing.
- Listing 3.3 Implementing the main class
Building a Java project
In the last section, we identified the Java classes required to write a standalone To Do application. To assemble an executable program, the source code needs to be compiled and the classes need to be packaged into a JAR file. The Java Development Kit (JDK) provides development tools like javac and jar that help with implementing these tasks. Unless you’re a masochist, you don’t want to run these tasks manually each and every time your source code changes. Gradle plugins act as enablers to automate these tasks. A plugin extends your project by introducing domain-specific conventions and tasks with sensible defaults. One of the plugins that Gradle ships with is the Java plugin. The Java plugin goes far beyond the basic functionality of source code compilation and packaging. It establishes a standard layout for your project and makes sure that tasks are executed in the correct order so they make sense in the context of a Java project. It’s time to create a build script for your application and apply the Java plugin.
Using the Java plugin
In chapter 1, you learned that every Gradle project starts with the creation of the build script named build.gradle. Create the file and tell your project to use the Java plugin like this:
Automatic project generation
When creating the source files, keep in mind that the package you used for the classes directly translates into subdirectories under the root source directory. After creating the build script and moving your source code into the correct location, your project structure should look like this:
BUILDING THE PROJECT
You’re ready to build the project. One of the tasks the Java plugin adds to your project is named build. The build task compiles your code, runs your tests, and assembles the JAR file, all in the correct order. Running the command gradle build should give you an output similar to this:
Each line of the output represents an executed task provided by the Java plugin. You may notice that some of the tasks are marked with the message UP-TO-DATE. That means that the task was skipped. Gradle’s incremental build support automatically identified that no work needed to be done. Especially in large enterprise projects, this feature proves to be a real timesaver. In Chapter 4 you’ll learn how to apply this concept to your own tasks. In the command-line output, you can see concrete examples of skipped tasks: compileTestJava and testClasses. As you provide any unit tests in the default directory src/test/java, Gradle happily moves on. If you want to learn how to write tests for your application and integrate them into the build, see Chapter 7. Here’s the project structure after executing the build:
On the root level of your project, you’ll now also find a directory named build, which contains all output of the build run, including class files, test reports, the assembled JAR file, and temporary files like a manifest needed for the archive. If you’ve previously used the build tool Maven, which uses the standard output directory target, the structure should look familiar. The name of the build output directory is a configurable standard property common to all Gradle builds. You’ve seen how effortless it is to build a Java project by convention without any additional configuration from your side. The JAR file was created under build/libs and is ready for execution. It’s important to understand that the name of the JAR file is derived from the project name. Let’s see the To Do application in action.
RUNNING THE PROJECT
Running a Java program is easy. For now, you’ll just use the JDK’s java command from the root directory of your project:
The Java program starts up, prints a list of all available to-do actions, and awaits your input from the command prompt.
Java standalone application support
That’s it—you effortlessly implemented a Java application and built it with Gradle. All it took was a one-liner in your build script as long as you stuck to the standard conventions. Next, we’ll look at how to customize the build-by-convention standards.
Customizing your project
The Java plugin is a small opinionated framework. It assumes sensible default values for many aspects of your project, like its layout. If your view of the world is different, Gradle gives you the option of customizing the conventions. How do you know what’s configurable? A good place to start is Gradle’s Build Language Reference, available at http://www.gradle.org/docs/current/dsl/. Remember the command-line option properties from chapter 2? Running gradle properties gives you a list of configurable standard and plugin properties, plus their default values. You’ll customize the project by extending the initial build script.
MODIFYING PROJECT AND PLUGIN PROPERTIES
In the following example, you’ll specify a version number for your project and indicate the Java source compatibility. Previously, you ran the To Do application using the java command. You told the Java runtime where to find the classes by assigning the build output directory to the classpath command-line option via -cp build/classes/main. To be able to start the application from the JAR file, the manifest MANIFEST.MF needs to contain the header Main-Class. The following listing demonstrates how to configure the default values in the build script and add a header attribute to the JAR manifest.
- Listing 3.4 Changing properties and adding a JAR header
RETROFITTING LEGACY PROJECTS
Rarely do enterprises start new software projects with a clean slate. All too often, you’ll have to integrate with a legacy system, migrate the technology stack of an existing project, or adhere to internal standards or limitations. A build tool has to be flexible enough to adapt to external constraints by configuring the default settings.
In this section we’ll explore examples that demonstrate the customizability of the To Do application. Let’s assume you started the project with a different directory layout. Instead of putting source code files into src/main/java, you chose to use the directory src. The same concept applies if you want to change the default test source directory. Additionally, you’d like to let Gradle render its build output into the directory out instead of the standard value build. The next listing shows how to adapt your build to a custom project layout.
- Listing 3.5 Changing the project default layout
Configuring and using external dependencies
Let’s think back to the main method in the class ToDoApp. You wrote some code to read the user’s input from the console and translate the first character into a to-do command. To do so, you needed to make sure that the entered input string had a length of only one digit. Otherwise, you’d assign the Unicode null character:
DEFINING THE REPOSITORY
In the Java world, dependencies are distributed and used in the form of JAR files. Many libraries are available in a repository, such as a file system or central server. Gradle requires you to define at least one repository to use a dependency. For your purposes, you’re going to use the publicly available, internet-accessible repository Maven Central:
DEFINING THE DEPENDENCY
A dependency is defined through a group identifier, a name, and a specific version. You’ll use version 3.1 of the library, as shown in this code snippet:
How to find a dependency
RESOLVING THE DEPENDENCY
Gradle automatically detects new dependencies in your project. If the dependency hasn’t been resolved successfully, it downloads it with the next task invocation that requires it to work correctly—in this case, task compileJava:
The dependent jar files are stored under:
Chapter 5 will give a deeper coverage of the topic of dependency management. I know that the To Do application in its current form doesn’t knock your socks off. It’s time to modernize it by adding a visually attractive user interface.
Web development with Gradle
In Java, server-side web components of the Enterprise Edition (Java EE) provide the dynamic extension capabilities for running your application within a web container or application server. As the name Servlet may already indicate, it serves a client request and constructs the response. It acts as the controller component in a Model-View-Controller (MVC) architecture. The response of a Servlet is rendered by the view component—the Java Server Page (JSP). Figure 3.4 illustrates the MVC architecture pattern in the context of a Java web application.
Gradle provides out-of-the-box plugins for assembling WAR files and deploying web applications to a local Servlet container. Before we look at how to apply and configure these plugins, you’ll need to turn your standalone Java application into a web application. We focus next on the web components we’re going to introduce and how they interact with each other.
Adding web components
The Java enterprise landscape is dominated by a wide range of web frameworks, such as Spring MVC and Tapestry. Web frameworks are designed to abstract the standard web components and reduce boilerplate code. Despite these benefits, web frameworks can introduce a steep learning curve as they introduce new concepts and APIs. To keep the example as simple and understandable as possible, we’ll stick to the standard Java enterprise web components.
Before jumping into the code, let’s see how adding web components changes the interaction between the existing classes from the previous section. The Servlet class you’re going to create is called ToDoServlet. It’s responsible for accepting HTTP requests, executing a CRUD operation mapped to a URL endpoint, and forwarding the request to a JSP. To present the user with a fluid and comfortable experience, you’ll implement the to-do list as a single-page application. This means you’ll only have to write one JSP, which you’ll name todo-list.jsp. The page knows how to dynamically render the list of to-do items and provides UI elements like buttons and links for initiating CRUD operations. Figure 3.5 shows the flow through your new system for the use case of retrieving and rendering all to-do items.
As you can see in the figure, you could reuse the class ToDoItem to represent the model and the class InMemoryToDoRepository to store the data. Both classes work seamlessly with the controller and view components. Let’s look at the inner workings of the controller component.
THE CONTROLLER WEB COMPONENT
To make matters simple and centralized, you’ll write a single entry point for all URL endpoints you want to expose to the client. The following code snippet shows the most important parts of your controller web component, the class ToDoServlet:
Using the War and Jetty plugins
Gradle provides extensive support for building and running web applications. In this section we’ll look at two plugins for web application development: War and Jetty. The War plugin extends the Java plugin by adding conventions for web application development and support for assembling WAR files. Running a web application on your local machine should be easy, enable rapid application development (RAD), and provide fast startup times. Optimally, it shouldn’t require you to install a web container runtime environment. Jetty is a popular, lightweight, open source web container supporting all of these features. It comes with an embedded implementation by adding an HTTP module to your application. Gradle’s Jetty plugin extends the War plugin, provides tasks for deploying a web application to the embedded container, and runs your application.
Alternative embedded container plugins
You already know the drill from the last section. First, you’ll apply the plugins and use the default conventions, and then you’ll customize them. Let’s focus on the War plugin first.
THE WAR PLUGIN
I already mentioned that the War plugin extends the Java plugin. In practice, this means that you don’t have to apply the Java plugin anymore in your build script. It’s automatically brought in by the War plugin. Note that even if you applied the Java plugin as well, there would be no side effect on your project. Applying plugins is an idempotent operation, and therefore is only executed once for a specific plugin. When creating your build.gradle file, use the plugin like this:
You implemented your web application with the help of classes that aren’t part of the Java Standard Edition, such javax.servlet.HttpServlet. Before you run the build, you’ll need to make sure that you declare those external dependencies. The War plugin introduces two new dependency configurations. The configuration you’ll use for the Servlet dependency is providedCompile. It’s used for dependencies that are required for compilation but provided by the runtime environment. The runtime environment in this case is Jetty. As a consequence, dependencies marked provided aren’t going to be packaged with the WAR file. Runtime dependencies like the JSTL library aren’t needed for the compilation process, but are needed at runtime. They’ll become part of the WAR file. The following dependencies closure declares the external libraries you need for your application:
Building a web application in Gradle is as straightforward as building a standalone Java application. The assembled WAR file can be found in the directory build/libs after running the command gradle build. By changing the nature of the project from a standalone application to a web application, the task jar was replaced by the task war, as shown in the following output:
The War plugin makes sure that the assembled WAR file adheres to the standard structure defined by the Java EE specification. The war task copies the contents of the default web application source directory src/main/webapp to the root of the WAR file without modifying the structure. Compiled classes end up in the directory WEB-INF/classes, and runtime libraries, defined through the dependencies closure, get put in WEB-INF/lib. The following directory structure shows the contents of the assembled WAR file after running jar tf build/libs/GradleIA-0.1.war:
By default, the WAR filename is derived from the project’s directory name. Even if your project doesn’t adhere to Gradle’s standard conventions, the plugin can be used to build a WAR file. Let’s look at some customization options.
CUSTOMIZING THE WAR PLUGIN
You’ve seen how easy it is to adapt a Java project to custom project structures. The same holds true for unconventional web project layouts. In the following example, we’re going to assume that all of your static files sit in the directory static, and that all of your web application content resides under the directory webfiles:
The following code snippet shows how to configure the convention properties. The War plugin exposes the convention property webAppDirName. The default value src/main/webapp is easily switched to webfiles by assigning a new value. Directories can be selectively added to the WAR file by invoking the from method, as follows:
You’ve seen how to build the WAR file from a web project with a standard structure or customized directory layout. Now it’s time to deploy the file to a Servlet container. In the next section, you’ll fire up Jetty to run the application on your local development machine.
RUNNING IN AN EMBEDDED WEB CONTAINER
An embedded Servlet container doesn’t know anything about your application until you provide the exact classpath and relevant source directories of your web application. Usually, you’d do that programmatically. Internally, the Jetty plugin does all this work for you. As the War plugin exposes all this information, it can be accessed at runtime by the Jetty plugin. This is a typical example of a plugin using another plugin’s configuration through the Gradle API. In your build script, use the plugin like this:
On the last line of the output, the plugin gives you the URL that Jetty listens to for incoming requests. Open your favorite browser and enter the URL. Finally, you can see the To Do web application in action. Gradle will leave the application running until you stop it by pressing Ctrl + C. How did Jetty know what port and context to use for running the application? Again, it’s conventions. The default port of a web application run by the Jetty plugin is 8080, and the context path GradleIA is derived from your project name. Of course, all of this is configurable.
Rapid application development
CUSTOMIZING THE JETTY PLUGIN
Let’s assume you’re not happy with the default values the Jetty plugin provides. Another application is already running on port 8080, and you got tired of typing in the long context path. Just provide the following configuration:
You put together a prototype of a task management web application. After you show it to your coworker, Mike, he says he wants to join forces and bring the application to the next level by adding more advanced features. The code has been committed to a version control system (VCS), so he can go ahead, check out the code, and get started working on it.
Mike has never worked with the build tool Gradle, so he asks you how to install the runtime on his machine and which version to use. Because he didn’t go through the motions of initially setting up Gradle, he’s also concerned about potential differences between setting up Gradle on his Windows machine versus installing it on a Mac. From experience with other build tools, Mike is painfully aware that picking the wrong version of the build tool distribution or the runtime environment may have a detrimental effect on the outcome of the build. All too often, he’s seen that a build completes successfully on his machine but fails on another for no apparent reason. After spending hours troubleshooting, he usually discovers that the cause was an incompatible version of the runtime.
Gradle provides a very convenient and practical solution to this problem: the Gradle wrapper. The wrapper is a core feature and enables a machine to run a Gradle build script without having to install the runtime. It also ensures that the build script is run with a specific version of Gradle. It does so by automatically downloading the Gradle runtime from a central location, unpacking it to your local file system, and using it for the build. The ultimate goal is to create reliable and reproducible builds independent of the operating system, system setup, or installed Gradle version.
When to use the wrapper
Setting up the wrapper
To set up the wrapper for your project, you’ll need to do two things: create a wrapper task and execute the task to generate the wrapper files (figure 3.6).
To enable your project to download the zipped Gradle runtime distribution, define a task of type Wrapper and specify the Gradle version you want to use through the property gradleVersion:
As a result, you’ll find the following wrapper files alongside your build script:
Keep in mind that you’ll only need to run gradle wrapper on your project once. From that point on, you can use the wrapper’s script to execute your build. The downloaded wrapper files are supposed to be checked into version control. For documentation reasons it’s helpful to also keep the task in your project. It’ll help you to upgrade your wrapper version later by changing the gradleVersion and rerunning the wrapper task. Instead of creating the wrapper task manually and executing it to download the relevant files, you can use the build setup plugin mentioned earlier. Executing the command gradle wrapper will generate the wrapper files with the current version of your Gradle runtime.
Using the wrapper
As part of the wrapper distribution, a command execution script is provided. For *nix systems, this is the shell script gradlew; for Windows operating systems, it’s gradlew.bat. You’ll use one of these scripts to run your build in the same way as you would with the installed Gradle runtime. Figure 3.7 illustrates what happens when you use the wrapper script to execute a task.
Let’s get back to our friend Mike. He checked out the application code from the VCS. Included in the source code tree of the project, he’ll find the wrapper files. As Mike develops his code on a Windows box, he’ll need to run the wrapper batch file to execute a task. The following console output is produced when he fires up the local Jetty container to run the application:
The distribution ZIP file is downloaded from a central server hosted by the Gradle project, stored on Mike’s local file system under $HOME_DIR/.gradle/wrapper/dists. The Gradle wrapper also takes care of unpacking the distribution and setting the appropriate permissions to execute the batch file. Note that the download only needs to happen once. Subsequent build runs reuse the unpacked installation of the runtime located in your Gradle home directory. What are the key takeaways? A build script executed by the Gradle wrapper provides exactly the same tasks, features, and behavior as it does when run with a local Gradle installation. Again, you don’t have to stick with the default conventions the wrapper gives you. Its configuration options are very flexible. We’ll look at them in the next section.
Customizing the wrapper
Some enterprises have very restrictive security strategies, especially if you work for a government agency, where access to servers outside of the network is prohibited. How do you enable your project to use the Gradle wrapper in that case? It’s all in the configuration. You’ll change the default properties to target an enterprise server hosting the runtime distribution. And while you’re at it, you’ll also change the local storage directory: