Implementing Basic and Advanced Search using Sencha ExtJS 6.5, Spring MVC, Spring Boot, RESTful API and MongoDB Example

Implementing Basic and Advanced Search using Sencha ExtJS 6.5, Spring MVC, Spring Boot, RESTful API and MongoDB Example

In this tutorial we will discuss how to implement basic and advanced search techniques in MongoDB using ExtJS 6.5 with Spring Boot, Spring Data and Spring MVC REST API backend. The advanced search user interface (UI) will use logical operators and build a JSON object which contains the search field name, boolean or logical operator and the search value. You are then able to add many more additional fieldSets to search for. The created JSON object will be used in the RESTful API POST call. This tutorial will also be using ExtJS Grid component for our search results and we will discuss User Interface (UI) components and how they integrate with our RESTful API backend. We have another tutorial which focuses on the Front-End User Interface (UI) components using ExtJS version 6.5.x and Sencha Cmd (Command Line Interface).

The focus of this project is create a similar and fully working application as was created using AngularJS / GridUI but with an ExtJS implementation. The look and feel will be slightly different but the functionality will be pretty close to identical. If you would like to look at my reference implementation, please refer to my original tutorial “Implementing Basic and Advanced Search using Angular Material Design, Grid-UI, Spring MVC REST API and MongoDB Example”

What is Covered in this Tutorial ?

  1. What is ExtJS ?
  2. What is Spring Boot ?
  3. Getting Started
  4. Complete Project Overview
  5. RESTful Web Service End Points
  6. Spring Boot Application
  7. Application Properties
  8. The Employee Model
  9. Our Controller Class
  10. EmployeeRepository Data Access Object (DAO) for MongoDB
  11. The SelectionCriteria Class
  12. The Field Class
  13. JSON Date/Time Serializer
  14. LogBack Configuration File
  15. Spring Boot Actuator
  16. MongoDB Employee Collection
  17. Testing out the Web Services

Although our Spring MVC RESTful API backend code supports the other three CRUD operations (Create, Update and Delete) our UI application will NOT focus on that aspect in this tutorial. I have focused the ExtJS User Interface (UI) on the search aspects alone. I have tested out these operations using POSTMAN and have provided screenshots below.

In our advanced tab, I have added the ability to add rows dynamically to the form. ExtJS supports dynamic form creating using Ext.Container and insert(index, item) method which will allows child components to be added at a given index. We will discuss these in more details in our other tutorial.

extjsDirectoryAdvSearch

What is ExtJS ?

ExtJS is one of the premiere Javascript frameworks available for building data intensive, cross platform web applications. ExtJS features more than 100 UI components are high-performance and have been pre-tested. These include Buttons, Panels, Forms, Tabs, Grids, Trees, Charts, Calendar and many many more…

ExtJS started it’s life under the BSD license and gradually changed to a more restrictive LGPL license model. Over time the authors gradually modified the license terms and nowadays ExtJS is released under the dual license structure of GNU General Public License version 3 (GPLv3) or the proprietary commercial pay per seat license.

Advantages of ExtJS

  • ExtJS has one of the largest component libraries of any javascript framework with hundreds of components
  • It has been battle tested for over 10 years and used in thousands of applications worldwide
  • ExtJS includes rich framework for building web based applications using MVC and MVVM.
  • Continued legacy browser support
  • ExtJS makes it easy to develop web based applications without the need to learn Typescript
  • ExtJS is supported by a suite of excellent design tools developed by Sencha

What is Spring Boot ?

Spring Boot was created by the folks at Pivotal to simply the configuration in Spring-based applications in order to get you up and running in minimal time and minimum effort. In order to accomplish this goal, Spring Boot takes an opinionated view and pre-configures the default settings for Spring Boot applications.

Spring Boot Features

  • Ability to easily create stand-alone Spring based applications
  • Ability to Embed Tomcat, Jetty or Undertow application web servers
  • Automatically configure Spring whenever possible
  • Provides the ability to monitor and manage your application with professional-level metrics and health checks
  • No requirement for XML configurations

Spring Boot / Spring MVC Search Application

Getting Started

In order to run this tutorial yourself, you will need the following:

  • Java JDK 1.7 or greater (although I used JDK 1.8)
  • Favorite IDE Spring Tool Suite (STS), Eclipse IDE or NetBeans (I happen to be using STS because it comes with a Tomcat server built-in)
  • Spring Boot open source solution for creating stand-alone spring applications using opinionated configuration parameters for no-fuss/quick starting applications.
  • ExtJS – ExtJS is one of the premiere Javascript frameworks available for building data intensive, cross platform web applications.
  • MongoDB is an open-source document database designed for ease of development and scaling.
  • Maven is an open-source project management and comprehension tool designed for assisting in the application build lifecycle. Maven simplifies the build process by enforcing standards and favoring convention over configuration.

Required Libraries

For this project we are using Maven, however to make things easier for you, please refer to the following list of libraries.

spring-boot-starter-web-1.5.2.RELEASE.jar
spring-boot-starter-1.5.2.RELEASE.jar
spring-boot-1.5.2.RELEASE.jar
spring-boot-autoconfigure-1.5.2.RELEASE.jar
spring-boot-starter-logging-1.5.2.RELEASE.jar
logback-classic-1.1.11.jar
logback-core-1.1.11.jar
jul-to-slf4j-1.7.24.jar
log4j-over-slf4j-1.7.24.jar
spring-core-4.3.7.RELEASE.jar
snakeyaml-1.17.jar
spring-boot-starter-tomcat-1.5.2.RELEASE.jar
tomcat-embed-core-8.5.11.jar
tomcat-embed-el-8.5.11.jar
tomcat-embed-websocket-8.5.11.jar
hibernate-validator-5.3.4.Final.jar
validation-api-1.1.0.Final.jar
jboss-logging-3.3.0.Final.jar
classmate-1.3.3.jar
jackson-databind-2.8.7.jar
jackson-annotations-2.8.0.jar
jackson-core-2.8.7.jar
spring-web-4.3.7.RELEASE.jar
spring-aop-4.3.7.RELEASE.jar
spring-beans-4.3.7.RELEASE.jar
spring-context-4.3.7.RELEASE.jar
spring-webmvc-4.3.7.RELEASE.jar
spring-expression-4.3.7.RELEASE.jar
spring-boot-starter-actuator-1.5.2.RELEASE.jar
spring-boot-actuator-1.5.2.RELEASE.jar
commons-pool-1.6.jar
commons-lang-2.6.jar
spring-boot-starter-data-mongodb-1.5.2.RELEASE.jar
mongodb-driver-3.4.2.jar
bson-3.4.2.jar
mongodb-driver-core-3.4.2.jar
spring-data-mongodb-1.10.1.RELEASE.jar
spring-tx-4.3.7.RELEASE.jar
spring-data-commons-1.13.1.RELEASE.jar
slf4j-api-1.7.24.jar
jcl-over-slf4j-1.7.24.jar
spring-boot-starter-thymeleaf-1.5.2.RELEASE.jar
thymeleaf-spring4-2.1.5.RELEASE.jar
thymeleaf-2.1.5.RELEASE.jar
ognl-3.0.8.jar
javassist-3.21.0-GA.jar
unbescape-1.1.0.RELEASE.jar
thymeleaf-layout-dialect-1.4.0.jar
groovy-2.4.9.jar
mongo-java-driver-3.4.2.jar
jstl-1.2.jar
junit-3.8.1.jar

