Build Automation with Gradle - Wrox Press Java Programming 24-Hour Trainer 2nd (2015)

Wrox Press Java Programming 24-Hour Trainer 2nd (2015)

Lesson 36. Build Automation with Gradle

While studying Java and trying to work on hands-on assignments, you’ve been compiling, deploying, and running Java programs in the integrated development environment (IDE). These actions were required to build projects. For a real-world project, the build process usually includes more steps before the application goes into production. For example:

· Running unit and integration tests

· Creating scripts for continuous integration

· Working with version control systems and code repositories

· Managing dependencies (ensuring that the right versions of the third-party libraries are linked to the code)

· Generating program documentation

· Deploying the application on development, quality assurance (QA), and production servers

· Sending notification e-mails about successful or failed builds

In the enterprise setup, creating, customizing and maintaining build scripts is a project in its own, which should be started in early stages of application development.

The build process should not depend on any particular IDE or on the presence of any guru-developer who knows how to compile all the code, link it with required libraries, and deploy it to the right computer. Compiling, building, and deploying the code should be run by scripts. A typical enterprise Java project consists of more than one software module and hundreds of classes, and it is developed by a team (or teams) of programmers, who may use different IDEs.

Continuous Integration (CI) is a practice that requires developers to check the code into a code repository several times a day, and every check-in may initiate a new automated build of the entire project. Besides, there could be scheduled builds that run nightly or weekly regardless of whether the new code was checked in. Teams that practice test-driven development (TDD) include running of multiple tests into the build script to get the reports and act on any issues early. Continuous Delivery practice goes even further than CI; delivery of software to the test or production servers is scripted and automated, too. When a programmer sees a “Who broke the build?” message in the team’s chat window, his first thought is, “It better not be me."

Implementing build scripts improves the discipline in the team . You better think (or test) twice before submitting a piece of the new functionality to the code repository without testing it as it might break the build.

This lesson starts with very brief introduction to the older build tools Ant and Maven, and then you get familiar with the modern build automation tool called Gradle.

Hello World in Ant

About fifteen years years ago, Apache Ant was the most popular build tool in the Java world. The build in Ant is described in a file that contains several targets that the build should achieve a sequence—for example, compile, jar, and run. A target consists of tasks, such as mkdir and javac.

Say you wrote a class HelloWorld, saved it in the file HelloWorld.java, and want to configure an Ant build process. Create a file build.xml with the following content:

<project default="main">

<target name="clean">

<delete dir="build"/>

</target>

<target name="compile" depends="clean">

<mkdir dir="build/classes"/>

<javac srcdir="src" destdir="build/classes"/>

</target>

<target name="jar" depends="compile">

<mkdir dir="build/jar"/>

<jar destfile="build/jar/HelloWorld.jar"

basedir="build/classes">

<manifest>

<attribute name="Main-Class" value="HelloWorld"/>

</manifest>

</jar>

</target>

<target name="run" depends="jar">

<java jar="build/jar/HelloWorld.jar" fork="true" />

</target>

<target name="main" depends="clean, compile, jar, run">

</target>

</project>

This file describes five targets: clean, compile, jar, run, and main. The clean task deletes the directory build. The compile target consists of two tasks: create a directory build and compile all classes located in the directory src into the directory build/classes.

The jar target creates a directory build/jar and then creates a jar file from compiled classes assigning the class HelloWorld as a runnable program. The run target launches the application. The main target runs by default if you go to the directory where the build.xml is located and enter the ant command without parameters.

You may also add custom <property> tags (for example, naming the source or destination directories) and the <path> element listing JAR files that otherwise should have been added to the CLASSPATH environment variable.

Most of the targets depend on the success of another (see the depends attributes). The target’s executional unit fails if any of the targets listed in the depends attribute fail. Writing such detailed instructions on what has to be done is known as the imperative way of describing builds.

You can run one or more targets by specifying their names in the command line. For example, this is how to run several targets with one command:

ant clean compile jar run

All Java IDEs support Ant, and you can run the target without even going to the command window. For example, Eclipse has an Ant view, and you can run each of the tasks from the right-click menu, as shown in Figure 36-1.

