Spring Boot File Download via Angular7 PART 01

Create a Simple Spring Boot App.

As a pre-requisite, we need to implement a service with file download feature.

Video guide of the spring-boot process



 

So I composed the below class, which is a complete spring boot application, with file download functionality at its simplest form.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package snmaddula.quicktrick.ng;

import static java.nio.file.Files.readAllBytes;
import static java.nio.file.Paths.get;

import javax.servlet.http.HttpServletResponse;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class FileDownloadApp {

@GetMapping("/download")
public void downloadFile(String fileName, HttpServletResponse res) throws Exception {
res.setHeader("Content-Disposition", "attachment; filename=" + fileName);
res.getOutputStream().write(contentOf(fileName));
}

private byte[] contentOf(String fileName) throws Exception {
return readAllBytes(get(getClass().getClassLoader().getResource(fileName).toURI()));
}

public static void main(String[] args) {
SpringApplication.run(FileDownloadApp.class, args);
}
}

The only dependency that is needed for this boot application is spring-boot-starter-web, add it to your pom.xml

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

For testing purpose, we need some files to be available for download.
So place some files in your application classpath under src/main/resources like below: (.xls, .xlsx, .pdf, etc)

With this we are done with the server side / spring boot side of the application. Now, let us write the Angular side implementation.

Java Quick Trick - Decode JWT Payload

A quick guide on programmatically decoding a JSON Web Token’s payload.

JWT basically consists of three parts separated by dots (.)

  • Header
  • Payload
  • Signing Key

The Header basically consists the type of the token which is JWT and the algorithm that is used to sign this token.

The second part of the token is the payload, which consists of the claims i.e. the information about the user.

The Base64 URL encoded form of the Header and Payload respectively form the 1st and 2nd parts of Json Web Token.

Video guide of the process



 

Steps involved:

Add commons-codec dependency from Apache in pom.xml

1
2
3
4
5
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>

1
String token = "<YOUR JWT TOKEN>";

Split the token based on period . and extract the payload part from the token.

1
String payload = token.split("\\.")[1];

Use Base64 utility to decode the payload.

1
String payloadValue = new String (Base64.decodeBase64(payload));

If you want to use a specific charset like UTF-8, you can use the below snippet.

1
2
3
4
5
try {
String payloadValue = new String(Base64.decodeBase64(payload), "UTF-8");
}catch(UnsupportedEncodingException ex) {
System.err.println(ex.getMessage());
}

Note: Make sure to import the Base64 from the package org.apache.commons.codec.binary.Base64.

Java Quick Trick - Transform JDBC ResultSet to Excel WorkBook

A quick guide on generating an excel workbook with the output from an SQL query.

Video guide of the process



 

Follow the below steps

Add Apache POI dependencies to your pom.xml

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.0.1</version>
</dependency>

<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.0.1</version>
</dependency>

Create a workbook and add the header row.

1
2
3
4
try (Workbook book = new XSSFWorkbook()) {
Sheet sheet = book.createSheet();
Row header = sheet.createRow(0);
}

Extract columns from the ResultSet.

1
2
3
4
5
ResultSetMetaData rsmd = rs.getMetaData();
List<String> columns = new ArrayList<String>() {{
for (int i = 1; i <= rsmd.getColumnCount(); i++)
add(rsmd.getColumnLabel(i));
}};

Populate the header row with the extracted column names.

1
2
3
4
for (int i = 0; i < columns.size(); i++) {
Cell cell = header.createCell(i);
cell.setCellValue(columns.get(i));
}

Extract the rows from the ResultSet and populate the workbook.

1
2
3
4
5
6
7
8
9
int rowIndex = 0;
while (rs.next()) {
Row row = sheet.createRow(++rowIndex);
for (int i = 0; i < columns.size(); i++) {
Cell cell = row.createCell(i);
// if you have complex values like Date, then use cell formatter.
cell.setCellValue(Objects.toString(rs.getObject(columns.get(i)), ""));
}
}

Writing the excel workbook to filesystem.

1
2
3
try (FileOutputStream fos = new FileOutputStream("FILE_PATH")) {
book.write(fos);
}

You can download this entire class - ResultSetToExcel.java, which is readily usable.

That’s all.

If you have any queries, please do reach out to me via email / phone.

Tuning JaCoCo for PowerMock Coverage

Jacoco is widely preferred for on-the-fly instrumentation, however it cannot extend the same support when it comes to powermock, as powermockdoes dynamic classfile transformation which conflicts with Jacoco.

To overcome this issue, we can use an offline instrumentation i.e. the classes can be pre-instrumented with JaCoCo before powermock kicks in.

Configuration Steps

Here are the steps involved in configuring Offline Instrumentation of JaCoCo:

JaCoCo Agent

Unlike with on-the-fly instrumentation offline instrumented classes get a direct dependency on the JaCoCo runtime.

Add jacoco-agent dependency to our pom.xml, so that it will be available on the classpath and accessible by the instrumented classes.

1
2
3
4
5
6
<dependency>
<groupId>org.jacoco</groupId>
<artifactId>org.jacoco.agent</artifactId>
<version>${jacoco.version}</version>
<classifier>runtime</classifier>
</dependency>

JaCoCo Maven plugin

Add the below plugin configuration to the plugins section of your pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<id>default-instrument</id>
<goals>
<goal>instrument</goal>
</goals>
</execution>
<execution>
<id>default-restore-instrumented-classes</id>
<goals>
<goal>restore-instrumented-classes</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>${project.build.directory}/coverage.exec</dataFile>
</configuration>
</execution>
</executions>
<configuration>
<excludes>
<exclude>com/somepack/subpack/**/*</exclude> <!-- to exclude any package -->
</excludes>
</configuration>
</plugin>

Surefire Maven plugin

Add the below configuration to provide coverage information to surefire.

1
2
3
4
5
6
7
8
9
10
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<systemPropertyVariables>
<jacoco-agent.destfile>${project.build.directory}/coverage.exec</jacoco-agent.destfile>
</systemPropertyVariables>
</configuration>
</plugin>

PowerMock Dependencies

Add the below powermock dependencies to your pom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
</dependency>

<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>${powermock.version}</version>
</dependency>

<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
</dependency>

Version Compatibility

One last thing is version Compatibility. Make sure you use compatible versions.

For Jacoco Offline Instrumentation, use powermock 1.6.6 or greater.

1
2
3
<jacoco.version>0.7.7.201606060606</jacoco.version>
<powermock.version>1.7.1</powermock.version>
<junit.version>4.12</junit.version>