Building Serverless APIs with Spring Boot, AWS Lambda and API Gateway

This post demonstrates how to expose a RESTful API implemented with Spring MVC in a Spring Boot application as a Lambda function to be deployed via AWS API Gateway. We will be using the aws-serverless-java-container package which supports native API gateway’s proxy integration models for requests and responses.

Project Setup

Create a new Spring Boot project e.g. using the Spring Initializer or modify an existing project to include the aws-serverless-java-container package dependency:

<dependency> 
      <groupId>com.amazonaws.serverless</groupId> 
      <artifactId>aws-serverless-java-container-spring</artifactId> 
      <version>1.1</version> 
</dependency>

We can remove the Spring Boot Maven Plugin from the pom file. Instead, add the Maven Shade Plugin and remove the embedded Tomcat from the deployed package:

<plugins> 
   <plugin> 
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-shade-plugin</artifactId>
      <configuration> 
        <createDependencyReducedPom>false</createDependencyReducedPom> 
      </configuration> 
      <executions> 
        <execution> 
          <phase>package</phase>
          <goals> 
            <goal>shade</goal> 
          </goals> 
          <configuration> 
             <artifactSet> 
                <excludes> 
                   <exclude>org.apache.tomcat.embed:*</exclude>
                </excludes> 
             </artifactSet>
          </configuration> 
        </execution>
      </executions> 
   </plugin>
</plugins>

Serverless API

1. HelloController

Implement RESTful APIs using Spring MVC as usual. For example:

package com.madman.lambda;
...
@RestController
public class HelloController {
 
     @RequestMapping(path = "/greeting", method = RequestMethod.GET) 
     public GreetingDto sayHello(@RequestParam String name) { 
          String message = "Hello " + name; 
          GreetingDto dto = new GreetingDto();
          dto.setMessage(message); return dto; 
     }

     ...
}

2. StreamLambdaHandler

To deploy Java codes to run as AWS Lambda function, it needs to implement the handler interface RequestStreamHandler. The aws-serverless-java-container library makes it rather straight forward:

...
public class StreamLambdaHandler implements RequestStreamHandler {
    private static Logger logger = LoggerFactory.getLogger(StreamLambdaHandler.class);     

    public static final SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
 
    static { 
       try { 
           handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(HelloLambdaApplication.class);
       } catch (ContainerInitializationException e) { 
           // if we fail here. We re-throw the exception to force another cold start 
           String errMsg = "Could not initialize Spring Boot application"; 
           logger.error(errMsg); 
           throw new RuntimeException("Could not initialize Spring Boot application", e); 
       } 
    }

    @Override 
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
        handler.proxyStream(inputStream, outputStream, context);
        // just in case it wasn't closed 
        outputStream.close(); 
    }
}

The class StreamLambdaHandler implements the AWS Lambda predefined handler  interface RequestStreamHandler for handling events.

Note the handling of the Lambda events is delegated to the class SpringBootLambdaContainerHandler.

3. HelloLambdaApplication

Note the SpringBootLambdaContainerHandler.getAwsProxyHandler method is provided with a Spring web application initializer interface, which is implemented by the main Spring Boot Application class by extending the implementing class SpringBootServletInitializer :

@SpringBootApplication
@ComponentScan(basePackages = "com.madman.lambda.controller")
public class HelloLambdaApplication extends SpringBootServletInitializer {
 
     public static void main(String[] args) { 
           SpringApplication.run(HelloLambdaApplication.class, args);
     }
}

4. HelloControllerTest

The aws-serverless-java-container library also supports integration testing the proxy API. Below is integration test for HelloController:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { HelloLambdaApplication.class })
@WebAppConfiguration
public class HelloControllerTest {
    private MockLambdaContext lambdaContext;
    private SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
    
    @Autowired 
    private ObjectMapper mapper;
 
    public HelloControllerTest() { 
       lambdaContext = new MockLambdaContext(); 
       this.handler = StreamLambdaHandler.handler; 
    }

    @Test public void testGreetingApi() throws JsonParseException, JsonMappingException, IOException {
       AwsProxyRequest request = new AwsProxyRequestBuilder("/greeting", "GET").queryString("name", "John").build(); 
       AwsProxyResponse response = handler.proxy(request, lambdaContext);
      
       assertThat(response.getStatusCode(), equalTo(200)); 
       GreetingDto responseBody = mapper.readValue(response.getBody(), GreetingDto.class);
       asserThat(responseBody.getMessage(), equalTo("Hello John")); 
    }
}

