How to get Git information in Java Application

In this post, we are going to see how to get the GIT information in a Java application.

To do that, we have to use the maven-git-commit-id-plugin plugin. Refer it @maven-git-commit-id-plugin

Now let’s see how we gonna do that.

1. Include the below plugin configuration in your Maven POM file.


  <plugin>
                <groupId>pl.project13.maven</groupId>
                <artifactId>git-commit-id-plugin</artifactId>
                <version>2.1.13</version>
                <executions>
                    <execution>
                        <id>populate-git-commit-information</id>
                        <goals>
                            <goal>revision</goal>
                        </goals>
                        <configuration>
                            <verbose>true</verbose>
                            <dateFormat>MM/dd/yyyy HH:mm:ss Z</dateFormat>
                            <abbrevLength>8</abbrevLength>
                            <generateGitPropertiesFile>true</generateGitPropertiesFile>
                            <generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
                        </configuration>
                    </execution>
                </executions>
                <configuration>
                    <dotGitDirectory>${project.basedir}/.git</dotGitDirectory>
                </configuration>
            </plugin> 
            

2. Then do maven install (mvn install). You can set the verbose as false if you don’t want to see the git commit id plugin log.

3. After the build is done, then go to target/classes folder. You will see git.properties contains the git related information. You can read this properties file in your application and use it.

Sample code is given below.


 static Properties properties = new Properties();
    static{
        try (InputStream is = this.class.getClassLoader()
                .getResourceAsStream("git.properties")) {
            properties.load(is);
        }
        catch (IOException io) {
            io.printStackTrace();
        }
    }

File Name: git.properties
File Path: /target/classes/git.properties
Sample properties are given below.



#Generated by Git-Commit-Id-Plugin
#Fri Nov 17 09:49:44 EST 2017
git.tags=
git.commit.id.abbrev=8ef4712a
git.commit.user.email=dkbalachandar@gmail.com
git.commit.message.full=Update Dockerfile
git.commit.id=8ef4712acd077d89b875c5bf67c20af1dbb8bb24
git.commit.id.describe-short=8ef4712-dirty
git.commit.message.short=Update Dockerfile
git.commit.user.name=Balachandar
git.build.user.name=Balachandar
git.commit.id.describe=8ef4712-dirty
git.build.user.email=dkbalachandar@gmail.com
git.branch=master
git.commit.time=09/19/2017 18\:25\:16 -0400
git.dirty=true
git.build.time=11/17/2017 09\:49\:44 -0500
git.remote.origin.url=https\://github.com/dkbalachandar/java-rest-docker.git


Refer my code @java-rest-docker

Advertisements

Curl Commands – Example

Curl command to fetch the response and HTTP status code


Command: 
curl -s -w '%{http_code}' -X GET ${SERVICE_URL}

s flag denotes silent output and w flag denotes write out the http_code in the output data.

Example:
curl -s -w '%{http_code}' -X GET 'https://openlibrary.org/api/books?bibkeys=ISBN:0201558025,LCCN:93005405&format=json'

Sample output:

curl -s -w '%{http_code}' -X GET 'https://openlibrary.org/api/books?bibkeys=ISBN:0201558025,LCCN:93005405&format=json'
{"LCCN:93005405": {"bib_key": "LCCN:93005405", "preview": "noview", "thumbnail_url": "https://covers.openlibrary.org/b/id/240726-S.jpg", "preview_url": "https://openlibrary.org/books/OL1397864M/Zen_speaks", "info_url": "https://openlibrary.org/books/OL1397864M/Zen_speaks"}, "ISBN:0201558025": {"bib_key": "ISBN:0201558025", "preview": "restricted", "thumbnail_url": "https://covers.openlibrary.org/b/id/135182-S.jpg", "preview_url": "https://archive.org/details/concretemathemat00grah_444", "info_url": "https://openlibrary.org/books/OL1429049M/Concrete_mathematics"}}200


To fetch the HTTP header alone.