image

Figure 36-1: Ant view in Eclipse IDE

Hello World in Maven

In 2004 Apache Maven was released, and it soon became the de facto standard for building Java projects. Maven is not just a build tool; it’s a project management tool. Based on the project object model (POM) defined in one or more pom.xml files, Maven can manage the project’s build process and pull all dependencies either from a local server or a central repository. Maven’s functionality can be extended using numerous existing plug-ins or with those you write on your own.

The smallest unit of work in Maven is called a goal. You can start development with the generation of the standard project structure by opening a Command or a Terminal window and entering the mvn command with the goal archetype:generate:

mvn archetype:generate -DgroupId=hello.app -DartifactId=Lesson36

-DarchetypeArtifactId=maven-archetype-quickstart

-DinteractiveMode=false

In several seconds the directories and files shown in Figure 36-2 are generated in the directory Lesson36.

image

Figure 36-2: Maven-generated project

Per Maven’s conventions, the source code is located in the directory src/main/java, and the compiled code goes into the directory target (it doesn’t exist yet). The file App.java contains the Java class with the code to print Hello World on the console. The file AppTest.java creates the code for unit testing using the JUnit framework covered in Lesson 35. The content of the generated file pom.xml is shown here:

<project xmlns="http://maven.apache.org/POM/4.0.0"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>hello.app</groupId>

<artifactId>Lesson36</artifactId>

<packaging>jar</packaging>

<version>1.0-SNAPSHOT</version>

<name>Lesson36</name>

<url>http://maven.apache.org</url>

<dependencies>

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>3.8.1</version>

<scope>test</scope>

</dependency>

</dependencies>

</project>

With Maven you can, but don’t have to, specify step-by-step instructions for the build process. You describe the project in a declarative way by specifying packaging and dependencies. You described what has to be done, but not how to do it as it’s done with Ant. For example, when you declare that a <packaging> element of the project is JAR, WAR, or EAR, you don’t have to specify detailed executional units because Maven automatically creates a number of goals that have to be executed to create a requested packaging.

Maven promotes the convention over configuration paradigm, where each project has a standardized layout. For example, Maven knows that the source code of the project is located in the directory src/main/java. Standard conventions work well unless you need to create build scripts for several modules that use different conventions. Customizing builds requires writing Maven plug-ins so the final build consists of a mix of XML and scripts written in the Java or Groovy languages.

The results of a Maven build are called artifacts. In the example, the artifact named Lesson36 depends on the artifact junit of version 3.8.1 (the current version of junit is 4.12).

Maven’s build life cycle consists of phases (such as install and clean) , which can run goals (for example, clean:clean or dependency:copy-dependencies). To build the project, you need to go to the directory that contains pom.xml and execute the phase package, which compiles and deploys it according to the <packaging> tag:

mvn package

The phase package compiles and creates the JAR file, attaching the prefix specified in <version> to the filename. You can find this file in the directory target as shown in Figure 36-3.

image

Figure 36-3: The target directory after running the phase package

If you didn’t want to package the compiled classes into a JAR, you could just run the mvn compile command and Maven would compile the classes into the target/classes directory. To see the Hello World message on the console run the following command:

java -cp target/Lesson36-1.0-SNAPSHOT.jar hello.app.App

To run tests that were generated in the test directory run the verify goal:

mvn verify

Both Ant and Maven describe the control flow of the build in XML format. Because XML is not a programming language, the ability to customize and adapt builds for changing environments may not be easy. In our Hello World projects, XML files were rather small, but in real-world projects they could consist of hundreds of lines.

Gradle Basics

Gradle is written in Java, and it doesn’t use XML to describe builds. Gradle uses a Domain Specific Language (DSL) based on Groovy, which is a dynamic language that runs in Java Virtual Machine (JVM). The build script is written in a text file, which has a .gradle extension. But this file is not just data formatted in XML or another way. It’s a program.