Complete Project Overview

I have added the project overview to give you a full view of the structure and show you all files contained in this sample project.

As you can see from the tree view below, Maven standardizes the Java directory structure such that all Java sources will be compiled from src/main/java. Additionally, resources folder will reside in src/main/resources and all test case source files will reside in the src/test/java folder.

extjsDirectorySearch_proj_struct

Spring Maven Dependencies (pom.xml)

For this application we will be using Maven to manage our build. Maven uses a pom.xml file to hold all of our project dependencies. POM stands for “Project Object Model“. This pom.xml file will contain all of the necessary information about our project as well as the configurations for any plugins uses during our build process.

<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>
 <groupId>com.avaldes</groupId>
 <artifactId>directoryservices</artifactId>
 <packaging>war</packaging>
 <version>0.0.1-SNAPSHOT</version>
 <name>EmployeeDirectoryBoot Maven Webapp</name>
 <url>http://maven.apache.org</url>
 
 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.2.RELEASE</version>
 </parent>
 <dependencies>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>

  <dependency>
   <groupId>commons-pool</groupId>
   <artifactId>commons-pool</artifactId>
   <version>1.6</version>
  </dependency>

  <dependency>
   <groupId>commons-lang</groupId>
   <artifactId>commons-lang</artifactId>
   <version>2.6</version>
  </dependency>
  
  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-mongodb</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
   </dependency>

  <dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongo-java-driver</artifactId>
    <version>3.4.2</version>
  </dependency>
  
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>jstl</artifactId>
  </dependency>

  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>3.8.1</version>
   <scope>test</scope>
  </dependency>
 </dependencies>

 <build>
    <finalName>directoryservices</finalName>
     <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <!-- <version>3.0</version> -->
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-war-plugin</artifactId>
        <configuration>
         <failOnMissingWebXml>false</failOnMissingWebXml>    
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
        <executable>true</executable>
       </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <configuration>
          <failOnMissingWebXml>false</failOnMissingWebXml>
          <archive>
       <manifest>
        <addDefaultImplementationEntries>
          true
        </addDefaultImplementationEntries>
        <addDefaultSpecificationEntries>
          true
        </addDefaultSpecificationEntries>
       </manifest>
       <manifestEntries>
        <Implementation-Build>
          ${buildNumber}
        </Implementation-Build>
        <Build-Timestamp>
          ${maven.build.timestamp}
        </Build-Timestamp>
       </manifestEntries>
     </archive>
    </configuration>
      </plugin>
    </plugins>
  </build>
</project>

RESTful Web Service End Points

In keeping with RESTful endpoint standards I am providing the ability to use either Path Parameters or Request Parameters with all of the CRUD endpoint URIs.

/directoryservices/v1/employees/{id} will be available for QUERY using the GET method.
/directoryservices/employees will be available for ADD using the POST method.
/directoryservices/employees/{id} will be available for UPDATE using the PUT method.
/directoryservices/employees/{id} will be available for DELETE using the DELETE method.

#URIMethodDescription
1/directoryservices/statusGETDisplays the standard status message.
2/directoryservices/v1/employeesGETRetrieves all employee objects from MongoDB returning them as a JSON array or XML object.
3/directoryservices/v1/employees/{id}GETRetrieves an employee given the ID, returning the employee as JSON or XML object.
4/directoryservices/v1/search/stdGETRetrieves all employee objects from MongoDB that match either the firstName, lastName or both, returning matches as a JSON array or XML document.
5/directoryservices/v1/search/advGETRetrieves employee objects from MongoDB that match selection criteria returning matches as a JSON array or XML document.
6/directoryservices/v1/employeesPOSTInserts the employee into our MongoDB data store based on the contents of the JSON or XML object
7/directoryservices/v1/employees/{id}PUTUpdates employee in our MongoDB data store based on the contents of the JSON or XML object
8/directoryservices/v1/employees/{id}DELETEDelete employee in our MongoDB data store based on the ID

Spring Boot Application (EmployeeDirectoryApplication.java)

The Spring Boot entry point follows the same conventions as a standard Java application with the main method. This main method in turn, starts the Spring application by running SpringApplication.run. SpringApplication bootstraps out application and starts Spring which in turns starts the embedded Tomcat, Jetty or Undertow applications web servers.