curl -I -X GET 'https://openlibrary.org/api/books?bibkeys=ISBN:0201558025,LCCN:93005405&format=json'

To print the CURL version information along with the response.


curl -v -X GET 'https://openlibrary.org/api/books?bibkeys=ISBN:0201558025,LCCN:93005405&format=json'

To extract out the HTTP status code


curl -I -s -X GET 'https://openlibrary.org/api/books?bibkeys=ISBN:0201558025,LCCN:93005405&format=json' | grep 'HTTP/1.1' | awk '{print $2}'

How to use HK2 injection in Standalone Java Application

In this post, we are going to see how we can use the HK2 injection framework in a standalone Java application.

We need to include the below hk2 dependencies in our pom file. Here is my pom file.


 <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>
    <name>Hk2 Java standlaone application</name>
    <version>1.0.0</version>
    <artifactId>hk2-java-sample</artifactId>
    <groupId>com</groupId>
    <properties>
        <commons-lang3-v>3.6</commons-lang3-v>
        <maven-compiler-plugin-v>3.7.0</maven-compiler-plugin-v>
        <hk2-v>2.5.0-b36</hk2-v>
        <junit-v>4.12</junit-v>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons-lang3-v}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit-v}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.hk2</groupId>
            <artifactId>hk2</artifactId>
            <version>${hk2-v}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.hk2</groupId>
            <artifactId>hk2-junitrunner</artifactId>
            <version>${hk2-v}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-compiler-plugin-v}</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.glassfish.hk2</groupId>
                <artifactId>hk2-inhabitant-generator</artifactId>
                <version>${hk2-v}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate-inhabitants</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Consider that, we have a standalone application which will be used for adding, deleting, updating the employee details and the back end is a HashMap.

We have two interfaces(EmployeeService and EmployeeDao) and its implemetation classes(EmployeeServiceImpl and EmployeeDaoImpl) in this application.

To make use of HK2 injection, All our interfaces should be annotated with @Contract and all the implementation classes should be annotated with @Service.

Refer the below code to know how to do that.


package com.service;

import com.model.Employee;
import org.jvnet.hk2.annotations.Contract;

@Contract
public interface EmployeeService {
    public Employee fetch(String id);
    public Employee add(Employee employee);
    public void delete(String id);
    public Employee update(Employee employee);
}



package com.service.impl;

import com.dao.EmployeeDao;
import com.model.Employee;
import com.service.EmployeeService;
import org.jvnet.hk2.annotations.Service;

import javax.inject.Inject;
import java.util.UUID;

@Service
public class EmployeeServiceImpl implements EmployeeService {

    private EmployeeDao employeeDao;

    @Inject
    public EmployeeServiceImpl(EmployeeDao employeeDao){
        this.employeeDao = employeeDao;
    }

    @Override
    public Employee fetch(String id) {
        return employeeDao.fetch(id);
    }

    @Override
    public Employee add(Employee employee) {
        employee.setId(UUID.randomUUID().toString());
        return employeeDao.add(employee);

    }

    @Override
    public void delete(String id) {
        employeeDao.delete(id);
    }

    @Override
    public Employee update(Employee employee) {
        return employeeDao.update(employee);
    }
}



package com.dao;

import com.model.Employee;
import org.jvnet.hk2.annotations.Contract;

@Contract
public interface EmployeeDao {

    public Employee fetch(String id);
    public Employee add(Employee employee);
    public void delete(String id);
    public Employee update(Employee employee);
}


package com.dao.impl;

import com.dao.EmployeeDao;
import com.model.Employee;
import org.jvnet.hk2.annotations.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class EmployeeDaoImpl implements EmployeeDao {

    private static Map empLocalCache = new HashMap();

    public Employee fetch(String id) {
        return empLocalCache.get(id);
    }

    public Employee add(Employee employee) {
        empLocalCache.put(employee.getId(), employee);
        return empLocalCache.get(employee.getId());
    }

    public void delete(String id) {
        empLocalCache.remove(id);
    }

    public Employee update(Employee employee) {
        empLocalCache.put(employee.getId(), employee);
        return empLocalCache.get(employee.getId());
    }
}