The main Gradle concepts are projects and tasks. By default, the name of the directory where the build script is located is a name of the project. Each build consist of tasks. A task is an equivalent of the Ant’s target. A task consists of actions. Gradle may decide to run certain tasks in parallel or not run at all. For example, if the source code has not been changed, Gradle won’t run the compile task.

Installing Gradle is a pretty simple process described in these installation instructions. Just download and unzip the file with the latest Gradle release and add the Gradle’s bin directory to the PATH environment variable on your computer.

Gradle has a user guide in both HTML and PDF formats, which you can find in the docs/userguide directory of your Gradle install. There is also a javadoc for the Gradle API in the directory docs/javadoc. There you can find a description of interfaces Project, Task, Action, and many others.

Similarly to Maven, Gradle uses a certain convention about the default project directory layout (it can be customized). Because we’ll be creating a Java build, the default location of the Java source files is the directory src/main/java. The directory src/main/resources is a default location for all project resources that are usually packaged inside a JAR file with compiled Java classes. By default, the build output goes into the directory called build.

The next section starts familiarizing you with Gradle by showing you how to compile and run a HelloWorld class that should print Hello World on the system console.

Hello World in Gradle

In this example you’re going to place the HelloWorld class in the package hello. Start with creating a directory src/main/java/hello and save the file HelloWorld.java there.

Building Hello World

Although core Gradle doesn’t provide much functionality, you can use it as a build tool for different programming languages. All language-specific features are provided as plug-ins, and you add only those that are needed for your project. Some plug-ins are available out of the box, and you can extend Gradle by writing your own plug-ins.

Here’s a simple script, build.gradle, that can perform several tasks. In particular, it can compile Java classes located in the directory src/main/java:

apply plugin: 'java'

The file build.gradle should be located in the same directory as src. To run the build, open the Command or Terminal window in this directory and run the build task:

gradle build

Although you’ve never declared the build task in build.gradle, it’s already a part of the Java plug-in. You’d need to read the documentation to learn what a specific plug-in can do for you. For example, the documentation for the java plug-in describes 22 tasks (for example,clean, compileJava, jar, and build). The test task runs the tests created with JUnit covered in Lesson 35. After running the build task, my Terminal window looked like Figure 36-4.

image

Figure 36-4: Running the Gradle build command the first time

Note that the build task initiated a series of other tasks: compileJava, processResources, classes, jar, and so on . Actually, some of the tasks were marked as UP-TO-DATE—they were not run because you don’t have any resources under src/main/resources or test classes under src/test/java. You also haven’t included another plug-in that can check the quality of your code. Now run the Gradle build task again. The output looks a little different, as shown in Figure 36-5.

image

Figure 36-5: Running Gradle build again

This time Gradle had nothing to do because everything, including compiled classes and the JAR, was up to date. This feature is called incremental build—Gradle doesn’t run the tasks, which would produce the same results.

The compileJava task supports incremental Java compilation, which saves time by compiling only those classes that have been modified.

I started the build having only class HelloWorld in the folder src/main/java/hello. Gradle’s build task has created the build directory with the content shown in Figure 36-6.

image

Figure 36-6: The Hello World project after running the Gradle build task

The classes directory contains the compiled classes, and the lib directory contains the JAR file with HelloWorld.class inside (it was created by the task jar).

Specifying the Build Filename

By default, Gradle assumes that the name of the build script is build.gradle. But if you’d like to keep more than one build file in the same directory, you can always use the -b command-line option and enter the build filename; for example:

gradle -b mySecondBuildFile.gradle build

Running Hello World

You can run the compiled HelloWorld class as any other Java program by entering the following command:

java -cp build/classes/main hello.HelloWorld

On the other hand, you can add to build.gradle the application plug-in that has tasks to run programs. Because your project may have multiple classes, you also need to specify the name of the class with the main() method that you want to run:

apply plugin: 'java'

apply plugin: 'application'

mainClassName="hello.HelloWorld"

Now enter gradle run on the command line, and you see the Hello World message printed on the console.