package com.avaldes;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class EmployeeDirectoryApplication {

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

Our Controller Class (DirectoryServicesController.java)

Our DirectoryServicesController class is the main class that contains all web service mapping end points defined in our table above. The @RestController annotation indicates that this particular class is playing the role of a controller.

package com.avaldes.tutorial;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.avaldes.dao.EmployeeRepository;
import com.avaldes.model.Employee;
import com.avaldes.model.Employees;
import com.avaldes.model.SelectionCriteria;
import com.avaldes.model.StatusMessage;

/**
 * Handles requests for the application home page.
 */
@RestController
public class DirectoryServicesController {

 private static final Logger logger = (Logger) LoggerFactory
   .getLogger(DirectoryServicesController.class);
 public static final String APPLICATION_JSON 
                        = "application/json; charset=UTF-8";
 public static final String APPLICATION_XML 
                        = "application/xml; charset=UTF-8";
 public static final String APPLICATION_HTML = "text/html";

 @Autowired
 private EmployeeRepository employeeRepository;

 /**
  * Simply selects the home view to render by returning its name.
  * 
  */
 @RequestMapping(value = "/status", method = RequestMethod.GET, 
                            produces = APPLICATION_HTML)
 public @ResponseBody String status() {
  logger.info("Inside status() method...");
  return "application OK...";
 }

 @RequestMapping(value = "/error", method = RequestMethod.GET)
 public @ResponseBody String getErrorMessage() {
  return "error";
 }

 @RequestMapping(value = "/v1/employees", method = RequestMethod.GET, 
                            produces = MediaType.APPLICATION_JSON_VALUE)
 public ResponseEntity<?> getAllEmployeesJson() {
  logger.info("Inside getAllEmployees() method...");

  List<Employee> allEmployees = employeeRepository
    .getAllEmployees();

  return new ResponseEntity<>(allEmployees, HttpStatus.OK);
 }

 @RequestMapping(value = "/v1/employees", method = RequestMethod.GET, 
                            produces = MediaType.APPLICATION_XML_VALUE)
 public ResponseEntity<?> getAllEmployeesXml() {
  logger.info("Inside getAllEmployees() method...");

  Employees allEmployees = new Employees(employeeRepository
    .getAllEmployees());

  return new ResponseEntity<>(allEmployees, HttpStatus.OK);
 }
 
 @RequestMapping(value="/v1/employees/{id}", method=RequestMethod.GET)
 public ResponseEntity<?> getEmployeeById(
   @PathVariable(value = "id", required = false) String id) {

  if (id == null || id.isEmpty()) {
   StatusMessage statusMessage = new StatusMessage();
   statusMessage.setStatus(HttpStatus.BAD_REQUEST.value());
   statusMessage.setMessage("'id' is a required field for this request");

   if (logger.isInfoEnabled()) {
    logger.info("'id' is a required field for this request");
   }

   return new ResponseEntity<>(statusMessage, HttpStatus.BAD_REQUEST);
  }
  
  Employee employee = employeeRepository.getEmployeeById(id);
  
  if (employee == null) {
   StatusMessage statusMessage = new StatusMessage();
   statusMessage.setStatus(HttpStatus.NOT_FOUND.value());
   statusMessage.setMessage("'id' is a required field for this request");

   if (logger.isInfoEnabled()) {
    logger.info("Inside getEmployeeById, ID: " + id + ", NOT FOUND!");
   }

   return new ResponseEntity<>(statusMessage, HttpStatus.NOT_FOUND);   
  }
  
  if (logger.isInfoEnabled()) {
   logger.info("Inside getEmployeeById, returned: "+employee.toString());
  }

  return new ResponseEntity<>(employee, HttpStatus.OK);
 }
 
 @RequestMapping(value = "/v1/search/std", method = RequestMethod.POST, 
                            produces = MediaType.APPLICATION_JSON_VALUE)
 public ResponseEntity<?> standardSearchJson(
   @RequestParam(value = "firstName", required = false) String firstName,
   @RequestParam(value = "lastName", required = false) String lastName) {
  
  logger.info("Inside standardSearchJson() method...");
  logger.info("firstName....: " + firstName);
  logger.info("lastName.....: " + lastName);

  if (firstName == null && lastName == null) {
   StatusMessage statusMessage = new StatusMessage();
   statusMessage.setStatus(HttpStatus.BAD_REQUEST.value());
   statusMessage
      .setMessage("Both firstName and lastName may not be empty.");

   logger.error(
    "Both firstName and lastName may not be empty.  Search aborted!!!");
   return new ResponseEntity<>(statusMessage, HttpStatus.BAD_REQUEST);
  } else {
   List<Employee> filteredAssociates = employeeRepository
     .getEmployeesStandardSearch(firstName, lastName);

   return new ResponseEntity<>(filteredAssociates, HttpStatus.OK);
  }
 }

 @RequestMapping(value = "/v1/search/std", method = RequestMethod.POST, 
                             produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<?> standardSearchXML(
  @RequestParam(value = "firstName", required = false) String firstName,
  @RequestParam(value = "lastName", required = false) String lastName) {
  
  logger.info("Inside standardSearchXML() method...");
  logger.info("firstName....: " + firstName);
  logger.info("lastName.....: " + lastName);
  
  if (firstName == null && lastName == null) {
  StatusMessage statusMessage = new StatusMessage();
  statusMessage.setStatus(HttpStatus.BAD_REQUEST.value());
  statusMessage
  .setMessage("Both firstName and lastName may not be empty.");
  
  logger.error(
  "Both firstName and lastName may not be empty.  Search aborted!!!");
  return new ResponseEntity<>(statusMessage, HttpStatus.BAD_REQUEST);
  } else {
    Employees filteredAssociates = new Employees(employeeRepository
                .getEmployeesStandardSearch(firstName, lastName));
  
    return new ResponseEntity<>(filteredAssociates, HttpStatus.OK);
  }
}
 
@RequestMapping(value = "/v1/search/adv", method = RequestMethod.POST, 
                           produces = MediaType.APPLICATION_JSON_VALUE)

  public ResponseEntity<?> advancedSearchJson(
       @RequestBody List<SelectionCriteria> criteriaList) {

  logger.info("Inside advancedSearchJson() method...");

  List<Employee> filteredAssociates = employeeRepository
            .getEmployeesBySelectionCriteria(criteriaList);

  return new ResponseEntity<>(filteredAssociates, HttpStatus.OK);
}

@RequestMapping(value = "/v1/search/adv", method = RequestMethod.POST, 
                           produces = MediaType.APPLICATION_XML_VALUE)

public ResponseEntity<?> advancedSearchXml(
      @RequestBody List<SelectionCriteria> criteriaList) {

  logger.info("Inside advancedSearchXml() method...");

  Employees filteredAssociates = new Employees(employeeRepository
                      .getEmployeesBySelectionCriteria(criteriaList));

  return new ResponseEntity<>(filteredAssociates, HttpStatus.OK);
}

 @RequestMapping(value="/v1/employees/{id}", method=RequestMethod.DELETE)
 public ResponseEntity<?> deleteEmployeeById(
   @PathVariable(value = "id", required = false) String id) {

  if (id == null || id.isEmpty()) {
   StatusMessage statusMessage = new StatusMessage();
   statusMessage.setStatus(HttpStatus.BAD_REQUEST.value());
   statusMessage.setMessage("'id' is a required field for this request");

   if (logger.isInfoEnabled()) {
    logger.info("'id' is a required field for this request");
   }

   return new ResponseEntity<>(statusMessage, HttpStatus.BAD_REQUEST);
  }
  
  Employee employee = employeeRepository.deleteEmployee(id);

  if (employee == null) {
   if (logger.isInfoEnabled()) {
    logger.info(
     "Inside deleteEmployeeById, ID: " + id + ", NOT FOUND!");
   }
   
   StatusMessage statusMessage = new StatusMessage();
   statusMessage.setStatus(HttpStatus.NOT_FOUND.value());
   statusMessage.setMessage("Unable to delete employee ID: " + id);

   if (logger.isInfoEnabled()) {
    logger.info("Inside getEmployeeById, ID: " + id + ", NOT FOUND!");
   }

   return new ResponseEntity<>(statusMessage, HttpStatus.NOT_FOUND);   
  }

  if (logger.isInfoEnabled()) {
   logger.info("Inside deleteEmployeeById, deleted: "
    + employee.toString());
  }
  
  return new ResponseEntity<>(employee, HttpStatus.OK);   
 }

 @RequestMapping(value="/v1/employees/{id}", method=RequestMethod.PUT)
 public ResponseEntity<?> updateEmployeeById(
   @PathVariable(value = "id", required = false) String id,
   @RequestBody Employee employee) {

  if (id == null || id.isEmpty()) {
   StatusMessage statusMessage = new StatusMessage();
   statusMessage.setStatus(HttpStatus.BAD_REQUEST.value());
   statusMessage.setMessage("'id' is a required field for this request");

   if (logger.isInfoEnabled()) {
    logger.info("'id' is a required field for this request");
   }

   return new ResponseEntity<>(statusMessage, HttpStatus.BAD_REQUEST);
  }
  
  Employee myEmployee = employeeRepository.updateEmployee(id,
    employee);

  if (myEmployee == null) {
   if (logger.isInfoEnabled()) {
    logger.info(
     "Unable to update employee.  ID: " + id + ", NOT FOUND!");
   }
   
   StatusMessage statusMessage = new StatusMessage();
   statusMessage.setStatus(HttpStatus.NOT_FOUND.value());
   statusMessage.setMessage("Unable to delete employee ID: " + id);

   return new ResponseEntity<>(statusMessage, HttpStatus.NOT_FOUND);   
  }

  if (logger.isInfoEnabled()) {
   logger.info("Inside updateEmployeeById, updated: "
    + myEmployee.toString());
  }
  return new ResponseEntity<>(myEmployee, HttpStatus.OK);   
 }

 @RequestMapping(value = "/v1/employees", method = RequestMethod.POST)
 public ResponseEntity<?> addEmployee(
   @RequestBody Employee employee) {

  logger.info("Inside addEmployee, model attribute: "
    + employee.toString());

  if (employee.getId() == null || employee.getId().isEmpty()) {
   StatusMessage statusMessage = new StatusMessage();
   statusMessage.setStatus(HttpStatus.BAD_REQUEST.value());
   statusMessage.setMessage("'id' is a required field for this request");

   if (logger.isInfoEnabled()) {
    logger.info("'id' is a required field for this request");
   }

   return new ResponseEntity<>(statusMessage, HttpStatus.BAD_REQUEST);
  }

  Employee myEmployee = employeeRepository
                              .getEmployeeById(employee.getId());
  if (myEmployee != null) {
   if (myEmployee.getId() != null
     && myEmployee.getId().equalsIgnoreCase(employee.getId())) {
    StatusMessage statusMessage = new StatusMessage();
    statusMessage.setStatus(HttpStatus.CONFLICT.value());
    statusMessage.setMessage("ID already exists in the system.");
    
    if (logger.isInfoEnabled()) {
     logger.info("'id' is a required field for this request");
    }

    return new ResponseEntity<>(statusMessage, HttpStatus.CONFLICT);
   }
  }

  if (employee.getId() != null && employee.getId().length() > 0) {
   logger.info("Inside addEmployee, adding: " + employee.toString());
   employeeRepository.addEmployee(employee);
  } else {
   StatusMessage statusMessage = new StatusMessage();
   statusMessage.setStatus(HttpStatus.NOT_MODIFIED.value());
   statusMessage.setMessage("Failed to add employee");
   
   if (logger.isInfoEnabled()) {
    logger.info("Failed to insert...");
   }

   return new ResponseEntity<>(statusMessage, HttpStatus.NOT_MODIFIED);
  }

  return new ResponseEntity<>(employee, HttpStatus.OK);  
 }
}

Employees Class (Employees.java)

This class is used as a container or wrapper class when using XML responses for list of Employees.

package com.avaldes.model;

import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "employees")
public class Employees {
  
  private List<Employee> employees;

  public Employees() {}
  
  public Employees(List<Employee> employees) {
    super();
    this.employees = employees;
  }

  @XmlElement(name = "employee")
  public List<Employee> getEmployees() {
    return employees;
  }

  public void setEmployees(List<Employee> employees) {
    this.employees = employees;
  }

  @Override
  public String toString() {
    return "Employees []";
  }
}

StatusMessage Class (StatusMessage.java)

This class is used to display JSON or XML responses in a standard format with status_code and a message component.

package com.avaldes.model;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonPropertyOrder({"status", "message"})
@XmlType (propOrder={"status", "message"})
@XmlRootElement(name = "statusmessage")
public class StatusMessage {

	private Integer status;
	private String message;
	
	public StatusMessage() {
	}

	@JsonProperty(value = "status")
	public Integer getStatus() {
		return status;
	}

	public void setStatus(Integer status) {
		this.status = status;
	}

	@JsonProperty(value = "message")
	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
	
	@Override
	public String toString() {
		return "StatusMessage [status=" + status + ", message=" + message
				+ "]";
	}
}

DateUtility Class (DateUtility.java)

package com.avaldes.util;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtility {
  public static final String DATETIME_FORMAT 
                   = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";        

  private DateUtility() {
  }       

  public static Date getDate(String dateStr) {
    final DateFormat formatter 
                   = new SimpleDateFormat(DATETIME_FORMAT);
    try {
        return formatter.parse(dateStr);
    } catch (ParseException e) {                
        return null;
    }
  }
  
  public static Date getDate(String dateStr, String format) {
      final DateFormat formatter = new SimpleDateFormat(format);
      try {
          return formatter.parse(dateStr);
      } catch (ParseException e) {                
          return null;
      }
  }
}

EmployeeRepository Data Access Object (DAO) for MongoDB (EmployeeRepository.java)

In this class you will notice two annotations being used. The first one, @Repository indicates that the class EmployeeRepository fulfills the role of a Data Access Object of a repository. This class will handle all of the persistence of Employee objects and database access for us.

The second annotation, @Autowired indicates that MongoTemplate is autowired from the Spring configuration, in this case our dispatcher-servlet.xml file.

In this scenario, we use Spring Boot’s auto-configuration features and opinionated defaults for configuring and setting up our Mongo Repository.

package com.avaldes.dao;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.RequestParam;

import com.avaldes.model.Employee;
import com.avaldes.model.SelectionCriteria;
import com.avaldes.util.DateUtility;

@Repository
public class EmployeeRepository {
  public static final String COLLECTION_NAME = "associate";
  private static final Logger logger = LoggerFactory
      .getLogger(EmployeeRepository.class);

  @Autowired
  private MongoTemplate mongoTemplate;

  public void addEmployee(Employee employee) {
    if (!mongoTemplate.collectionExists(Employee.class)) {
      mongoTemplate.createCollection(Employee.class);
    }
    mongoTemplate.insert(employee, COLLECTION_NAME);
  }

  public Employee getEmployeeById(String id) {
    return mongoTemplate.findOne(
        Query.query(Criteria.where("id").is(id)), Employee.class,
        COLLECTION_NAME);
  }

  public List<Employee> getAllEmployees() {
    return mongoTemplate.findAll(Employee.class, COLLECTION_NAME);
  }

  public List<Employee> getEmployeesStandardSearch(
      @RequestParam("firstName") String firstName,
      @RequestParam("lastName") String lastName) {

    List<Criteria> andCriteriaList = new ArrayList<Criteria>();
    boolean ok = false;

    Query query = new Query();

    if (firstName != null && firstName.length() > 0) {
      Criteria c1 = Criteria.where("first_name").regex(firstName, "i");
      andCriteriaList.add(c1);
      ok = true;
    }
    if (lastName != null && lastName.length() > 0) {
      Criteria c1 = Criteria.where("last_name").regex(lastName, "i");
      andCriteriaList.add(c1);
      ok = true;
    }

    if (ok) {
      query.addCriteria(new Criteria().andOperator(andCriteriaList
          .toArray(new Criteria[andCriteriaList.size()])));

      return mongoTemplate.find(query, Employee.class,
          COLLECTION_NAME);
    } else {
      return null;
    }
  }

  public List<Employee> getEmployeesBySelectionCriteria(
      List<SelectionCriteria> criteriaList) {

    List<Criteria> andCriteriaList = new ArrayList<Criteria>();

    Query query = new Query();

    for (SelectionCriteria criteriaElem : criteriaList) {
      if (criteriaElem.getOperator().getId().equals("equalTo")) {
        Criteria c1 = Criteria.where(criteriaElem.getField().getId())
            .is(criteriaElem.getValue());
        andCriteriaList.add(c1);
      } else if (criteriaElem.getOperator().getId().equals("like")) {
        Criteria c1 = Criteria.where(criteriaElem.getField().getId())
            .regex(criteriaElem.getValue(), "i");
        andCriteriaList.add(c1);
      } else if (criteriaElem.getOperator().getId()
          .equals("notEqualTo")) {
        Criteria c1 = Criteria.where(criteriaElem.getField().getId())
            .ne(criteriaElem.getValue());
        andCriteriaList.add(c1);
      } else if (criteriaElem.getOperator().getId()
          .equals("greaterThan")) {
        Criteria c1 = Criteria.where(criteriaElem.getField().getId())
            .gt(DateUtility.getDate(criteriaElem.getValue()));
        andCriteriaList.add(c1);
      } else if (criteriaElem.getOperator().getId()
          .equals("lessThan")) {
        Criteria c1 = Criteria.where(criteriaElem.getField().getId())
            .lt(DateUtility.getDate(criteriaElem.getValue()));
        andCriteriaList.add(c1);
      }
      logger.info(criteriaElem.toString());
    }
    query.addCriteria(new Criteria().andOperator(andCriteriaList
        .toArray(new Criteria[andCriteriaList.size()])));

    return mongoTemplate.find(query, Employee.class, COLLECTION_NAME);
  }

  public Employee deleteEmployee(String id) {
    Employee Employee = mongoTemplate.findOne(
        Query.query(Criteria.where("id").is(id)), Employee.class,
        COLLECTION_NAME);
    mongoTemplate.remove(Employee, COLLECTION_NAME);

    return Employee;
  }

  public Employee updateEmployee(String id,
      com.avaldes.model.Employee Employee) {
    Query query = new Query();
    query.addCriteria(Criteria.where("id").is(id));

    Update update = new Update();
    update.set("id", Employee.getId());
    update.set("short_name", Employee.getShort_name());
    update.set("first_name", Employee.getFirst_name());
    update.set("last_name", Employee.getLast_name());
    update.set("job_desc", Employee.getJob_desc());
    update.set("employee_type", Employee.getEmployee_type());
    update.set("employee_status", Employee.getEmployee_status());
    update.set("location_type", Employee.getLocation_type());
    update.set("title_desc", Employee.getTitle_desc());
    update.set("alt_title", Employee.getAlt_title());
    update.set("cost_center", Employee.getCost_center());
    update.set("working_shift", Employee.getWorking_shift());
    update.set("preferred_name", Employee.getPreferred_name());
    update.set("middle", Employee.getMiddle());
    update.set("full_name", Employee.getFull_name());
    update.set("country", Employee.getCountry());
    update.set("company_name", Employee.getCompany_name());
    update.set("company_code", Employee.getCompany_code());
    update.set("department", Employee.getDepartment());
    update.set("region", Employee.getRegion());
    update.set("district", Employee.getDistrict());
    update.set("building", Employee.getBuilding());
    update.set("floor", Employee.getFloor());
    update.set("section", Employee.getSection());
    update.set("section_num", Employee.getSection_num());
    update.set("phone", Employee.getPhone());
    update.set("extension", Employee.getExtension());
    update.set("manager_id", Employee.getManager_id());
    update.set("manager_name", Employee.getManager_name());
    update.set("email", Employee.getEmail());
    update.set("hire_date", Employee.getHire_date());
    update.set("is_active", Employee.getIs_active());

    mongoTemplate.updateFirst(query, update, 
                    Employee.class, COLLECTION_NAME);

    return Employee;
  }
}

The SelectionCriteria Class (SelectionCriteria.java)

package com.avaldes.model;

public class SelectionCriteria {
  private Field field;

  private Operator operator;

  private String value;

  public Field getField() {
    return field;
  }

  public void setField(Field field) {
    this.field = field;
  }

  public Operator getOperator() {
    return operator;
  }

  public void setOperator(Operator operator) {
    this.operator = operator;
  }

  public String getValue() {
    return value;
  }

  public void setValue(String value) {
    this.value = value;
  }

  @Override
  public String toString() {
    return "SelectionCriteria [field="+field+", operator="+operator
      + ", value=" + value + "]";
  }
}

The Operator Class (Operator.java)

package com.avaldes.model;

public class Operator {
  private String id;
  private String name;
  private String type;

  public String getId() {
    return id;
  }

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

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }

  @Override
  public String toString() {
    return "Operator [id="+id+", name="+name+", type="+type+"]";
  }
}