Below is my main class which will use the Employee service class to perform add, update and delete operations with Employee Object.


package com;

import com.model.Employee;
import com.service.EmployeeService;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.ServiceLocatorUtilities;

public class Application {

    public static void main(String[] args) {

        ServiceLocator serviceLocator = ServiceLocatorUtilities.createAndPopulateServiceLocator();
        EmployeeService employeeService = serviceLocator.getService(EmployeeService.class);
        Employee employee = new Employee();
        employee.setFirstName("First Name");
        employee.setLastName("Last Name");

        System.out.println("Add employee details");
        Employee employeeAddResponse = employeeService.add(employee);
        System.out.println("The employee details after it has been added Employee: "+employeeAddResponse);

        System.out.println("Now fetch the employee details with ID");
        Employee employeeFetchResponse = employeeService.fetch(employee.getId());
        System.out.println("Employee :"+employeeFetchResponse);

        System.out.println("Now update the employee details");
        employee.setFirstName("Bala");
        employee.setLastName("Samy");
        Employee employeeUpdateResponse = employeeService.update(employee);
        System.out.println("Employee After Updation:"+employeeUpdateResponse);

        System.out.println("Now delete the employee details");
        employeeService.delete(employee.getId());
        System.out.println("Employee After Deletion:"+ employeeService.fetch(employee.getId()));

    }
}


To initialize all the HK2 dependencies, we have to call the ServiceLocatorUtilities.createAndPopulateServiceLocator() which will perform the initialization and return the ServiceLocator object.

We can make use of ServiceLocator object for getting the Object instance like below

EmployeeService employeeService = serviceLocator.getService(EmployeeService.class);

Before executing this, we need to do an important step which is to create the hk2 default file under our target/classes/META-INF folder. The hk2-inhabitant-generator plugin will help us to generate that file. Hence we need to include this plugin in our file. So we have to do maven install/package before we start executing our program.

The sample default file content will be like this,

#
# Generated on Thu Nov 09 17:04:57 EST 2017 by hk2-inhabitant-generator
#

[com.dao.impl.EmployeeDaoImpl]S
contract={com.dao.EmployeeDao}

[com.service.impl.EmployeeServiceImpl]S
contract={com.service.EmployeeService}

The output of my program is given below,