Next, configure the manifest property of the jar object (yes, Gradle tasks are objects). To make the JAR executable, it has to include the manifest file that specifies the class with the main() method in our application. In the Hello World example, you have a single class, which has the main() method. But in larger projects you have multiple files, and only one of them is the main one. The following build file takes care of the manifest:

apply plugin: 'java'

apply plugin: 'application'

mainClassName="hello.HelloWorld"

jar {

manifest {

attributes 'Main-Class': 'hello.HelloWorld'

}

}

After running the build task, you can go to the build/libs directory and run the HelloWorld application as follows:

java -jar HelloWorld.jar

If you want to see the content of the manifest file that Gradle created and placed inside the JAR, visit the build/tmp/jar directory where you’ll find a MANIFEST.MF file with the following content:

Manifest-Version: 1.0

Main-Class: hello.HelloWorld

Your build.gradle file looks a lot shorter than the build.xml shown in the section Hello World with Ant, but with Ant it’s easier to understand what the build process can do. On the other hand, on large projects you need to write lots of Ant targets whereas Gradle plug-ins include multiple tasks out of the box.

Changing Gradle Conventions

Software developers may be accustomed to certain project layouts, which may not necessarily match Gradle default conventions. This section shows you how to change Gradle’s conventions on where the sources and resources are located, and where the output files should go.

Say that I want to keep the Java source code in the src directory, and not src/main/java, which is Gradle’s default. I want to keep additional resource files in the resources directory instead of src/main/resources. I also want to change my output directory from build to bin .

The SourceSet API enables you to change Gradle conventions of the Java projects. A source set is a concept of a Java plug-in that represents a group of files (classes and resources) that are compiled and executed together. The Java plug-in defines two standard source sets: main and test. Because you haven’t written any test classes; you can just redefine the directory for the source set main.

For illustration purposes, this example also shows you how to have Gradle create the libs directory as a sibling to src and binusing the property libsDirName. Use the same HelloWorld example to try this out. I’m using the build.gradle from the previous section and adding a sourceSets code block to it:

apply plugin: 'java'

apply plugin: 'application'

mainClassName="hello.HelloWorld"

jar {

manifest {

attributes 'Main-Class': 'hello.HelloWorld'

}

}

sourceSets{

main{

java{

srcDirs=['src']

output.classesDir='bin'

}

resources {

srcDir 'resources'

}

}

}

libsDirName='../libs'

Now move hello/HelloWorld.java from src/main/java to src and run the preceding build to see the bin and libs directory at the same level as src, as shown in Figure 36-7.

image

Figure 36-7: Running the build after changing Gradle’s defaults

I’m not encouraging you to change the default project layout, but in some cases you need to introduce Gradle in the existing projects with a different directory structure, so having this type of flexibility helps.

Managing Dependencies with Gradle

Real-world Java projects often depend on external libraries. For example, the JDBC example from Lesson 21 wouldn’t work without the Derby DB drivers packaged in the file derbyclient.jar. Hence, to create a build script for the application that works with Derby DB, you need to add this JAR to the build.gradle script.

But knowing what JAR files to add is not enough. You also need to specify where these files are located. Accordingly, the dependencies code block answers the what question, and repositories points at the location where these files are stored.

Dependency Management is well described in the Gradle tutorial, but this section shows you how to work with repositories and dependencies while working on a concrete example, namely the EmployeeList program from Lesson 21. This program retrieves a list of employees from the Derby DB database. Start with creating a directory derbySample; the build.gradle goes there. Then create subdirectories src/main/java and copy to there the file EmployeeList.java from Lesson 21:

// Class EmployeeList displays Employees from the table EMP

// using JDBC drivers of type 4

import java.sql.*;

class EmployeeList {

public static void main(String argv[]) {

String sqlQuery = "SELECT * from Employee";

// Open autocloseable Connection, Statement

// and get the result set

try (Connection conn = DriverManager.getConnection(

"jdbc:derby://localhost:1527/Lesson21");

Statement stmt = conn.createStatement();

ResultSet rs = stmt.executeQuery(sqlQuery); ) {

// Process the result set - print Employees

while (rs.next()){

int empNo = rs.getInt("EMPNO");

String eName = rs.getString("ENAME");

String job = rs.getString("JOB_TITLE");

System.out.println(""+ empNo + ", " + eName + ", " + job );

}

} catch( SQLException se ) {

System.out.println ("SQLError: " + se.getMessage ()

+ " code: " + se.getErrorCode ());

} catch( Exception e ) {

System.out.println(e.getMessage());

}

}

}

