Implementing APIs to support autocomplete with Spring and Elasticsearch

Autocomplete is a common feature where a user can enter a few characters in a text box and the application can then use them to provide a list of alternative values (suggestions). The user can then select the correct one, typically using a drop down box, without having to type the full text. Example usage would include an e-commerce shopping cart where a customer is required to enter her address for delivery.

This post will demonstrate how to implement the backend support for autocomplete. In particular, APIs for returning suggestions based on input search string using Spring Boot (MVC) and Elasticsearch High Level Java Rest Client.

Project Setup

Below is the versions of Spring Boot and Elasticsearch used:

  • Spring Boot – version 2.0.4.RELEASE
  • Elasticsearch Java High Level REST Client – version 6.3.2

Maven

...
	<properties>
                ...
		<elasticsearch.version>6.3.2</elasticsearch.version>
	</properties>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.elasticsearch.client</groupId>
		<artifactId>elasticsearch-rest-high-level-client</artifactId>
		<version>${elasticsearch.version}</version>
	</dependency>
	<dependency>
		<groupId>org.elasticsearch.client</groupId>
		<artifactId>elasticsearch-rest-client</artifactId>
		<version>${elasticsearch.version}</version>
	</dependency>
	<dependency>
		<groupId>org.elasticsearch</groupId>
		<artifactId>elasticsearch</artifactId>
	</dependency>

Autocomplete APIs

We are going to implement an autocomplete feature for an input field of an address type. Spring MVC is used for the APIs and they will accept a text in the input request and return in the response a list of full address suggestions.

Completion Suggester

This is the standard way to implement type-as-you-go autocomplete with Elasticsearch. Below is the codes to construct the query using Elasticsearch Java REST client:

 

@Override
public SearchResultDto autocomplete(String prefixString, int size) {
     SearchRequest searchRequest = new SearchRequest(INDEX);
     CompletionSuggestionBuilder suggestBuilder = new CompletionSuggestionBuilder(FIELD_COMPLETION); // Note 1

     suggestBuilder.size(size)
                   .prefix(prefixString, Fuzziness.ONE) // Note 2
                   .skipDuplicates(true)
                   .analyzer("standard");
 
     SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // _search
     sourceBuilder.suggest(new SuggestBuilder().addSuggestion(SUGGESTION_NAME, suggestBuilder));
     searchRequest.source(sourceBuilder);

     SearchResponse response;
     try {
          response = client.search(searchRequest);
          return getSuggestions(response); // Note 3
     } catch (IOException ex) {
          logger.error("Error in autocomplete search", ex);
          throw new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR, "Error in ES search");
     }
}
  • Note 1: FIELD_COMPLETION is the name of the field to search. In order for the field to be used here, it has to be indexed with the completion type. For example, for the field named formattedAddress, the mapping setting for the index should look like below. FIELD_COMPLETION should then be “formattedAddress.completion”
    "mappings": {
...    
     "formattedAddress": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              },
              "completion": {
                "type": "completion",
                "analyzer": "standard",
                "preserve_separators": true,
                "preserve_position_increments": true,
                "max_input_length": 100
              }
            }
          },
...
  • Note 2: Fuzziness is set here to provide some leeway with typos. By default it’s 0
  • Note 3: To get the suggestions from the search response:
private SearchResultDto getSuggestions(SearchResponse response) {
	SearchResultDto dto = new SearchResultDto();
	Suggest suggest = response.getSuggest();
	Suggestion<Entry<Option>> suggestion = suggest.getSuggestion(SUGGESTION_NAME);
	for(Entry<Option> entry: suggestion.getEntries()) {
	      for (Option option: entry.getOptions()) {
	        dto.add(option.getText().toString());
	      }
	}
	return dto;
}

where SearchResultDto is just a wrapper class for list of suggestions

public class SearchResultDto {
     private List<String> suggestedAddresses;
...

Finally the Spring MVC controller for implementing the API

@RestController
@RequestMapping("/address")
@CrossOrigin
public class AddressController {

     @Autowired
     private AddressSearchService service;

     @GetMapping(params = {"type=autocomplete"})
     public SearchResultDto autocomplete(@RequestParam String search, @RequestParam(defaultValue = "20") int size) {
          return service.autocomplete(search, size);
     }
...

That’s it. As an example, a call to the API with search string “8 Rudd” would return a list of address below with the database I have. Note the fuzziness of the returned addresses, e.g. RUDA… vs RUDD

8 RUDALL STREET LATHAM ACT 2615
8 RUDD STREET CITY ACT 2601
8 RUDDER PLACE KAMBAH ACT 2902
8 RUNDLE PLACE KAMBAH ACT 2902
8 REDDALL CLOSE ISAACS ACT 2607

 

 

 

 

Advertisements

Building REST APIs with Spring Boot and SpringFox/Swagger

This blog post demonstrates step by step how to develop RESTful APIs using Spring Boot and then how to document and expose the API via Swagger (OpenAPI)  specification using SpringFox.

Project Setup

The project is a Spring Boot web application, e.g. created with Spring Initializr, with the following dependencies (in Maven):

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

I am using Spring Data backed by MongoDB data store here. We can also use a relational database such as MySQL instead.

To use SpringFox, we also need to add the following:

		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.9.2</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.9.2</version>
		</dependency>

The first dependency is required to add SpringFox support for Swagger 2 to the project. The second dependency adds swagger-ui, which is a GUI framework for displaying the API documentation as part of the web application.

Product API

We are going to build a very simple CRUD API for product resources.

@Document(collection = "product")
public class Product {