The Field Class (Field.java)

package com.avaldes.model;

public class Field {
  private String id;
  private String name;
  private String type;
  private String selected;

  public String getId() {
    return id;
  }

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

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }

  public String getSelected() {
    return selected;
  }

  public void setSelected(String selected) {
    this.selected = selected;
  }

  @Override
  public String toString() {
    return "Field [id=" + id + ", name=" + name + ", type=" + type
        + ", selected=" + selected + "]";
  }
}

JSON Data/Time Serializer (JsonDateTimeSerializer.java)

In our example, we use the @JsonSerialize Jackson annotation in order to specify how to serialize the date field in the Employee class.

package com.avaldes.util;
 
import java.io.IOException; 
import java.text.SimpleDateFormat; 
import java.util.Date;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider; 
 
@Component
@ComponentScan("com.avaldes.util")
public class JsonDateTimeSerializer extends JsonSerializer<Date> {
  private static final SimpleDateFormat dateFormat = 
    new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSZ");
   
  @Override
  public void serialize(Date date, JsonGenerator gen, 
    SerializerProvider provider)
      throws IOException, JsonProcessingException {
    String formattedDate = dateFormat.format(date);
    gen.writeString(formattedDate);
  }
}

LogBack Configuration File (logback.xml)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

 <property name="USER_HOME" value="/local1/directoryservices/logs" />
 
 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  <layout class="ch.qos.logback.classic.PatternLayout">
  <Pattern>
   %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n
  </Pattern>
 </layout>
 </appender>
 
 <appender name="fileAppender" 
   class="ch.qos.logback.core.rolling.RollingFileAppender">
   <file>${USER_HOME}/DirectoryService.log</file>
   <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
   <Pattern>
    %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n
   </Pattern>
  </encoder>
  
  <rollingPolicy 
    class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
   <!-- rollover daily -->
   <fileNamePattern>
    ${USER_HOME}/archived/archive.%d{yyyy-MM-dd}.%i.log
   </fileNamePattern>
   <timeBasedFileNamingAndTriggeringPolicy
    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    <maxFileSize>10MB</maxFileSize>
   </timeBasedFileNamingAndTriggeringPolicy>
  </rollingPolicy>
 </appender>

 <logger name="org.springframework" level="info" additivity="false">
  <appender-ref ref="STDOUT" />
 </logger>

 <logger name="com.avaldes" level="info" additivity="false">
  <appender-ref ref="STDOUT" />
  <appender-ref ref="fileAppender" />
 </logger>

 <root level="error">
  <appender-ref ref="STDOUT" />
 </root>