In the directory derbySample, create the gradle.build file that looks like this:

apply plugin: 'java'

apply plugin: 'application'

mainClassName="EmployeeList"

Running the task gradle build produces no errors and creates the file EmployeeList.class in the directory build/classes/main and derbySample.jar in the directory build/libs. But when I executed the command gradle run it gave me the runtime exception "No suitable driver," as shown in Figure 36-8.

image

Figure 36-8: Running Gradle results in an exception

You know why: Java could not find JDBC drivers to work with Derby DB. In other words, the application EmployeeList depends on the file derbyclient.jar., which was not included in the CLASSPATH. I broke the build! Let’s fix it by adding repositories and dependencies.

Repositories

Repositories of the code that a project depends on can be local or remote. You may have a dedicated server within the organization where all third-party and homemade JARs are located. In another scenario, the JAR (or JARs) you project may be created as a part of another project build and published in a predefined location.

There are many software source code hosting facilities on the Internet. Some of them are public, and some are private. Some of them host source code, and some host-compiled code (or binaries). In the Java world, the most popular repositories of the compiled code are Maven Central, Apache Ivy, and Bintray by JFrog. This section uses Maven Central to look for the derbyclient.jar. A quick search in Maven Central produces the result shown in Figure 36-9.

image

Figure 36-9: Derby Client at Maven Central

Every artifact in Maven Central has such attributes as group id, artifact id, and version. If you click on the Latest Version link, you find a detailed information about this artifact, and you can even copy the string with dependencies for automatic download of this artifact. You use group id,artifact id, and version in the dependencies section of the build in the next section. For now, just add Maven Central in the repositories block of the build script:

apply plugin: 'java'

apply plugin: 'application'

mainClassName="EmployeeList"

repositories{

mavenCentral()

}

I’ll use Maven Central repository, but if you want to store the artifacts in one of your company servers, you could specify it as follows:

repositories{

mavenLocal()

}

By default, Gradle uses the directory USER_HOME/.m2/repository on the local computer. This location can be changed in the file settings.gradle (see Gradle documentation for details).

You’re done with the first part. Your build script knows where to look for project dependencies, which are covered in the next section.

Dependencies and Configurations

Your build process may depend on certain JARs during different phases of its control flow. In some cases, your code won’t even compile if a certain JAR is not available in the CLASSPATH. For example, when you’ve been creating Dynamic Web Projects in Eclipse, all references to the Java EE classes, interfaces, or annotations (for example, HttpServlet or @Stateless) were known to the project during the compilation. The reason being that when Eclipse creates a Dynamic Web Project for a specific target server, it adds all JARS (dependencies) to the project. Open the Build Path of any of your GlassFish Eclipse projects, and you’ll find there an entry called GlassFish System Libraries that includes dozens of JARs.

In some cases (as with the EmployeeList program), the code compiles but generates errors during the run time. The same applies to compiling and running test programs.

In Gradle, when you declare a dependency, you need to specify the configuration where this dependency should be used. Run the task gradle dependencies in the Command window to see a list of available configurations for Java projects, as shown in Figure 36-10.

As you can see, dependencies can be configured for compilation, run time, archiving, and testing tasks. Add the dependencies section to your gradle.build from the derbySample directory:

apply plugin: 'java'

apply plugin: 'application'

mainClassName="EmployeeList"

repositories{

mavenCentral()

}

dependencies {

runtime group: 'org.apache.derby', name: 'derbyclient',

version: '10.11.1.1'

}

If you execute the gradle run task now, the “No suitable driver” exception isn’t thrown. The task run downloads the dependency from Maven Central and the JDBC driver is found. You get the server connection error because the Derby DB server is not running, but it’s a different problem to address (see Figure 36-11).