Add employee details
The employee details after it has been added Employee: Employee{id='f25b119a-820e-4162-af99-5c3580fe06f1', firstName='First Name', lastName='Last Name}
Now fetch the employee details with ID
Employee :Employee{id='f25b119a-820e-4162-af99-5c3580fe06f1', firstName='First Name', lastName='Last Name}
Now update the employee details
Employee After Updation:Employee{id='f25b119a-820e-4162-af99-5c3580fe06f1', firstName='Bala', lastName='Samy}
Now delete the employee details
Employee After Deletion:null


 

Refer the code @hk2-java-sample

 

 

Apache Commons CSV

Apache Commons CSV library is used for creating and reading CSV files.

In this post, we are going to see, how we can use this library with an example.

First thing is to include the below dependency in your maven pom file.


 <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-csv</artifactId>
            <version>1.5</version>
  </dependency>

Consider that the employee information is available in a CSV file called (employee.csv). The sample data are given below.

Id First Name Last Name Age
1 John Arthur 25
2 Sam Andrew 26
3 Hary Peter 26

Let’s see how we can read this CSV file in a Java Program with Commons CSV.



import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;

public class ReadCSVFileMain {

    public static void main(String[] args) throws IOException {

        String filePath = "/tmp/employee.csv";
        List employeeList = new ArrayList();
        Reader reader = null;
        try {
            String[] headers = { "Id", "First Name", "Last Name", "Age" };
            reader = new FileReader(filePath);
            Iterable records = CSVFormat.DEFAULT.withHeader(headers).withFirstRecordAsHeader().parse(reader);
            records.forEach(csvRecord -> {
                Employee employee = new Employee();
                employee.setId(Integer.valueOf(csvRecord.get("Id")));
                employee.setFirstName(csvRecord.get("First Name"));
                employee.setLastName(csvRecord.get("Last Name"));
                employee.setAge(Integer.valueOf(csvRecord.get("Age")));
                employeeList.add(employee);
            });
        }

        finally {
            if (reader != null) {
                reader.close();
            }
        }
        System.out.println("Parsed Content: employeeList::" + employeeList);
    }
}



public class Employee {

    private Integer id;
    private String firstName;
    private String lastName;
    private Integer age;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Employee{");
        sb.append("id=").append(id);
        sb.append(", firstName='").append(firstName).append('\'');
        sb.append(", lastName='").append(lastName).append('\'');
        sb.append(", age=").append(age);
        sb.append('}');
        return sb.toString();
    }

}


The above program is used to parse the employee.CSV file and maps the contents into Employee object. I have created a string array which holds the header names and using that while parsing the file.

The output will be like below,


Parsed Content: employeeList::[Employee{id=1, firstName='John', lastName='Arthur', age=25}, Employee{id=2, firstName='Sam ', lastName='Andrew', age=26}, Employee{id=3, firstName='Hary', lastName=' Peter', age=26}]

Now lets see how we can create a CSV file with an example.



import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class CreateCSVFileMain {

    public static void main(String[] args) throws IOException {

        String filePath = "/tmp/employee_new.csv";
        String[] headers = { "Id", "First_Name", "Last_Name", "Age" };
        List employeeList = new ArrayList();
        Employee employee1 = new Employee();
        employee1.setId(1234);
        employee1.setFirstName("fname1");
        employee1.setLastName("lname1");
        employee1.setAge(45);
        Employee employee2 = new Employee();
        employee2.setId(1234);
        employee2.setFirstName("fname1");
        employee2.setLastName("lname1");
        employee2.setAge(45);
        employeeList.add(employee1);
        employeeList.add(employee2);

        FileWriter writer = null;
        try {
            writer = new FileWriter(filePath);
            try (CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT.withHeader(headers))) {
                employeeList.forEach(employee -> {
                    try {
                        csvPrinter.printRecord(employee.getId(), employee.getFirstName(), employee.getLastName(),
                                               employee.getAge());
                    }
                    catch (IOException io) {
                        io.printStackTrace();
                    }
                });
            }
        }
        finally {
            if (writer != null) {
                writer.close();
            }
        }
    }
}


If we don’t want the header then, don’t specify with header.
The content of the created employee_new.csv file is given below,


Id,First_Name,Last_Name,Age
1234,fname1,lname1,45
1234,fname1,lname1,45

Apache Commons Digester – How to Substitute Variables in Configuration XML

The Apache Commons Digester is used for parsing a XML file and converting into java bean objects.

In this post, we are going to see how we can use the digester with an example.

Refer below my book configuration XML file. My objective is to parse this file, substitute the variables present in this file by environmental/hardcoded values and get the book config bean objects.

book.xml


<?xml version="1.0"?>
<books>
    <book>
        <title>Book1</title>
        <author>Author1</author>
        <price>${BOOK1_PRICE}</price>
    </book>
    <book>
        <title>Book2</title>
        <author>Author2</author>
        <price>${BOOK2_PRICE}</price>
    </book>
    <book>
        <title>Book3</title>
        <author>Author3</author>
        <price>${BOOK3_PRICE}</price>
    </book>
    <book>
        <title>Book4</title>
        <author>Author4</author>
        <price>${BOOK4_PRICE}</price>
    </book>
</books>

Refer below my Book Config Bean class. This class should have the corresponding XML fields and then getter/setter methods.

BookConfigBean.java


package com;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;

import java.io.Serializable;

public class BookConfigBean implements Serializable {

    private static final long serialVersionUID = -2405094408469829853L;

    private String title;

    private String author;

    private String price;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }
    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }


    @Override
    public boolean equals(Object o) {
        return EqualsBuilder.reflectionEquals(this, o);
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }
    
}