     @Id
     private String id;
     private String sku;
     private String description;
     private BigDecimal price;
...

The entity is to be persisted to a MongoDB collection product as denoted by the @Document annotation. This is implemented with Spring Data using the MongoRepository

// ProductRepository.java
import org.springframework.data.mongodb.repository.MongoRepository;
...

public interface ProductRepository extends MongoRepository<Product, String> {

}

The above repository is accessed by the controller via the service layer as defined by the following interface

public interface ProductService {
	Product read(String id);	
	Product update(String id, Product product);
	Product create(Product product);
	void delete(String id);
}

Finally, the rest controller implemented with Spring MVC

// ProductController.java - plain old Spring MVC
@RestController
@RequestMapping("/product")
public class ProductController {

	@Autowired
	private ProductService service;

	@PostMapping(value = "")
	public Product create(@RequestBody Product product) {
		return service.create(product);
	}

	@GetMapping(value = "/{id}")
	public Product read(@PathVariable("id") String id) {
		return service.get(id);
	}

	@PatchMapping(value = "/{id}")
	public Product update(@PathVariable("id") String id, @RequestBody Product product) {
		return service.update(id, product);
	}

	@DeleteMapping(value = "/{id}")
	public void delete(@PathVariable("id") String id) {
		service.delete(id);
	}
}

Swagger with SpringFox

So far, we have a standard Spring Boot web application which can serve the product API. To use Swagger with SpringFox, we will need to update the codes as follows:

Configuration

@Configuration
@EnableSwagger2
public class SpringFoxConfig {

     // Inject API info property values
     @Value("${api.title}")
     private String title;
     ...

     @Bean
     public Docket apiDocket() {
          //@formatter:off
          return new Docket(DocumentationType.SWAGGER_2)
                     .select()
                     .apis(RequestHandlerSelectors.basePackage("com.madman.bootswagger.controller"))
                     .paths(PathSelectors.any())
                     .build()
                     .apiInfo(getApiInfo());
          //@formatter:on
      }

     private ApiInfo getApiInfo() {
     //@formatter:off
          return new ApiInfo(
                     title,
                     description,
                     version,
                     termOfServiceUrl,
                     new Contact(contactName, contactUrl, contactEmail),
                     "LICENSE",
                     "LICENSE URL",
                     Collections.emptyList()
           );
      //@formatter:on
     }
}

The configuration setup is rather straight forward. All is needed is to declare the @EnableSwagger2 annotation to indicate Swagger support should be enabled and a Docket bean as builder into SpringFox. Note we restrict the apis scanning to the controller package.

ProductController with Swagger

The controller has been updated with SpringFox as below with the changes highlighted:

// ProductController.java with Swagger
@RestController
@RequestMapping("/product")
@Api(value = "products")
public class ProductController {

     @Autowired
     private ProductService service;

     @PostMapping(value = "/")
     public Product create(Product product) {
          return service.create(product);
     }

     @GetMapping(value = "/{id}")
     public Product read(@ApiParam(value = "product id", required = true) @PathVariable("id") String id) {
          return service.get(id);
     }

     @PatchMapping(value = "/{id}")
     public Product update(@ApiParam(value = "product id", required = true) @PathVariable("id") String id, 
                           @ApiParam(value = "product", required = true) @RequestBody Product product) {
          return service.update(id, product);
     }

     @DeleteMapping(value = "/{id}")
     public void delete(@ApiParam(value = "product id", required = true) @PathVariable("id") String id) {
          service.delete(id);
     }
}

The @Api annotation at the class level tells SpringFox to scan the class methods for API documentation.

Product model document

Finally, it is possible to annotate entity class to generate more appropriate documentation in Swagger using the @ApiModal and @ApiModelProperty annotations

@Document(collection = "product")
@ApiModel(description = "Product model")
public class Product {

@Id
@ApiModelProperty(value = "id in mongodb store", accessMode = AccessMode.READ_ONLY)
private String id;

@ApiModelProperty(value = "stock keeping unit")
private String sku;

@ApiModelProperty(value = "product description")
private String description;

@ApiModelProperty(value = "product base price")
private BigDecimal price;

Product API Specification

Now fire up Spring Boot and open the Swagger UI url (http://localhost:8080/swagger-ui.html) should display something like the screenshot below

blog_swagger

The above GUI display the API documentation as set by the annotations in the domain model and the controller. More importantly, the link http://localhost:8080/v2/api-docs provides the Swagger API specifications which the API clients can use in their development and testing.

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)

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>