image

Figure 36-10: Running Gradle dependencies shows available configurations

image

Figure 36-11: Gradle’s run task downloads dependency derbyclient-10.11.1.1.jar

The shorter way of declaring the same dependency would look like this:

dependencies {

runtime 'org.apache.derby:derbyclient:10.11.1.1'

}

If you wouldn’t need to specifically request the version 10.11.1.1, you could request any version of the derbyclient.jar that’s greater than 10:

dependencies {

runtime 'org.apache.derby:derbyclient:10+'

}

Gradle caches the downloaded artifacts and stores them in your home directory under the .gradle directory. On my computer, I found the downloaded file derbyclient-10.11.1.1.jar in the following directory:

/Users/yfain11/.gradle/caches/modules-2/files-2.1/org.apache.derby

If you want to be able to print the exact location of the cached runtime artifacts on your computer, you can add the task showMeCache (the name can be different) to your gradle.build file:

task showMeCache << {

configurations.runtime.each { println it }

}

Running gradle showMeCache prints the location of the derbyclient JAR. Similarly, the task printing the cache content of the compile configurations could look like this:

task showMeCache << {

configurations.compile.each { println it }

}

Packaging Dependencies Inside a JAR

Knowing that dependencies are cached in the .gradle directory helps if you want to know where they are located during development, but if you’re deploying the debrySample.jar on another computer the derbyclient JAR won’t be there. At this point the derbySample.jar includes only one file:EmployeeList.class. It doesn’t even have a manifest file making this JAR executable. You can customize and add the jar task to your build script so it packages the derbyclient-10.11.1.1.jar inside derbySample.jar.

So far you have Derby client as a runtime dependency. But the derbySample.jar is created before you run the application. So specify your dependency earlier in the compile configuration. This way the build task downloads it during the project compilation. You also need to customize the jar section to copy the dependencies inside the output jar. This is how I did it:

apply plugin: 'java'

apply plugin: 'application'

mainClassName="EmployeeList"

repositories{

mavenCentral()

}

dependencies {

compile 'org.apache.derby:derbyclient:10+'

}

jar {

from configurations.compile.collect {entry -> zipTree entry}

manifest {attributes 'Main-Class': 'EmployeeList'

}

}

It takes just one line to loop through the file hierarchies of all compile configurations. In this case, the file hierarchy represents the content of derbyclient-10.11.1.1.jar, and the following action extracts all its content:

from configurations.compile.collect {zipTree it}

The method collect gets a reference to all compile configurations, and the zipTree method extracts the entire file hierarchy from the file in a zip format. Then the jar task includes these file hierarchies inside the output JAR. After running gradle build I unzipped the derbySample.jar. It included all derby classes that were initially located inside the file derbyclient-10.11.1.1.jar, as shown in Figure 36-12.

image

Figure 36-12: The content of the derbySample.jar

Now the derbySample.jar is a self-contained application, and if you open a Command window and change the directory to build/libs, you can run the application by running the standard Java command:

java -jar derbySample.jar

The file derbySample.jar can be copied on any server now. You can read about available file operations in Chapter 16 of the Gradle User Guide.

If you really want to see the employee list retrieved from the database created in Lesson 21, start Derby DB server in a separate command window and rerun the preceding command. Figure 36-13 shows my result.

image

Figure 36-13: Getting employees from Derby DB database

Gradle Wrapper

When a team of programmers works on a project, you may run into a situation when developers have different versions of Gradle run time. If a new developer joins the team, she may not even have Gradle installed yet. The Gradle Wrapper allows to run build scripts without worrying about incompatible versions of the runtime. The Wrapper ensures that the build script runs with a specific version of Gradle. It automatically downloads and installs the proper version of Gradle if need be.

You need to create the wrapper task. For example:

task wrapper (type: Wrapper) {

gradleVersion = '2.2.1'

}

Then run it:

gradle wrapper