Deploying to AWS

The full source codes can be found in GitHub here. Run Maven to build the jar file and deploy it as Lambda function. I use the AWS Toolkit for Eclipse to deploy the jar package to AWS. Refer to AWS documentation for more options and information on deploying Lambda applications.

To setup AWS API Gateway as trigger for the Lambda function:

  1. Create a New API
  2. Create Resource
    1. Configure as proxy resource
    2. Resource Name: greeting
  3. Create Method
    1. Get
    2. Integration type: Lambda Function
    3. Use Lambda Proxy Integration: true
    4. Lambda Function: <Name of Lambda function>

You should then be able to test the API with the AWS console (as screenshot below).

blog_apigateway

A couple of things to note:

  1. Cold start – the Java container takes a good few seconds. The latency is ok once it’s warmed up
  2. Fat jar – the Spring Boot jar in this example is around 13MB which is still ok for Lambda (limit 50MB)
Advertisements

Validating Spring MVC Request Mapping Method parameters

This short post demonstrates how to set up and use JSR-303 Validation for the arguments in Spring MVC request mapping methods for path variable and request parameter arguments. I am using Spring Boot v1.2.5 with Maven 3.

I. MethodValidationPostProcessor

The only configuration needed is adding the MethodValidationPostProcessor (javadoc) bean to the Spring configuration, e.g.

 @Bean
 public MethodValidationPostProcessor methodValidationPostProcessor() {
      return new MethodValidationPostProcessor();
 }

II Add validation to controller request mapping method

First, add the @Validate annotation to the controller class as follows:

@RestController
@Validated
public class HelloController {
     ...

Then add any JSR-303 validation annotation to a request mapping method arguments:

 @RequestMapping("/hi/{name}")
 public String sayHi(@Size(max = 10, min = 3, message = "name should have between 3 and 10 characters") @PathVariable("name") String name) {
      return "Hi " + name;
 }

The example codes above shows how to validate a value in the request path marked by @PathVariable. You can do the same with @RequestParam

III Validation exception handling

A ConstraintViolationException will be thrown if the size of the path variable is not within 3 to 10 characters. You may need to catch this exception and process it using a Spring MVC exception handler, for example to return the error messages in the response:

 @ExceptionHandler(value = { ConstraintViolationException.class })
 @ResponseStatus(value = HttpStatus.BAD_REQUEST)
 public String handleResourceNotFoundException(ConstraintViolationException e) {
      Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
      StringBuilder strBuilder = new StringBuilder();
      for (ConstraintViolation<?> violation : violations ) {
           strBuilder.append(violation.getMessage() + "\n");
      }
      return strBuilder.toString();
 }

Setup Spring Security with Active Directory LDAP in Spring Boot Web Application

This post illustrates how to set up Spring Security in Spring Boot configuration with Active Directory LDAP for a Spring MVC web application. I will also show what needs to be configured for the embedded tomcat to accept HTTPS.

Spring Security with LDAP

To configure Spring Security in Spring Boot, add the following Configuration class to your project. Note the use of annotation @EnableWebMvcSecurity. The configuration class extends the WebSecurityConfigurerAdapter class in Spring Security. More information can be found in the Spring Security Reference here.

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

     @Value("${ldap.domain}")
     private String DOMAIN;

     @Value("${ldap.url}")
     private String URL;

     @Value("${http.port}")
     private int httpPort;

     @Value("${https.port}")
     private int httpsPort;

     @Override
     protected void configure(HttpSecurity http) throws Exception {
          /*
           * Set up your spring security config here. For example...
          */
          http.authorizeRequests().anyRequest().authenticated().and().formLogin().loginUrl("/login").permitAll();
          /*
           * Use HTTPs for ALL requests
          */
          http.requiresChannel().anyRequest().requiresSecure();
          http.portMapper().http(httpPort).mapsTo(httpsPort);
     }