</configuration>

Spring Boot Actuator

Spring Boot Actuator makes it easy to collect critical metrics from the application, its environment and monitor the application’s health. Without this insight you would be hard-pressed to really know what is going on in your spring-based application. By using the Actuator module available in Spring Boot we are able to implement production-grade features like metrics, health check, traces, security and administration.

Set up is extremely straight forward, you need only add the following dependency in your application’s pom.xml file.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

and add the following lines into the application.properties file.

management.port: 8181
management.context-path=/manage
management.security.enabled=false

I added a few more lines to provide some additional application-level details that will be available when using the /info endpoint.

info.app.name=Spring Boot Directory Services Application
info.app.description=Spring Boot Directory Services Application
info.app.version=1.0.0
info.app.developer=Amaury Valdes
http://localhost:8181/manage/info
{
  "app": {
    "version": "1.0.0",
    "developer": "Amaury Valdes",
    "description": "Spring Boot Directory Services Application",
    "name": "Spring Boot Directory Services Application"
  }
}
http://localhost:8181/manage/health
{
  "status": "UP",
  "diskSpace": {
    "status": "UP",
    "total": 499578830848,
    "free": 329006968832,
    "threshold": 10485760
  },
  "mongo": {
    "status": "UP",
    "version": "3.2.10"
  }
}
http://localhost:8181/manage/metrics
{
  "mem": 335731,
  "mem.free": 125347,
  "processors": 4,
  "instance.uptime": 2289729,
  "uptime": 2292650,
  "systemload.average": -1,
  "heap.committed": 283136,
  "heap.init": 131072,
  "heap.used": 157788,
  "heap": 1840640,
  "nonheap.committed": 53568,
  "nonheap.init": 2496,
  "nonheap.used": 52595,
  "nonheap": 0,
  "threads.peak": 39,
  "threads.daemon": 36,
  "threads.totalStarted": 46,
  "threads": 39,
  "classes": 7486,
  "classes.loaded": 7486,
  "classes.unloaded": 0,
  "gc.ps_scavenge.count": 8,
  "gc.ps_scavenge.time": 58,
  "gc.ps_marksweep.count": 2,
  "gc.ps_marksweep.time": 115,
  "httpsessions.max": -1,
  "httpsessions.active": 0
}
http://localhost:8181/manage/mappings
{
  "/webjars/**": {
      "bean": "resourceHandlerMapping"
  },
  "/**": {
      "bean": "resourceHandlerMapping"
  },
  "/**/favicon.ico": {
      "bean": "faviconHandlerMapping"
  },
  "{[/status],methods=[GET],produces=}": {
      "bean": "requestMappingHandlerMapping",
      "method": "public java.lang.String com.avaldes.tutorial.DirectoryServicesController.status()"
  },
  "{[/error],methods=[GET]}": {
      "bean": "requestMappingHandlerMapping",
      "method": "public java.lang.String com.avaldes.tutorial.DirectoryServicesController.getErrorMessage()"
  },
  "{[/v1/employees],methods=[GET],produces=[application/json]}": {
      "bean": "requestMappingHandlerMapping",
      "method": "public org.springframework.http.ResponseEntity<?> com.avaldes.tutorial.DirectoryServicesController.getAllEmployeesJson()"
  },
  "{[/v1/employees/{id}],methods=[PUT]}": {
      "bean": "requestMappingHandlerMapping",
      "method": "public org.springframework.http.ResponseEntity<?> com.avaldes.tutorial.DirectoryServicesController.updateEmployeeById(java.lang.String,com.avaldes.model.Employee)"
  },
  "{[/v1/employees],methods=[GET],produces=[application/xml]}": {
      "bean": "requestMappingHandlerMapping",
      "method": "public org.springframework.http.ResponseEntity<?> com.avaldes.tutorial.DirectoryServicesController.getAllEmployeesXml()"
  },
  "{[/v1/employees/{id}],methods=[DELETE]}": {
      "bean": "requestMappingHandlerMapping",
      "method": "public org.springframework.http.ResponseEntity<?> com.avaldes.tutorial.DirectoryServicesController.deleteEmployeeById(java.lang.String)"
  },
  "{[/v1/employees],methods=[POST]}": {
      "bean": "requestMappingHandlerMapping",
      "method": "public org.springframework.http.ResponseEntity<?> com.avaldes.tutorial.DirectoryServicesController.addEmployee(com.avaldes.model.Employee)"
  },
  "{[/v1/employees/{id}],methods=[GET]}": {
      "bean": "requestMappingHandlerMapping",
      "method": "public org.springframework.http.ResponseEntity<?> com.avaldes.tutorial.DirectoryServicesController.getEmployeeById(java.lang.String)"
  },
  "{[/v1/search/std],methods=[POST]}": {
      "bean": "requestMappingHandlerMapping",
      "method": "public org.springframework.http.ResponseEntity<?> com.avaldes.tutorial.DirectoryServicesController.standardSearch(java.lang.String,java.lang.String)"
  },
  "{[/v1/search/adv],methods=[POST]}": {
      "bean": "requestMappingHandlerMapping",
      "method": "public java.util.List<com.avaldes.model.Employee> com.avaldes.tutorial.DirectoryServicesController.advancedSearch(java.util.List<com.avaldes.model.SelectionCriteria>)"
  },
  "{[/error]}": {
      "bean": "requestMappingHandlerMapping",
      "method": "public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)"
  },
  "{[/error],produces=}": {
      "bean": "requestMappingHandlerMapping",
      "method": "public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)"
  }
}