This creates two scripts for executing Gradle commands—gradlew and gradlew.bat—and a directory gradle/wrapper with two files in it: gradle-wrapper.jar and gradle-wrapper.properties. The JAR file contains the library to download and unpack Gradle’s distribution. The properties file contains the wrapper’s metadata. If you already have the right version of Gradle running, the wrapper task won’t do anything.

After all these files have been added to your project, you should use gradlew instead of gradle to execute all your build tasks. When a person without Gradle (or with the wrong version) runs any task for the first time using the gradlew script, the Gradle distribution is downloaded and unpacked in .gradle/wrapper/dist in her home directory.

Building a WAR file

If you need to compile a web application and package it in a WAR file, you need to add the war plug-in (it extends thejava plug-in) to your build file:

apply plugin: 'war'

But your code becomes dependent on the availability of certain JARs that come with your Java EE server. In Eclipse IDE, when you create a Dynamic Web Project and select GlassFish (or any other Java server) as a target run time, and all required JARS become automatically available for your project during the compilation and run time. But Gradle doesn’t use IDE settings, and it needs to know where these files are located. Say that your web application has the following Java servlet:

import javax.servlet.*;

import javax.servlet.annotation.*;

@WebServlet("/books")

public class FindBooksServlet extends HttpServlet {

// the servlet's code goes here

}

Unless you add a repository and dependencies section to your build script, this servlet won’t compile because it won’t find the declaration of the annotation @WebServlet and the class HttpServlet. If you run the command gradle dependencies, you see that in addition to all configurations available for the java plug-in, the war plug-in adds two more: providedCompile and providedRuntime.

The providedCompile configuration is for specifying an additional CLASSPATH for libraries that shouldn’t be part of the WAR file. The word provided means that these libraries are provided in the environment where the task runs. providedRuntime has a similar use, but it adds aCLASSPATH for the run time. Add providedCompile and a repository to the build file:

apply plugin: 'war'

repositories {

mavenCentral()

}

dependencies {

providedCompile 'javax.servlet:javax.servlet-api:3.1+'

}

If you run the gradle war command now, the JAR with servlet 3.1 API is downloaded, the code compiles, and the WAR (not JAR) file is created in the directory build/libs of your project. The size of this file is rather small as it contains only the application code.

If you’d like to add not only the servlets but the entire Java EE API, the dependencies section could look like this:

dependencies {

providedCompile 'javax:javaee-api:7+'

}

Bintray Repository

In all code samples, I’ve been using Maven Central as a repository. However, instead of mavenCentral() you could use jcenter() if you’re getting the JARs from Bintray.

Using Gradle in Eclipse IDE

Gradle supports all major Java IDEs. Each IDE has its own proprietary project structure, which depends on the project type. For example, when you create a Dynamic Web Project in Eclipse, it creates certain directories (for example, WebContentand WEB-INF), which would not be created for a regular Java project. To add a JAR to the Java CLASSPATH, you open project properties and add the required file using the Project Build Path window.

Eclipse stores the project structure in the files .project, .classpath, and in an optional directory .settings . If a zip file or a directory contains an Eclipse project file, you can create an Eclipse project by selecting File → Import → General → Existing Project into Workspace.

The creators of Gradle added support of Eclipse projects in the form of Gradle plug-ins for Eclipse, which are covered in the next section.

Gradle Eclipse Plug-ins

Gradle comes with two Eclipse plug-ins: eclipse and eclipse-wtp:

· The eclipse plug-in is used for creating regular Eclipse Java projects.

· The eclipse-wtp plug-in is used for creating Dynamic Web Projects. Internally it uses the settings from Web Toolkit Platform—hence the wtp suffix. If you decide to use eclipse-wtp, it supports all the tasks available in the eclipse plug-in, too.

To see the eclipse plug-in in action, create a copy of the HelloWorld directory from hellogradle and call it HelloWorldEcipse. It has the HelloWorld class in the directory src/main/java/hello. The build.gradle file should have the following content:

apply plugin: 'java'

apply plugin: 'eclipse'

