Deploying to remote containers using Gradle
This article shows how to deploy Java web application to remote Tomcat container using Gradle and gradle-cargo-plugin. If you haven’t used Gradle before you can install it following this guide. Gradle Cargo plugin uses Cargo (in fact, it uses Cargo Ant tasks) internally, that provides APIs for manipulating Java EE containers. Full list of supported containers is available on the official website.
Example web application
I’m going to start with simple Java webapp as an example. All it does is simply display “Hello!” string. Project layout follows standard Maven layout for webapps, i.e. src/main/webapp for Web application sources.
├── build.gradle
└── src/
└── main/
├── java/
│ └── com/
│ └── passionatedeveloper/
│ └── gradle/
│ └── Start.java
└── webapp/
└── WEB-INF/
└── web.xml
Initial build script is very simple. All we need here is war plugin and servlet-api dependency declaration. Note, that I’m using “providedCompile” scope. It works exactly like “compile”, except that dependencies with this configuration are not added to the WAR archive.
apply plugin: 'war'
repositories {
mavenCentral()
}
dependencies {
providedCompile 'javax.servlet:servlet-api:2.5'
}
Using Cargo plugin
In order to use Gradle Cargo plugin (or any external plugin), we need to add its jar file to the build script’s classpath. There are a couple of ways we can do this. We could for example download jar file from the website and then install it in our local Maven repository, but the easiest way is to let Gradle handle it, and download jar directly from GitHub.
The following snippet shows latter approach and defines GitHub as a repository. We’re using buildscript block to declare build script dependencies as opposite to project dependencies.
buildscript {
repositories {
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = 'GitHub'
addArtifactPattern 'http://cloud.github.com/downloads/[organisation]/[module]/[module]-[revision].[ext]'
}
}
dependencies {
classpath 'bmuschko:gradle-cargo-plugin:0.3'
}
}
Next, we need to apply cargo plugin by:
apply plugin: 'cargo'
Note, that cargo plugin extends war plugin (i.e. applies it internally), so the latter one should be removed from the script.
We’re also going to need Cargo API dependencies. Gradle plugin model currently doesn’t support other way of doing it, so we have to provide all dependencies on our own.
dependencies {
cargo 'org.codehaus.cargo:cargo-core-uberjar:1.1.1',
'org.codehaus.cargo:cargo-ant:1.1.1',
'jaxen:jaxen:1.1.1'
}
Last thing is container configuration. “containerId” is the id of the container we want to use. “tomcat6x” used here corresponds to Tomcat 6, other possible values can be found here. If we skip “context” property, name of the WAR file will be used.
cargo {
containerId = 'tomcat6x'
port = 8080
context = 'mycontext'
remote {
hostname = '192.168.1.101'
username = 'tomcat'
password = 'tomcat'
}
}
Assuming that we have Tomcat instance running at 192.168.1.101:8080 and “tomcat” user with “manager” role created, we can deploy our app by using “cargoDeployRemote” task.
gradle cargoDeployRemote
In most of the cases you will want to use “cargoRedeployThis” task thought. It will first undeploy existing application (if it exists) and then deploy new one.
You can see other available tasks by typing:
gradle tasks
Complete script looks like this:
apply plugin: 'cargo'
repositories {
mavenCentral()
}
buildscript {
repositories {
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = 'GitHub'
addArtifactPattern 'http://cloud.github.com/downloads/[organisation]/[module]/[module]-[revision].[ext]'
}
}
dependencies {
classpath 'bmuschko:gradle-cargo-plugin:0.3'
}
}
cargo {
containerId = 'tomcat6x'
port = 8080
context = 'mycontext'
remote {
hostname = '192.168.1.101'
username = 'tomcat'
password = 'tomcat'
}
}
dependencies {
providedCompile 'javax.servlet:servlet-api:2.5'
cargo 'org.codehaus.cargo:cargo-core-uberjar:1.1.1',
'org.codehaus.cargo:cargo-ant:1.1.1',
'jaxen:jaxen:1.1.1'
}
Conclusion
Gradle Cargo plugin gives you ability to deploy your web application to both local and remote containers by leveraging well and established api. Gradle is wonderful tool that tries to address issues existing in other build tools like Ant and Maven. Even though it’s still under heavy development, plugins like Cargo provides support for more and more use cases.
Thanks for reading! If you’re interested in source files used in this blog post, they’re available here.
In this blog post I’m going to show you how to write custom assertions using two popular assertions frameworks on Java platform: Hamcrest and FEST-Assert.
As an example I will use Range class which simply says whether the given int occurs within its range.
public class Range {
private final int start;
private final int end;
public Range(int start, int end) {
this.start = start;
this.end = end;
}
public boolean contains(int i) {
return start = i;
}
@Override
public String toString() {
return String.format("%d..%d", start, end);
}
}
If we wanted to test this class we would have to write something like this:
Hamcrest
@Test
public void containsIntegersWithinRange() {
Range range = new Range(1, 3);
assertThat(range.contains(1), is(true));
assertThat(range.contains(2), is(true));
assertThat(range.contains(3), is(true));
assertThat(range.contains(4), is(false));
}
FEST
@Test
public void containsIntegersWithinRange() {
Range range = new Range(1, 3);
assertThat(range.contains(1)).isTrue();
assertThat(range.contains(2)).isTrue();
assertThat(range.contains(3)).isTrue();
assertThat(range.contains(4)).isFalse();
}
The first thing we notice is that we have to repeat several statements in order to verify our result. Such procedural style of verification should be avoided in favor of more declarative one. Other problem is that failure messages won’t say us anything useful, unless we override them, but this will make out test code even more complex.
We would rather prefer something like this:
Hamcrest
@Test
public void containsIntegersWithinRange() {
Range range = new Range(1, 3);
assertThat(range, contains(1, 2, 3));
assertThat(range, not(contains(4)));
}
FEST
@Test
public void containsIntegersWithinRange() {
Range range = new Range(1, 3);
assertThat(range).contains(1, 2, 3);
assertThat(range).doesNotContain(4);
}
This code is much more precise and easier to read.
Implementing Hamcrest matcher
Hamcrest uses concept of matcher objects allowing to match given rules. In order to create custom matcher we need to extend TypeSafeMatcher class parametrized by the type of the class we want to make assertions on. Next, we have to implement two abstract methods: matchesSafely and describeTo. To follow convention we should also add convenient factory method:
public class Contains extends TypeSafeMatcher<Range> {
private final int[] integers;
public Contains(int[] integers) {
this.integers = integers;
}
@Override
public void describeTo(Description description) {
description.appendText(format("contains ",
Arrays.toString(integers)));
}
@Override
public boolean matchesSafely(Range item) {
for (int i : integers) {
if (!item.contains(i))
return false;
}
return true;
}
@Factory
public static Matcher contains(int... integers) {
return new Contains(integers);
}
}
Implementing FEST assertions
FEST uses different approach and instead of matchers we’re creating one class for all our assertions. First, we have to subclass GenericAssert class parametrized by the type of our custom assertion class and type of the assertion’s subject class. Next, we can add our assertions code, and we’re done. We also add assertThat factory method so that we can use our assertions in the same way we would use other assertions provided by FEST:
public class RangeAssert extends GenericAssert<RangeAssert, Range> {
public RangeAssert(Range actual) {
super(RangeAssert.class, actual);
}
public static RangeAssert assertThat(Range actual) {
return new RangeAssert(actual);
}
public RangeAssert contains(int... integers) {
for (int i : integers) {
if (!actual.contains(i)) {
fail(format("range: does not contain :", actual,
integers));
}
}
return this;
}
public RangeAssert doesNotContain(int... integers) {
for (int i : integers) {
if (actual.contains(i)) {
fail(format("range: should not contain :", actual,
integers));
}
}
return this;
}
}
Summary
Both frameworks use different approaches for writing custom assertions, but the results are very similar. Using FEST we can implement all our assertions in one class and use them as they were build-in assertions. Hamcrest on the other hand requires us to put all matchers in separate classes (it provides a command-line tool that can generate one Java class for easier imports though).
You can find more information about custom assertions in “xUnit Test Patterns” book by Gerard Meszaros.
All source files can be found here: github