     @Override
     protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
          authManagerBuilder.authenticationProvider(activeDirectoryLdapAuthenticationProvider()).userDetailsService(userDetailsService());
     }

     @Bean
     public AuthenticationManager authenticationManager() {
          return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
     }
     @Bean
     public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
          ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(DOMAIN, URL);
          provider.setConvertSubErrorCodesToExceptions(true);
          provider.setUseAuthenticationRequestCredentials(true);
          return provider;
     }
}

Add HTTPS connector for embedded Tomcat in Spring Boot

Now that Spring Security is set up, you need to update the web server to accept requests from HTTPS. To do that using the embedded Tomcat server in Spring Boot, add the following EmbeddedServletContainerCustomizer bean to the application configuration as shown below. Note I am using anonymous inner classes here instead of lambda expression as I see in other examples for Java 7 compatibility. You will need a keystore file for this to work.

@Bean
EmbeddedServletContainerCustomizer containerCustomizer (

     @Value("${https.port}") final int port, 
     @Value("${keystore.file}") Resource keystoreFile,
     @Value("${keystore.alias}") final String alias, 
     @Value("${keystore.password}") final String keystorePass,
     @Value("${keystore.type}") final String keystoreType) throws Exception {
          final String absoluteKeystoreFile = keystoreFile.getFile().getAbsolutePath();
          return new EmbeddedServletContainerCustomizer() {
               public void customize(ConfigurableEmbeddedServletContainer container) {
                    TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container;
                    tomcat.addConnectorCustomizers(new TomcatConnectorCustomizer() {
                         public void customize(Connector connector) {
                              connector.setPort(port);
                              connector.setSecure(true);
                              connector.setScheme("https");
                              Http11NioProtocol proto = (Http11NioProtocol) connector.getProtocolHandler();
                              proto.setSSLEnabled(true);
                              proto.setKeystoreFile(absoluteKeystoreFile);
                              proto.setKeyAlias(alias);
                              proto.setKeystorePass(keystorePass);
                              proto.setKeystoreType(keystoreType);
                        }
               });
           }
     };
 }

 

Using Spring 4 WebSocket, sockJS and Stomp support to implement two way server client communication

One exciting new feature of Spring 4 is the support for WebSocket, SockJS and STOMP messaging. This allows two way communication between the server and its clients in a Spring MVC web application using the standard point-to-point and publish-subscribe messaging protocols. In this post, I will demonstrate how to set up a basic boilerplate project to start using this new feature. It is in part based on this article.

Maven Setup

First we need to add the Spring messaging modules in the POM file:

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-messaging</artifactId>
 <version>4.0.0.RELEASE</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-websocket</artifactId>
 <version>4.0.0.RELEASE</version>
 </dependency>

Spring MVC Configuration

Next, we need to add the message broker config to the Spring MVC config XML file.

<beans
 ...
 xmlns:websocket="http://www.springframework.org/schema/websocket"
 xsi:schemaLocation="
 ...
 http://www.springframework.org/schema/websocket
 http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd">
<websocket:message-broker application-destination-prefix="/app">
       <websocket:stomp-endpoint path="/hello">
            <websocket:sockjs/>
       </websocket:stomp-endpoint>
       <websocket:simple-broker prefix="/topic"/>
</websocket:message-broker>
<!-- Other MVC config omitted here-->

The main thing here is the set up of the message broker for handling the message exchange between the server and its clients. This is done via the <message-broker> and its child tags. The tag <websocket:simple-broker> indicates we are using in-memory message broker.

It is easy to understand together with the server and client codes so I will include them below first before attempting to give a bit more explanations by cross-referencing the client and server codes.

Spring MVC Controller

Below is my Spring MVC Controller

 @Controller
 public class MessageController {
      @MessageMapping("/hello")
      @SendTo("/topic/greetings")
      public Greeting greeting(HelloMessage message) throws Exception {
           return new Greeting("Hello, " + message.getName() + "!");
     }
}

The method argument HelloMessage and output Greeting are just POJOs representing the body of the messages being sent and returned.

public class Greeting {
    private String content;
    public Greeting(String content) {
           this.content = content;
    }
    public String getContent() {
      return content;
    }
}
public class HelloMessage {
    private String name;
    public String getName() {
        return name;
    }
}

Client sockJS and STOMP codes

On the client side, I use the sockJS protocol fallback option as outlined in the Spring documentation. The javascript codes are included below