Refer below the BookConfigurationDigester class which has the parsing and variable substitution logic.

Refer below to know the logic behind in parsing the book XML file(load() method).

1. The first step is to create a Source Map and populate the key/value pairs into it. In this example, I have hardcoded all the values, but you can also retrieve the configuration from Environmental variables. Keep in mind that the format of the Source Map is .

2. Then, Create an instance of MultiVariableExpander and add the source map and symbol to it. Here we use ‘$’ as MARKER symbol.

3. Then create an instance of VariableSubstitutor and pass the MultiVariableExpander instance to it

4. Then create an instance of Digester and pass the VariableSubstitutor instance to it.

5. Parse the book XML file and use VariableSubstitutor object for doing the variable substitution.

BookConfigurationDigester.java


package com;

import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.Substitutor;
import org.apache.commons.digester3.substitution.MultiVariableExpander;
import org.apache.commons.digester3.substitution.VariableSubstitutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BookConfigurationDigester {

    private static Logger LOGGER = LoggerFactory.getLogger(BookConfigurationDigester.class);

    private static final String MARKER = "$";

    private List bookConfigBeans = new ArrayList();

    public void addBookConfigElement(BookConfigBean bean) {
        bookConfigBeans.add(bean);
    }

    public void load() {

        Map sourceMap = new HashMap();
        sourceMap.put("BOOK1_PRICE", "$10");
        sourceMap.put("BOOK2_PRICE", "$10");
        sourceMap.put("BOOK3_PRICE", "$20");
        sourceMap.put("BOOK4_PRICE", "$10");
        MultiVariableExpander mvExpander = new MultiVariableExpander();
        mvExpander.addSource(MARKER, sourceMap);
        Substitutor substitutor = new VariableSubstitutor(mvExpander);
        Digester digester = new Digester();
        try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("book.xml")) {
            digester.push(this);
            digester.addObjectCreate("books/book", BookConfigBean.class);
            digester.addSetNext("books/book", "addBookConfigElement");
            digester.addCallMethod("books/book/title", "setTitle", 0);
            digester.addCallMethod("books/book/author", "setAuthor", 0);
            digester.addCallMethod("books/book/price", "setPrice", 0);
            digester.setSubstitutor(substitutor);
            digester.parse(is);
        }
        catch (Exception e) {
            LOGGER.error("Error parsing configuration xml", e);
            throw new IllegalArgumentException("Error parsing configuration xml", e);
        }
        finally {
            digester.clear();
        }
    }

    public List getBookConfigBeans() {
        return bookConfigBeans;
    }
}

Here is my Junit test class.

BookConfigurationDigesterTest.java


package com;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class BookConfigurationDigesterTest {

    private static Logger LOGGER = LoggerFactory.getLogger(BookConfigurationDigester.class);

    @Test
    public void testBookConfiguration() {
        BookConfigurationDigester  bookConfigurationDigester = new BookConfigurationDigester();
        bookConfigurationDigester.load();
        List configBeanList = bookConfigurationDigester.getBookConfigBeans();
        LOGGER.debug("configBeanList:{}",configBeanList);
        assertNotNull(configBeanList);
        assertEquals(4 , configBeanList.size());
    }
}

Refer the output below.



com.BookConfigurationDigesterTest,testBookConfiguration
2017-10-18T11:57:04.967-0400 [main] DEBUG c.BookConfigurationDigester - configBeanList:[com.BookConfigBean@1a968a59[title=Book1,author=Author1,price=$10], com.BookConfigBean@204f30ec[title=Book2,author=Author2,price=$10], com.BookConfigBean@e25b2fe[title=Book3,author=Author3,price=$20], com.BookConfigBean@754ba872[title=Book4,author=Author4,price=$10]]

Refer the code @ xml-digester

How to use StrSubstitutor

StrSubstitutor is a class available in the commons-lang package which is used to Substitutes variables within a string by values.

In this post, we are going to see how we can use this StrSubstitutor with few examples.

