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

Advertisements

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"
}

JAX-RS: How to use BeanParam

BeanParam annotation is available in JAX-RS 2.0. It allows us to aggregate the parameters into java bean.

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

Below is my Resource class.

I have specified the annotation @BeanParam for the SearchParamBean class. Hence the query parameters those are passed to this endpoint will be mapped to the SearchParamBean model class.

TestResource.java


import javax.ws.rs.BeanParam;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("test")
public class TestResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("search")
    public Response performSearchWithMultiParameters(@BeanParam SearchParamBean searchParamBean) {
        return Response.ok().entity(searchParamBean).type(MediaType.APPLICATION_JSON).build();
    }

}

Below is my BeanParam class.

I have given the necessary annotation for all the fields of this class. I have used QueryParam here. Hence the query parameters ‘query’, ‘sort’ and ‘filter’ are mapped to fields of SearchParamBean class.

We can also use PathParam, HeaderParam, CookieParam, MatrixParam and FormParam.

SearchParamBean.java


import javax.ws.rs.QueryParam;

public class SearchParamBean {

    @QueryParam("query")
    public String query;

    @QueryParam("sort")
    public String sort;

    @QueryParam("filter")
    public String filter;

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("SearchParamBean{");
        sb.append("query='").append(query).append('\'');
        sb.append(", sort='").append(sort).append('\'');
        sb.append(", filter='").append(filter).append('\'');
        sb.append('}');
        return sb.toString();
    }

}

Here is my request and response.

URL: /api/test/search?query=test&sort=test&filter=test
Response:


 {
query: "test",
sort: "test",
filter: "test"
}

Most of the time, we don’t need to use this annotation instead we can use Jackson to map the query parameters directly to a bean class.

For example, When the service accepts more than one query parameters and we want to do any validation and need to use those values further in service layer or DAO layer, then we can make use of this annotation.

Git: Pushing remote branch to Gerrit

If you use the Gerrit as your code review tool, then follow the below steps for pushing the remote branch to Gerrit.


1. Create a local branch to track for the Remote Branch. Run the below command for the same. 

 git branch LOCAL_BRANCH_NAME origin/REMOTE_BRANCH_NAME 

2. Then try to merge your local branch with master branch to avoid any conflicts. This will be helpful when we merge the master with REMOTE_BRANCH_NAME in the future.

 git merge origin/master 

3. Make the necessary changes in the code and run the below commands to add and commit it. 

 git add . 
 git commit -m "COMMIT_MESSAGE" 

4. Run the below command to push the code to gerrit for the remote branch 

git push gerrit LOCAL_BRANCH_NAME:refs/for/REMOTE_BRANCH_NAME 

How to get the name of the enclosing method

If you ever want to get the name of the enclosing method for logging purpose, then we can follow any one of the below approaches.

Get the method name from the exception stack trace


   private void methodNameFromExceptionTrace() {
        System.out.println("Method Name with Exception class:: " + new Exception().getStackTrace()[0].getMethodName());
    }

The main drawback in the above one is the unnecessary creation of Exception object.

Get the method name from the Thread



    private void methodNameFromCurrentThread() {
 Stream.of(Thread.currentThread().getStackTrace())
       .forEach(System.out::println);
 System.out.println("Method Name with Thread :: " + Thread.currentThread().getStackTrace()[1].getMethodName());     
    }

Refer below the entire code.



import java.util.stream.Stream;

public class Main {

    public static void main(String[] args) {
        Main main = new Main();
        // Get the method name From Exception trace
        main.methodNameFromExceptionTrace();
        // Get the method name From Thread
        main.methodNameFromCurrentThread();
      // Get the method name From Exception trace
        main.getMethodNameWithException();
    }

    private void methodNameFromExceptionTrace() {
        System.out.println("Method Name with Exception class:: " + new Exception().getStackTrace()[0].getMethodName());
    }

    private void methodNameFromCurrentThread() {
Stream.of(Thread.currentThread().getStackTrace()).forEach(System.out::println);
        System.out.println("Method Name with Thread :: " + Thread.currentThread().getStackTrace()[1].getMethodName());

    }

    private void getMethodNameWithException() {
        try {
            throw new Exception("Test");
        }
        catch (Exception e) {
            System.out.println("Exception happened in " + e.getStackTrace()[0].getMethodName());
        }
    }
}

Here in the getMethodNameWithException method, we are throwing an exception and in the catch block, we use that object to get back the method name. But if you call some other method and that method throws that exception, then the e.getStackTrace()[0] will be different so you need to change the index which should be 1. Refer the below example.



 private void getMethodNameWithException() {
        try {
            testMethod();
        }
        catch (Exception e) {
            Stream.of(e.getStackTrace()).forEach(System.out::println);
            System.out.println("Exception happened in " + e.getStackTrace()[1].getMethodName());
        }
    }
    private void testMethod() throws Exception {
        throw new Exception("Test");
    }