MongoDB Employee Collection

On MongoDB, please make sure you insert the following records in order to test this application locally otherwise you won’t have any data stored in your MongoDB local instance.

MongoDB Prerequiste

{
    "_id" : "00001",
    "short_name": "avaldes",
    "job_desc" : "IT Development",
    "employee_type" : "permanent",
    "employee_status" : "active",
    "location_type" : "domestic",
    "title_desc" : "Senior Developer",
    "alt_title" : "developer",
    "cost_center" : "1025",
    "working_shift" : 1,
    "first_name" : "Amaury",
    "preferred_name" : "Amaury",
    "middle" : "",
    "last_name" : "Valdes",
    "full_name" : "Amaury Valdes",
    "country" : "USA",
    "company_name" : "Lark Productions",
    "company_code" : 121,
    "department" : "Product Development",
    "region": "NorthEast",
    "district": "NJEast",
    "building": "800B",
    "floor": "2",
    "section": "C",
    "section_num": "302",
    "phone": "800-555-1212",
    "extension": "x4555",
    "manager_id": "pmcneal",
    "manager_name": "Paul McNeal",
    "email": "amaury@lark.com",
    "hire_date" : ISODate("2012-05-18T04:00:00.0001Z"),
    "is_active" : false
}
{
    "_id" : "00002",
    "short_name": "sadelson",
    "job_desc" : "IT Management",
    "employee_type" : "permanent",
    "employee_status" : "active",
    "location_type" : "domestic",
    "title_desc" : "Senior Manager",
    "alt_title" : "manager",
    "cost_center" : "1025",
    "working_shift" : 1,
    "first_name" : "Steven",
    "preferred_name" : "Steve",
    "middle" : "J",
    "last_name" : "Adelson",
    "full_name" : "Steven Adelson",
    "country" : "USA",
    "company_name" : "Lark Productions",
    "company_code" : 121,
    "department" : "Product Development",
    "region": "NorthEast",
    "district": "NJEast",
    "building": "800B",
    "floor": "1",
    "section": "B",
    "section_num": "102",
    "phone": "800-555-1212",
    "extension": "x3223",
    "manager_id": "khenderson",
    "manager_name": "Keith Henderson",
    "email": "sadelson@lark.com",
    "hire_date" : ISODate("2010-03-02T04:00:00.0001Z"),
    "is_active" : true
}
{
    "_id" : "00003",
    "short_name": "rpaterson",
    "job_desc" : "Senior Management",
    "employee_type" : "permanent",
    "employee_status" : "active",
    "location_type" : "domestic",
    "title_desc" : "Senior Group Manager",
    "alt_title" : "manager",
    "cost_center" : "1025",
    "working_shift" : 1,
    "first_name" : "Robert",
    "preferred_name" : "Bob",
    "middle" : "",
    "last_name" : "Paterson",
    "full_name" : "Robert Paterson",
    "country" : "USA",
    "company_name" : "Lark Animation, LLC",
    "company_code" : 122,
    "department" : "Animation Studio",
    "region": "MidWest",
    "district": "Ch08",
    "building": "3902F",
    "floor": "3",
    "section": "DD",
    "section_num": "344",
    "phone": "800-849-8878",
    "extension": "x4904",
    "manager_id": "ganderson",
    "manager_name": "Greg Anderson",
    "email": "ganderson@larkAnimation.com",
    "hire_date" : ISODate("2010-09-04T04:00:00.0001Z"),
    "is_active" : true
}
{
    "_id" : "00004",
    "short_name": "sjefferies",
    "job_desc" : "Receptionist",
    "employee_type" : "temp",
    "employee_status" : "active",
    "location_type" : "domestic",
    "title_desc" : "Front Desk Reception",
    "alt_title" : "receptionist",
    "cost_center" : "1025",
    "working_shift" : 1,
    "first_name" : "Sandra",
    "preferred_name" : "Sandy",
    "middle" : "",
    "last_name" : "Jeffries",
    "full_name" : "Sandra Jeffries",
    "country" : "USA",
    "company_name" : "Kelly Temps",
    "company_code" : 322,
    "department" : "Office Support",
    "region": "South",
    "district": "Tx5",
    "building": "TT8800",
    "floor": "1",
    "section": "1B",
    "section_num": "200",
    "phone": "888-263-3222",
    "extension": "x203",
    "manager_id": "rwilliams",
    "manager_name": "Roger Williams",
    "email": "rwilliams@kelly.com",
    "hire_date" : ISODate("2008-12-23T04:00:00.0001Z"),
    "is_active" : true
}
{
    "_id" : "00005",
    "short_name": "csmith",
    "job_desc" : "Developer",
    "employee_type" : "permanent",
    "employee_status" : "active",
    "location_type" : "domestic",
    "title_desc" : "Front-End Developer",
    "alt_title" : "developer",
    "cost_center" : "982",
    "working_shift" : 1,
    "first_name" : "Christopher",
    "preferred_name" : "Chris",
    "middle" : "",
    "last_name" : "Smith",
    "full_name" : "Christopher Smith",
    "country" : "USA",
    "company_name" : "Lark Productions",
    "company_code" : 121,
    "department" : "Development Support",
    "region": "NorthEast",
    "district": "NJEast",
    "building": "800B",
    "floor": "4",
    "section": "WW",
    "section_num": "700",
    "phone": "800-555-1212",
    "extension": "x3738",
    "manager_id": "ltillnow",
    "manager_name": "Larry Tillnow",
    "email": "csmith@lark.com",
    "hire_date" : ISODate("2010-05-02T04:00:00.0001Z"),
    "is_active" : true
}
{
    "_id" : "00006",
    "short_name": "cbarnes",
    "job_desc" : "Developer",
    "employee_type" : "consultant",
    "employee_status" : "active",
    "location_type" : "domestic",
    "title_desc" : "Front-End Developer",
    "alt_title" : "developer",
    "cost_center" : "982",
    "working_shift" : 1,
    "first_name" : "Christa",
    "preferred_name" : "Chrissy",
    "middle" : "",
    "last_name" : "Barnes",
    "full_name" : "Christa Barnes",
    "country" : "USA",
    "company_name" : "Sapient Technologies",
    "company_code" : 572,
    "department" : "Development Support",
    "region": "West",
    "district": "CaWest",
    "building": "650",
    "floor": "3",
    "section": "G",
    "section_num": "540",
    "phone": "866-433-2393",
    "extension": "x400",
    "manager_id": "bclark",
    "manager_name": "Brian Clark",
    "email": "cbarnes@lark.com",
    "hire_date" : ISODate("2012-07-13T04:00:00.0001Z"),
    "is_active" : true
}
{
    "_id" : "00007",
    "short_name": "cverde",
    "job_desc" : "Developer",
    "employee_type" : "permanent",
    "employee_status" : "active",
    "location_type" : "domestic",
    "title_desc" : "Java Developer",
    "alt_title" : "developer",
    "cost_center" : "960",
    "working_shift" : 1,
    "first_name" : "Christine",
    "preferred_name" : "Christine",
    "middle" : "",
    "last_name" : "Verde",
    "full_name" : "Christine Verde",
    "country" : "USA",
    "company_name" : "Lark Technologies",
    "company_code" : 120,
    "department" : "Development Support",
    "region": "West",
    "district": "SVWest",
    "building": "32000",
    "floor": "55",
    "section": "ZZ",
    "section_num": "644",
    "phone": "888-999-2020",
    "extension": "x2343",
    "manager_id": "msalvano",
    "manager_name": "Michael Salvano",
    "email": "cverde@larkTech.com",
    "hire_date" : ISODate("2006-03-15T04:00:00.0001Z"),
    "is_active" : true
}