Assume that you have a string like this “LANG: ${LANG}”. Now you want to replace ${LANG} by env variable value.

You can simply call the System.getenv method for this key and get the value and replace it in this string content. But what happens if we want to do the same kind of substitutions for many string expressions. During that time the class StrSubstitutor class comes in handy.

In the below example, I have created an instance of StrSubstitutor class and passing an instance of EnvLookUp which is a subclass of StrLookup class. The StrLookup class is used to lookup a String key to a String value. Here in this, we want to look up the value from the environmental variables. Hence, I have created a class which extends the StrLookup and override the lookup method with my own implementation.

So when we call the “replace” method of StrSubstitutor(envPropertyResolver) with the sample string, then it will go and look up the env variables and find the value, then substitute the value in the string expression.

StrSubstitutorMain.java



import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.StrLookup;
import org.apache.commons.lang3.text.StrSubstitutor;

public class StrSubstitutorMain {

    private static final StrSubstitutor envPropertyResolver = new StrSubstitutor(new EnvLookUp());

    public static void main(String[] args) {

        String sample = "LANG: ${LANG}";
        //LANG: en_US.UTF-8
        System.out.println(envPropertyResolver.replace(sample));

    }

    private static class EnvLookUp extends StrLookup {

        @Override
        public String lookup(String key) {
            String value = System.getenv(key);
            if (StringUtils.isBlank(value)) {
                throw new IllegalArgumentException("key" + key + "is not found in the env variables");
            }
            return value;
        }
    }
}


Tne above one is a very simple example, Now let’s see how we can use this to substitute the variables found in a property file.

Here is our properties file.

test-prop.properties


java.home=${JAVA_HOME}
java.version=${java.version}
os.name=${os.name}

Now, We have to modify our code to read the above properties file and substitute the variables with the env variables.

Refer below the updated code. Once we load the properties, then we have to call the replaceAll method of Properties class and use our StrSubstitutor (envPropertyResolver) over there. So for each key/value pair, the StrSubstitutor’s replace method would be called and the corresponding value would be passed and it will replace all the variables found in property value.

StrPropertySubstitutor.java



import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.StrLookup;
import org.apache.commons.lang3.text.StrSubstitutor;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class StrPropertySubstitutor {

    private static final StrSubstitutor envPropertyResolver = new StrSubstitutor(new EnvLookUp());

    public static void main(String[] args) {

        Properties properties = new Properties();;
        try (InputStream is =
                StrPropertySubstitutor.class.getClassLoader().getResourceAsStream("test-prop.properties")) {
            properties.load(is);
            properties.replaceAll((k, v) -> envPropertyResolver.replace((String) v));
        }
        catch (IOException io) {
            io.printStackTrace();
        }

        properties.forEach( (k, v) -> System.out.println("key:: " + k + " value: "+v));

    }

    private static class EnvLookUp extends StrLookup {

        @Override
        public String lookup(String key) {
            String value = System.getenv(key);
            if (StringUtils.isBlank(value)) {
                throw new IllegalArgumentException("key " + key + " is not found in the env variables");
            }
            return value;
        }
    }

}


Output:

The output will be like below,


key::os.name value: Linux
key::java.home value: /usr/lib/jvm/java-8-oracle
key::java.version value: 1.8.0_144

JAX RS: Sub Resources

In this post, I am going to explain about the JAX RS Subresources with an example.

JAX RS Resource classes can partially process a request and provide an another “sub” resource object that can process the remainder of the request.  Those classes will be called as Subresources.

Subresources are used to represent the relationships between the resources. If a resource is part of another resource, then we can use Subresources. An example for this is Employee and Address resources. Here the Employee object contains the Address object.

If you want to design a readable API, then Subresources is a perfect choice.

If you want to design a flexible API, then you should use fewer Subresources.

The Subresources can’t be used separately and also if you have more level, then its very hard to remember the URL path.

Now let’s see it with an example, Assume that we have a JAX RS web service which returns the “Employee” and “Address” details. Here we can design the Employee as root resource and the Address as Subresource.