// Create stomp client over sockJS protocol (see Note 1)
 var socket = new SockJS("/hello");
 var stompClient = Stomp.over(socket);

 // callback function to be called when stomp client is connected to server (see Note 2)
 var connectCallback = function() {
      alert("connected!");
      stompClient.subscribe('/topic/greetings', function(greeting){
           alert(JSON.parse(greeting.body).content);
      });
 }; 

 // callback function to be called when stomp client could not connect to server (see Note 3)
 var errorCallback = function(error) {
      // display the error's message header:
      alert(error.headers.message);
 };

 // Connect as guest (Note 4)
 stompClient.connect("guest", "guest", connectCallback, errorCallback);

Note

  1. The client starts by create a sockJS by specifying the endpoint (ie. /hello) to connect to and then a stomp client is created over the socket. The endpoint here should match that defined in the Spring MVC configuration in the lines. Note also the 2nd line referring to sockJS.

    <websocket:stomp-endpoint path=”/hello”>
    <websocket:sockjs/>
    </websocket:stomp-endpoint>

  2. Then a callback function is created and assigned to a variable connectCallback. This is called when a successful connection is made by the stomp client. This allows us to start making subscriptions to messages (as in codes, repeated below) and sending messages. Note the subscription is for the topic “/topic/greetings”

    stompClient.subscribe(‘/topic/greetings’, function(greeting){
    alert(JSON.parse(greeting.body).content);
    });

  3. A error callback function is defined if stomp client fails to connect to server.
  4. This line makes the connection registering the callback functions.

Now we are ready to send messages from the client, e.g. using the following javascript function

// function to send message
 function fnSayHi() {
       stompClient.send("/app/hello", {}, JSON.stringify({ 'name': 'Joe' }));
 }

The message will be sent to the Spring MVC message handler method greeting() as defined via the annotation @MessageMapping(“/hello”).

 <websocket:message-broker application-destination-prefix=”/app”>

Note the prefix “/app” is defined in the Spring config as  application-destination-prefix attribute of the message broker: Note also, the use of @SendTo annotation to direct the message to a given destination. I repeat the controller method below

 @MessageMapping("/hello")
 @SendTo("/topic/greetings")
 public Greeting greeting(HelloMessage message) throws Exception {
      return new Greeting("Hello, " + message.getName() + "!");
 }

That’s it for now.

Configure Spring MVC ContentNegotiatingViewResolver to generate RSS feeds

The spring 3.2 document provides the following example for configuring the ContentNegotiatingViewResolver for generating rss feeds:

<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
  <property name="mediaTypes">
    <map>
      <entry key="atom" value="application/atom+xml"/>
      <entry key="html" value="text/html"/>
      <entry key="json" value="application/json"/>
    </map>
  </property>
  <property name="viewResolvers">
    <list>
      <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
      <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
      </bean>
    </list>
  </property>
  <property name="defaultViews">
    <list>
      <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView" />
    </list>
  </property>
</bean>

However when I run this, I got class cast exception with the mediaTypes properties

java.lang.ClassCastException: java.lang.String cannot be cast to org.springframework.http.MediaType
 at org.springframework.web.accept.MappingMediaTypeFileExtensionResolver.<init>(MappingMediaTypeFileExtensionResolver.java:56)
 at org.springframework.web.accept.AbstractMappingContentNegotiationStrategy.<init>(AbstractMappingContentNegotiationStrategy.java:42)
 at org.springframework.web.accept.PathExtensionContentNegotiationStrategy.<init>(PathExtensionContentNegotiationStrategy.java:74)
 at org.springframework.web.accept.ServletPathExtensionContentNegotiationStrategy.<init>(ServletPathExtensionContentNegotiationStrategy.java:47)
 at org.springframework.web.accept.ContentNegotiationManagerFactoryBean.afterPropertiesSet(ContentNegotiationManagerFactoryBean.java:166)
 at org.springframework.web.servlet.view.ContentNegotiatingViewResolver.afterPropertiesSet(ContentNegotiatingViewResolver.java:270)

This is because the map expects a MediaType objects as values but got String objects instead, e.g. “application/atom+xml”. The correct way to setup the view resolver should be:

<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
 <property name="contentNegotiationManager">   [1]
      <bean class="org.springframework.web.accept.ContentNegotiationManager">
           <constructor-arg>
                <bean class="org.springframework.web.accept.PathExtensionContentNegotiationStrategy">
                     <constructor-arg>
                     <map>
                        <entry key="atom">
                             <util:constant static-field="org.springframework.http.MediaType.APPLICATION_ATOM_XML" />  [2]
                        </entry>
                        <entry key="rss" value-ref="rssMediaType"/>
                     </map>
                     </constructor-arg>
                </bean>
           </constructor-arg>
      </bean>
 </property>

Note:

  1. The use of property contentNegotiationManager and injection of the bean ContentNegotiationManager
  2. Static field of MediaType org.springframework.http.MediaType.APPLICATION_ATOM_XML
  3. The MediaType class does not define a constant for RSS type so you have to create your own:
<bean id="rssMediaType" class="org.springframework.http.MediaType">
      <constructor-arg value="application"/>
      <constructor-arg value="rss+xml"/>
</bean>

Integrating ZK/MVVM and Spring MVC

This blog demonstrates how to use the ZK framework as the view in a Spring MVC web application to leverage ZK’s rich UI and transparent Ajax capabilities. In particular, I will show how to pass data between the 2 frameworks.

Getting data from Spring MVC to ZK View (.zul)

Passing values from Spring MVC to ZK can be done in the same way as for JSP by adding attributes to the response model:

@RequestMapping(“/hello.action”)
public String passToZK(Model model) {
model.addAttribute(“appname”, “zk”);
return “/demo/hellozk.zul”;
}

and use in the ZK view file hellozk.zul:

<window>
<label>Hello from ${appname}!</label>
</window>

Binding data in ZK View to Spring MVC

Getting data from ZK back to Spring MVC involves a bit more work. Suppose we have to implement a web form to get the first and last names of a customer. The Customer class is a normal POJO:

public class Customer {

private String firstname;
private String lastname;

// getters and setters here…

}

and the .zul file:

<zk xmlns:n=”native”>

<window apply=”org.zkoss.bind.BindComposer” viewModel=”@id(‘vm’) @init(‘myblog.viewmodel.CustomerViewModel’)”>
<n:form action=”save.action” method=”post”>
<div width=”700px” class=”form”>
<vlayout spacing=”7px”>
<hlayout spacing=”20px”>
<label class=”name” value=”Firstname :” />
<textbox id=”firstname” value=”@bind(vm.entity.firstname)” />
<textbox visible=”false” name=”firstname” value=”@bind(vm.entity.firstname)” />
</hlayout>
<hlayout spacing=”20px”>
<label class=”name” value=”Lastname :” />
<textbox id=”name” value=”@bind(vm.entity.lastname)” />
<textbox visible=”false” name=”lastname” value=”@bind(vm.entity.lastname)” />
</hlayout>
<hlayout spacing=”20px”>
<button label=”Save” onClick=”@command(‘submit’)” />
</hlayout>
</vlayout>
</div>
</n:form>
</window>

Note

  1. Native form is used: “<n:form action=”save.action” method=”post”>
  2. The attributes firstname and lastname are bind to a ZK viewmodel (to be discussed later)
  3. In order to pass the form values (firstname and lastnames) back to Spring MVC when the form is submitted, the attributes are also bind to invisible textbox elements, e.g.:

<textbox visible=”false” name=”lastname” value=”@bind(vm.entity.lastname)” />

The zul form is bind to MVVM class myblog.viewmodel.CustomerViewModel