Testing out the Web Services

In addition to using our AngularJS/Angular Material/Grid-UI web application to test out our restful services I used Postman which is a Google Chrome Application. Using this tool I validated each of the REST API calls. Please review the screen shots below:

Testing Application and POSTMAN Chrome Extension

Download Complete Source Code for Employee Directory Boot / Sencha ExtJS Application

That’s It!

I hope you enjoyed this tutorial. It was certainly a lot of fun putting it together and testing it out. Please continue to share the love and like us so that we can continue bringing you quality tutorials. Happy Coding!!!

springboot_search_backend

Related Spring Posts

  • Creating Hello World Application using Spring MVC on Eclipse IDE
    In this tutorial we will go into some detail on how to set up your Eclipse IDE environment so that you can develop Spring MVC projects. In this post, we will create our first Spring MVC project with the all to familiar “Hello World” sample program.
  • Spring MVC Form Handling Example
    The following tutorial will guide you on writing a simple web based application which makes use of forms using Spring Web MVC framework. With this web application you will be able to interact with the customer entry form and enter all of the required values and submit them to the backend processes. I have taken the liberty of using CSS to beautify and transform the HTML page from a standard drab look and feel to a more appealing view.
  • Spring @RequestHeader Annotation Example
    In this tutorial, we will discuss the different ways that Spring MVC allow us to access HTTP headers using annotation. We will discuss how to access individual header fields from the request object as well accessing all the headers by supplying Map and then iterating through the LinkedHashMap collection. We will also show you how to set the headers in the response object.
  • Spring MVC Exception Handling using @ExceptionHandler with AngularJS GUI
    Good exception handling is a essential part of any well developed Application Framework and Spring MVC is no exception — pardon the pun. Spring MVC provides several different ways to handle exceptions in our applications. In this tutorial, we will cover Controller Based Exception Handling using the @ExceptionHandler annotation above the method that will handle it.
  • Spring RESTful Web Service Example with JSON and Jackson using Spring Tool Suite
    For this example, I will be using Spring Tool Suite (STS) as it is the best integrated development environment for building the Spring framework projects. Spring is today's leading framework for building Java, Enterprise Edition (Java EE) applications. One additional feature that makes Spring MVC so appealing is that it now also supports REST (REpresentational State Transfer) for build Web Services.
  • Spring MVC RESTful Web Service Example with Spring Data for MongoDB and ExtJS GUI
    This post will show another example of how to build a RESTful web service using Spring MVC 4.0.6, Spring Data for MongoDB 1.6.1 so that we can integrate the web application with a highly efficient datastore (MongoDB 2.6). In this tutorial we will walk you through building the web service and NoSQL database backend and show you how to implement CRUD (Create, Read, Update and Delete) operations.
  • Building DHTMLX Grid Panel User Interface with Spring MVC Rest and MongoDB Backend
    In this tutorial we will show how easy it is to use DHTMLX dhtmlxGrid component while loading JSON data with Ajax pulling in data from the Spring MVC REST web service from our MongoDB data source. You will see how simple it is to create a visually appealing experience for your client(s) with minimal javascript coding.
  • Spring MVC with JNDI Datasource for DB2 on AS/400 using Tomcat
    In this tutorial we will discuss how to set up Spring MVC web services and configure a JNDI Datasource using Tomcat and connect to IBM DB2 Database on a AS/400. JNDI (Java Naming and Directory Interface) provides and interface to multiple naming and directory services.
  • Java Spring MVC Email Example using Apache Velocity
    In this tutorial we will discuss how to set up a Java Spring MVC RESTful Webservice with Email using Apache Velocity to create a Velocity template that is used to create an HTML email message and embed an image, as shown below, using MIME Multipart Message.
  • Implementing Basic and Advanced Search using Angular Material Design, Grid-UI, Spring MVC REST API and MongoDB Example
    In this tutorial we will discuss how to implement basic and advanced search techniques in MongoDB using AngularJS and Google’s Material Design with Spring MVC REST API backend. The advanced search user interface (UI) will use logical operators and build a JSON object which contains the search field name, boolean or logical operator and the search value.
  • Spring MVC Interceptor using HandlerInterceptorAdapter Example
    In this tutorial we will discuss how to use the HandlerInterceptorAdapter abstract class to create a Spring MVC interceptor. These interceptors are used to apply some type of processing to the requests either before, after or after the complete request has finished executing.

Please Share Us on Social Media

Facebooktwitterredditpinterestlinkedinmail

Leave a Reply

Your email address will not be published. Required fields are marked *