Here are the value classes for Employee and Address.

Employee.java



public class Employee {

    private String id;

    private String firstName;

    private String lastName;

    private Address address;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Employee{");
        sb.append("id='").append(id).append('\'');
        sb.append(", firstName='").append(firstName).append('\'');
        sb.append(", lastName='").append(lastName).append('\'');
        sb.append(", address=").append(address);
        sb.append('}');
        return sb.toString();
    }
}

Address.java



public class Address {

    private String streetAddress;

    private String city;

    private String state;

    private String zip;

    public String getStreetAddress() {
        return streetAddress;
    }

    public void setStreetAddress(String streetAddress) {
        this.streetAddress = streetAddress;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getZip() {
        return zip;
    }

    public void setZip(String zip) {
        this.zip = zip;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Address{");
        sb.append("streetAddress='").append(streetAddress).append('\'');
        sb.append(", city='").append(city).append('\'');
        sb.append(", state='").append(state).append('\'');
        sb.append(", zip='").append(zip).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

The below things should be kept in mind while creating the Subresources.

  1. Sub Resource class should not have the @Path annotation. In our example, AddressResource is the Sub Resource class and it does contain the @Path annotation.
  2. Subresource method should not have the HTTP method and it should return an object of the subresource class. In this example, getEmployeeAddress is the subresource method and it returns the AddressResource Object

Refer below our EmployeeResource class. I use a HashMap to store the Employee details as this is just an example. The getEmployeeData() method return an employee object and the createEmployee create an employee object.

The method getEmployeeAddress() returns the AddressResource. So when we access ‘/api/employee/{ID}/address’ the JAX-RS runtime will map the request to AddressResource class and call the appropriate method.

EmployeeResource.java


import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

@Path("employee")
public class EmployeeResource {

    private static Map empLocalCache = new HashMap();

    static{
        //For testing purpose only.
        createTestData();
    }

    public static void createTestData() {
        Employee employee = new Employee();
        employee.setId("123");
        employee.setFirstName("test");
        employee.setLastName("test");
        Address address = new Address();
        address.setStreetAddress("streetAddress");
        address.setCity("Columbus");
        address.setState("OH");
        address.setZip("43202");
        employee.setAddress(address);
        empLocalCache.put("123", employee);
    }

    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Employee getEmployeeData(@PathParam("id") String id) {
        return empLocalCache.get(id);
    }

    @Path("{id}/address")
    public AddressResource getEmployeeAddress(@PathParam("id") String id) {
        return new AddressResource(id);
    }

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Employee createEmployee(Employee employee) {
        employee.setId(UUID.randomUUID().toString());
        empLocalCache.put(employee.getId(), employee);
        return employee;
    }

    public class AddressResource {

        private String id;

        AddressResource(String id){
            this.id = id;
        }

        @GET
        @Produces(MediaType.APPLICATION_JSON)
        public Address getAddress() {
            Optional optional = Optional.ofNullable(empLocalCache.get(id));
            return optional.isPresent()? optional.get().getAddress(): null;
        }
    }
}

Testing
The first step is to create an employee details with Address data. So we have to call /api/employee(POST) method with the employee data in the body.


URL: /api/employee
Method: POST
Header: Content-Type:application/json
Body:
{
    "firstName": "test",
    "lastName": "test",
    "address": {
        "streetAddress": "streetAddress",
        "city": "Columbus",
        "state": "OH",
        "zip": "43202"
    }
}

Response:

{
    "id": "cfd59155-a4cf-4fdd-83ae-5198c506d56a",
    "firstName": "test",
    "lastName": "test",
    "address": {
        "streetAddress": "streetAddress",
        "city": "Columbus",
        "state": "OH",
        "zip": "43202"
    }
}

Next step is to get the address of this employee with sub resource method.


URL: /api/employee/cfd59155-a4cf-4fdd-83ae-5198c506d56a/address
Method: GET
Response:

{
    "streetAddress": "streetAddress",
    "city": "Columbus",
    "state": "OH",
    "zip": "43202"
}