Run the gradle eclipse command, and it generates two new files—.classpath and .project—and the directory .settings. Open Eclipse and select File → Import → General → Existing Projects into Workspace, and point at the HelloWorldEclipse as the root directory. Eclipse imports the project, and you can run it as you did all other projects. Figure 36-14 shows how the imported project looks on my computer.

image

Figure 36-14: Gradle-generated project in Eclipse

A simple build.gradle file for generating an Eclipse Dynamic Web Project can look like this:

apply plugin: 'war'

apply plugin: 'eclipse-wtp'

The war plug-in extends the java plug-in and adds support for assembling WAR files for web application deployments.

To generate an Eclipse Dynamic Web Project you still need to run the gradle eclipse command, but the content of generated files .project and .classpath will be different. In the “Try It” section of this lesson you generate, import, and deploy an Eclipse project using the eclipse-wtp plug-in.

If you need to regenerate Eclipse project files from scratch, run the gradle cleanEclipse command to delete the existing project files.

Eclipse IDE and Gradle

Gradle supports IDEs, and IDEs support Gradle. Gradle Integration for Eclipse is an Eclipse IDE plug-in created by Pivotal. It allows creating and importing Gradle projects, and you can run builds right from the Eclipse IDE.

After installing the plug-in, I imported the HelloWorldEclipse project from the previous section by using File → Import → Gradle → Gradle Project. The right-click menu on the build.gradle now has an option Run As → Gradle Build. You can either manually enter the task to run or open the tasks view by selecting Window → Show View → Gradle → Gradle Tasks. Figure 36-15 shows a Gradle Task View in Eclipse for my imported project HelloWorldEclipse.

image

Figure 36-15: Gradle Tasks View in Eclipse IDE

You can select and execute a task by pressing the green play button on the status bar on the top.

Eclipse Marketplace has yet another product that includes Gradle Integration. It’s called Gradle ID Pack, and it offers additional utilities for code block highlighting, an archive editor, and more.

If Gradle Integration for Eclipse Won’t Install

If you run into issues during the Gradle Integration plug-in installation, turn off all Spring-related options on the confirmation window displayed during install.

This concludes my introduction to Gradle. I haven’t covered the subject of creating custom tasks in Gradle. For this and other Gradle features, please refer to the book Building and Testing with Gradle, which is available as a free online version .

Try It

In this assignment you need to create and use a Gradle build script to generate an Eclipse Dynamic Web Project. Then you import it into the Eclipse IDE, deploy it under GlassFish, and confirm that the web application works. In this assignment you use the FindBooksServlet class fromLesson 26.

Lesson Requirements

You should have Java, GlassFish, and Gradle installed.

You can download the code and resources for this “Try It” from the book’s web page at www.wrox.com/go/javaprog24hr2e. You can find them in the Lesson36 folder in the download.

Step-by-Step

1. Create a directory FindBookGradle with two subdirectories: src and WEB-INF.

2. Inside the directory FindBookGradle create the build.gradle file with the following content:

3. apply plugin: 'war'

4. apply plugin: 'eclipse-wtp'

5. Under the src directory, create subdirectories main and java and copy the file FindBooksServlet.java from the Lesson 26 code samples into src/main/java.

6. In the Command or Terminal window, change the directory to FindBookGradle and run the command gradle eclipse. After this task completes, you find there files .classpath, .project, and the directory .settings.

7. Open Eclipse in the Java EE perspective. You should have GlassFish server configured there. Select File → Import → General → Existing Projects into Workspace. Select FindBookGradle as a root directory and press the Finish button. The project is imported to Eclipse.

8. In the project properties menu, select Targeted Runtimes, and then select GlassFish 4 as your server. Click OK.

9. Right-click the GlassFish server in the Servers view and deploy the project FindBookGradle using the Add and Remove menu. Because you are deploying the copy of the FindBookServlet from Lesson 26, make sure that the project Lesson26 is not deployed in GlassFish.

10. Enter the URL http://localhost:8080/FindBookGradle/books in your web browser, and you should see the message Hellow from FindBooks.

TIP Please select the videos for Lesson 36 online at www.wrox.com/go/javaprog24hr2e. You will also be able to download the code and resources for this lesson from the website.