public class CustomerViewModel {

private Customer entity;
@Init
public void init(@ExecutionParam(“customer”) Customer             customer) {
this.entity = customer;
}

@Command(“submit”)
public void submit() {

// submit form
Clients.evalJavaScript(“jq(‘form’)[0].submit();”);
}

Note the annotation @ExecutionParam in the init method. This is required so to pass the model attribute in the Spring MVC controller to the form to populate any initial form values, for updating an existing customer. The submit command method is simple – it just calls the client method to submit the form to Spring MVC. For completeness, below is the controller’s submit method:

@RequestMapping(“/save.action”)
public String save(@ModelAttribute Customer customer, Model model) {
// implementation here
}

That’s it. It involves a bit of work to put the 2 frameworks together but may be useful if you have already had the web app implemented using Spring MVC and/or experienced in Spring MVC and just interested in using the rich UI and Ajax side of the ZK framework.

Searchable dropdown for Spring MVC/Hibernate web applications – Part I

Introduction

One common feature in a J2EE web application is to provide a dropdown box for user to select from a number of options. Typically, the selected option represents a key to the underlying domain object in the system. For example, a user may select a share code and the application will then display the detailed information of the chosen share by loading the share domain object using the value selected from the database.

In this  blog, I will demonstrate how to implement an end-to-end searchable dropdown solution for Spring MVC/Hibernate web applications. In this article, I will focus on building the backend codes required to provide a generic solution for generating option lists for any entities defined in the system. I will demonstrate how to build the searchable dropdown functions at the front-end in another blog.

Back-end solution design

1. Domain model

The domain model for the dropdown option is simple. It has 2 properties: name and value which correspond to the name and value attributes used in option elements in HTML:

public class DropdownOption {

private String name;

private String value;

public DropdownOption(String name, String value) {
this.name = name;
this.value = value;
}

2. Repository layer

The repository layer is where most of the implementation happens. There is only one method in the interface :

public interface IDropdownRepository {

List<DropdownOption> getOptions(String entityName, String nameProp, String valueProp, String filter);
}

The getOptions() method has 4 arguments:

  1. entityName – name of the entity to get values from
  2. nameProp – name of the entity’s property to use as the name of the dropdown option
  3. valueProp – name of the entity’s property to use as the value of the dropdown option
  4. filter – optional HQL string to filter search result

The implementation of IDropdownRepository using Spring Hibernate support:

@Repository
public class DropdownRepositoryImpl extends HibernateDaoSupport implements
IDropdownRepository {

@Autowired(required=true)
public DropdownRepositoryImpl(SessionFactory sessionFactory) {
super.setSessionFactory(sessionFactory);
}

public List<DropdownOption> getOptions(String entityName, String nameProp,
String valueProp, String filter) {
String where = StringUtils.isBlank(filter) ? “” : ” ” + filter;
 final String hql = “SELECT new com.rlee.myapp.domain.DropdownOption(” + nameProp + “,” + valueProp  + “) FROM ” + entityName + where;
List<DropdownOption> options = getHibernateTemplate().find(hql);
return options;
}

}

As shown in the codes above, the implementation constructs the proper HQL using the input arguments and then execute the query to get the result list. Note

  • the use of constructor of the DropdownOption(name,value) in the HQL SELECT statement to create the DropdownOption domain objects.
  • HQL functions can be used in either or both nameProp and valueProp values. E.g. it is possible to use concat() function to use values of multiple properties (i.e. multiple database columns) of the entity as the display option names.

3. Service layer

Typically, you would access repository via the service layer. In our case, the service just delegates the method to its repository.

Interface:

public interface IDropdownService {
List<DropdownOption> getOptions(String entityName, String nameProp, String valueProp, String filter);
}

Implementing class:

@Service
public class DropdownServiceImpl implements IDropdownService {

@Autowired(required=true)
private IDropdownRepository repository;

public List<DropdownOption> getOptions(String entityName, String nameProp,
String valueProp, String filter) {
return repository.getOptions(entityName, nameProp, valueProp, filter);
}

}

4. Controller

The dropdown option list is obtained via AJAX with requests with  address /dropdown/<entity>/get.action where <entity> is the name of the entity required:

@Controller
@RequestMapping(value = “/dropdown”)
public class DropdownController {

@Autowired(required = true)
private IDropdownService service;

@RequestMapping(value = “/{entity}/get.action”, method = RequestMethod.GET)
public @ResponseBody List<DropdownOption> getDropdownList(
@PathVariable(“entity”) String entity, HttpServletRequest request)
throws ServletRequestBindingException {
// Get name and value property from request
String nameProp = ServletRequestUtils.getRequiredStringParameter(
request, “nameProp”);
String valueProp = ServletRequestUtils.getRequiredStringParameter(
request, “valueProp”);
String filter = ServletRequestUtils.getStringParameter(request,
“filter”);
return service.getOptions(entity, nameProp, valueProp, filter);
}

}

Using Spring MVC JSON support, the method getDropdownList() will return the result as the JSON object representing the underlying list of DropdownOption objects, which can then be easily converted into the corresponding HTML option elements.

That’s it for now. I will show how to use the codes above to implement AJAX based searchable dropdown in the front-end.