Image by Brian Kenney via Shutterstock, with addition of Mockito Made Clear cover

Unboxing Day, Part II

Mockito Made Clear

Kenneth Kousen
6 min readFeb 1, 2023

--

https://pragprog.com/newsletter/

Unboxing my ebook in Java; downloading it first.

In the first article in this series, I talked about the challenges of creating an unboxing video for an ebook, given the complication that there’s no box to open. The book in question is Mockito Made Clear, from the Pragmatic Bookshelf, also available as a Kindle version at Amazon.

Amazon recommends that you add a video to the book’s page on their site, as I did for my previous book, Help Your Boss Help You. Sadly, the video I added shows up only on the paperback version page and not on the Kindle version page, but I guess that makes sense.

In this waggish and frolicsome series, I demonstrate Java-based methods that download and open the ebook — all so I can simulate an unboxing process. I left off by using the following to open a file on the local file system with the application associated with the file extension:

public void openPdf(String fileName) {
if (Desktop.isDesktopSupported()) {
try {
Desktop.getDesktop().open(new File(fileName));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

The next question: how to download the file itself programmatically? That task will be complicated by the fact that Dropbox URLs have issues — which we’ll get to later.

Step 0 was just click to open the pdf file. Step 1 was mostly the openPdf method just shown. Now let’s move on to Step 2.

Step 2: Downloading the PDF

There are a minimum of three different ways to download a file given its URL. I’ll show you all three just to mesh with my ultimate goal, which is to over-complicate the unboxing process to an absurd degree.

Method 1: HTTP Client

The first way to download a file is based on the HttpClient API added to Java in version 11. I’ve been using that for a while, but never had the opportunity to use the HttpResponse.BodyHandlers.ofFile method, until now.

The HTTP Client API is formally JEP 321, which replaces an Incubator version in JEP 110. The goals of the client were essentially to replace the existing HttpURLConnection class, introduce an asynchronous mode, support HTTP/2 and WebSockets, and more. Like so many APIs these days, it’s all about factory methods and fluent interfaces.

Here’s how to use it to download a file at a link:

public long downloadFile(String url) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.build();
HttpResponse<Path> response =
client.sendAsync(request,
HttpResponse.BodyHandlers.ofFile(Paths.get(FILENAME)))
.join();
Path body = response.body();
return body.toFile().length();
}

Create a new HttpClient from a factory method. Create a new HttpRequest from a factory method, specifying the URL. Then execute the request using either the send (synchronous, blocking) method or the sendAsync (asynchronous) method.

The fun part is that both the send and the sendAsync methods take two arguments, the request and a body handler, whatever that is:

The send and sendAsync methods from HttpClient

The send method blocks, but then returns an HttpResponse, while the sendAsync method does not block, but returns a CompletableFuture that wraps the output HttpResponse. The HttpResponse.BodyHandler inner interface is a functional interface that has several implementations in the HttpResponse.BodyHandlers (plural) inner class. The Javadocs show a few implementations, including:

// Receives the response body as a String
HttpResponse<String> response = client
.send(request, BodyHandlers.ofString());

// Receives the response body as a file
HttpResponse<Path> response = client
.send(request, BodyHandlers.ofFile(Paths.get("example.html")));

The downloadFile implementation demonstrates using the sendAsync method to create a file from the URL, calling the join method on the CompletableFuture to wait for it to finish. Might as well do the networking part off the UI thread, even though there’s no actual UI in this case.

Finally, extract the body from the response, and the method returns the length of the file — useful in testing:

@Test
void downloadFile() {
long length = unboxing.downloadFile(dropBoxLink);
assertThat(length).isCloseTo(downloadedBookSize, within(10000L));
}

The test uses a few methods from the AssertJ library of syntactic sugar that makes the tests easier to follow. There’s a bit of a complication with links from Dropbox, but we’ll get to that later.

This approach works, but feels overly complicated, even when complications are what we want. Isn’t there an easier way?

Method 2: Open a Stream and Copy

As it turns out, you can just open an InputStream to the URL and use the Files.copy method to save the returned bytes:

public long simplerDownloadFile(String url) {
try (InputStream in = URI.create(url).toURL().openStream()) {
return Files.copy(in, Paths.get(FILENAME));
} catch (IOException e) {
throw new RuntimeException(e);
}
}

Note the nice try-with-resources construct, which not only closes the stream when you’re done but also gives you cool nerd credibility during the inevitable code review.

The copy method in the Files class copies all the incoming bytes into a File and returns the total number of bytes read. Sweet.

But wait, there’s more. Before any of this mess came along, the Apache Commons IO project supplied lots of useful options for handling tasks like these. Can’t that help too?

Of course it can, or I wouldn’t have brought it up.

Method 3: Apache Commons IO FileUtils

The Apache Commons IO project is “a library of utilities to assist with developing IO functionality.” The project includes a whole series of classes to perform a wide range of functions.

The one we want is the FileUtils class, from org.apache.commons.io, naturally enough, which states:

Part of the Javadocs for the FileUtils class from Apache Commons IO

The method that helps is called copyURLToFile, which takes a URL source and a File destination. Using that approach gives this implementation:

public long copyURLToFile(String url) {
try {
File file = new File(FILENAME);
FileUtils.copyURLToFile(URI.create(url).toURL(), file);
return file.length();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

The copyURLToFile method returns void, so it’s slightly more work to return the length of the file, but so be it.

Feel free to use any of these methods. They all do the job, meaning the tests in the GitHub repository pass for all three of them. If it weren’t for the specter of checked exceptions, they’d all be a bit simpler, but none are too bad.

The Dropbox Complication

We face another complication: Dropbox shared links aren’t direct, and that’s a problem when you access them programmatically. Normal Dropbox links end in ?dl=0. In order for the links to work in Java, you need to change that ending to ?dl=1. Look at the headers and you’ll see that the latter version returns a 302 redirect, but includes the actual path to the file in a header. The default path returns a 200, but that kept giving me a FileNotFoundException, which was very annoying. Due to the magic of Stack Overflow and other sources, I was able to fix that by changing the links as stated.

Where’s Mockito?

You might be thinking, “This series is about downloading the awesome new book, Mockito Made Clearisn’t there any way to incorporate Mockito into the code?”

No, not as we’ve shown so far, mostly because one of the lessons of Mockito is that you’re not supposed to mock classes you don’t own. I didn’t want to mock the library classes, so what else could I mock?

In the third and final article in this series, I’ll introduce another class to print the downloaded file. Then I can mock that class and show off Mockito — don’t touch that dial!

As before, all code for this nonsense is in this GitHub repository.

Cover of Mockito Made Clear by Ken Kousen featuring limes and mint as a play on “mojito”
Cover of Mockito Made Clear by Ken Kousen

--

--

Kenneth Kousen
The Pragmatic Programmers

Author of the books Mockito Made Clear, Help Your Boss Help You, Kotlin Cookbook, Modern Java Recipes, Gradle Recipes for Android, and Making Java Groovy