Spring Boot is a powerful jump start into Spring. It allows you to build powerful applications with production grade services with little effort on your part.
Aerospike is a high available, low latency NoSQL database that scales linearly. It is an in-memory database optimized to use both DRAM and native Flash.
Aerospike boasts latencies of 1 to 3 ms consistently across throughput loads on a correctly sized cluster. Aerospike also has high reliability and strong consistency with no downtime.
Our oldest customer has many terabytes of data and has never been offline, even during Hurricane Sandy in New York City where one of their data centers went down from the storm.
What you will build
This guide will take you through creating a simple RESTful web service with Spring Boot. You will build a service that accepts an HTTP GET request:
http://localhost:8080/as/test/flights/getAll/1234
It responds with the following JSON:
{
"expiration": 121023390,
"bins": {
"DISTANCE": 2446,
"DEST_CITY_NAME": "New York",
"DEST": "JFK",
"YEAR": 2012,
"ORI_AIRPORT_ID": "14679",
"DEP_TIME": "802",
"DAY_OF_MONTH": 12,
"DEST_STATE_ABR": "NY",
"ORIGIN": "SAN",
"FL_NUM": 160,
"CARRIER": "AA",
"ORI_STATE_ABR": "CA",
"FL_DATE": "2012/01/12",
"AIR_TIME": 291,
"ORI_CITY_NAME": "San Diego",
"ELAPSED_TIME": 321,
"ARR_TIME": "1623",
"AIRLINE_ID": 19805
},
"generation": 1
}
The data you will use is commercial flight details, included in the GitHub repository is a zip file named .zip. Unzip this file and you will find a file called flights_from.csv. It contains approximately 1 million flight records.
There are also many features added to your application out-of-the-box for managing the service in a production (or other) environment. This functionally comes from Spring, see the Spring guide: Building a RESTful web service.
What you will need
About 30 minutes
A favorite text editor or IDE
JDK 7 or later
Set up the project
You can use any build system you like when building applications with Spring, but the Maven code is included here. If you are unfamiliar with Maven refer to the Spring guide: Building Java Projects with Maven.
You will also need to build and install the Aerospike Java client into your local Maven repository. Download the source distribution, unzip/untar it and run the following Maven commands:
mvn install:install-file -Dfile=client/depends/gnu-crypto.jar -DgroupId=org.gnu -DartifactId=gnu-crypto -Dversion=2.0.1 -Dpackaging=jar
mvn clean
mvn package
Create the directory structure
In the project of your choosing, create the following subdirectory structure:
↳src
↳main
↳java
↳com
↳aerospike
↳client
↳rest
Create a Maven pom
Create a maven pom.xml file in the root directory of your project with the following code:
4.0.0
com.aerospike
aerospike-restful-example
1.0.0
org.springframework.boot
spring-boot-starter-parent
0.5.0.M4
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
com.aerospike
aerospike-client
3.0.9
commons-cli
commons-cli
1.2
com.aerospike.client.rest.AerospikeRESTfulService
maven-compiler-plugin
2.3.2
org.springframework.boot
spring-boot-maven-plugin
spring-snapshots
Spring Snapshots
http://repo.spring.io/libs-snapshot
true
spring-snapshots
Spring Snapshots
http://repo.spring.io/libs-snapshot
true
It looks scary, but it really isn’t.
Create a JSON translator class
The Aerospike API will return a Record object and it will contain the generation, expiry and bin values of the record. But you want to have these values returned in JSON format. The easiest way to achieve this is to use a translator class.
Create a translator class with the following code. It is a generic class that will translate an Aerospike Record object to a JSONObject.
src/main/java/com/aerospike/client/rest/JSONRecord.java
package com.aerospike.client.rest;
import java.util.Map;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.aerospike.client.Record;
/**
* JSONRecord is used to convert an Aerospike Record
* returned from the cluster to JSON format
*
*/
@SuppressWarnings("serial")
public class JSONRecord extends JSONObject {
@SuppressWarnings("unchecked")
public JSONRecord(Record record){
put("generation", record.generation);
put("expiration", record.expiration);
put("bins", new JSONObject(record.bins));
if (record.duplicates != null){
JSONArray duplicates = new JSONArray();
for (Map<String, Object> duplicate : record.duplicates){
duplicates.add(new JSONObject(duplicate));
}
put("duplicates", duplicates);
}
}
}
This class is not complicated and is very generic. You may wish to specialize your JSON translation for specific records.
Create a resource controller
In Spring, REST endpoints are Spring MVC controllers. The following code handles a GET request for /as/{namespace}/{set}/getAll/1234
and returns the Flight record whose key is 1234
, where {namespace}
is the path variable for the Aerospike namespace and {set}
is the path variable for the Aerospike set.
package com.aerospike.client.rest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
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.multipart.MultipartFile;
import com.aerospike.client.AerospikeClient;
import com.aerospike.client.Bin;
import com.aerospike.client.Key;
import com.aerospike.client.Record;
import com.aerospike.client.policy.Policy;
import com.aerospike.client.policy.WritePolicy;
@Controller
public class RESTController {
@Autowired
AerospikeClient client;
@RequestMapping(value="/as/{namespace}/{set}/getAll/{key}", method=RequestMethod.GET)
public @ResponseBody JSONRecord getAll(@PathVariable("namespace") String namespace,
@PathVariable("set") String set,
@PathVariable("key") String keyvalue) throws Exception {
Policy policy = new Policy();
Key key = new Key(namespace, set, keyvalue);
Record result = client.get(policy, key);
return new JSONRecord(result);
}
}
The difference between a human-facing controller and a REST endpoint controller is the the response body will contain data, in your case a JSON object that represents the record read from Aerospike.
The @ResponseBody
annotation tells Spring MVC to write the returned object into the response body.
Create an executable main class
Implement the main method to create a Spring MVC controller. The easiest way to do this is to use the SpringApplication
helper class.
src/main/java/com/aerospike/client/rest/AerospikeRESTfulService.java
package com.aerospike.client.rest;
import java.util.Properties;
import javax.servlet.MultipartConfigElement;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import com.aerospike.client.AerospikeClient;
import com.aerospike.client.AerospikeException;
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class AerospikeRESTfulService {
@Bean
public AerospikeClient asClient() throws AerospikeException {
Properties as = System.getProperties();
return new AerospikeClient(as.getProperty("seedHost"),
Integer.parseInt(as.getProperty("port")));
}
@Bean
public MultipartConfigElement multipartConfigElement() {
return new MultipartConfigElement("");
}
public static void main(String[] args) throws ParseException {
Options options = new Options();
options.addOption("h", "host", true,
"Server hostname (default: localhost)");
options.addOption("p", "port", true, "Server port (default: 3000)");
// parse the command line args
CommandLineParser parser = new PosixParser();
CommandLine cl = parser.parse(options, args, false);
// set properties
Properties as = System.getProperties();
String host = cl.getOptionValue("h", "localhost");
as.put("seedHost", host);
String portString = cl.getOptionValue("p", "3000");
as.put("port", portString);
// start app
SpringApplication.run(AerospikeRESTfulService.class, args);
}
}
The @EnableAutoConfiguration annotation has been added: it provides a load of defaults (like the embedded servlet container) depending on the contents of your classpath, and other things.
It is also annotated with @ComponentScan, which tells Spring to scan the hello package for those controllers (along with any other annotated component classes).
Finally this class is annotated with @Configuration. This allows you to configure an instance of the AerospikeClient as a Spring bean.
There is also a MultipartConfigElement
bean defined. This allows you to process POST operations with this service.
Most of the body of the main method simply reads command line arguments and sets system properties to specify the seed host and port of the Aerospike cluster.
Too easy!
Uploading data
You may want to upload data to this service. To do this you need to add an additional method to the RESTController class to process the uploaded file. In this example, it will be a CSV file containing flight records.
src/main/java/com/aerospike/client/rest/RESTController.java
@Controller
public class RESTController {
. . . (code omitted) . . .
/*
* CSV flights file upload
*/
@RequestMapping(value="/uploadFlights", method=RequestMethod.GET)
public @ResponseBody String provideUploadInfo() {
return "You can upload a file by posting to this same URL.";
}
@RequestMapping(value="/uploadFlights", method=RequestMethod.POST)
public @ResponseBody String handleFileUpload(@RequestParam("name") String name,
@RequestParam("file") MultipartFile file){
if (!file.isEmpty()) {
try {
WritePolicy wp = new WritePolicy();
String line = "";
BufferedReader br = new BufferedReader(new InputStreamReader(file.getInputStream()));
while ((line = br.readLine()) != null) {
// use comma as separator
String[] flight = line.split(",");
/*
* write the record to Aerospike
* NOTE: Bin names must not exceed 14 characters
*/
client.put(wp,
new Key("test", "flights",flight[0].trim() ),
new Bin("YEAR", Integer.parseInt(flight[1].trim())),
new Bin("DAY_OF_MONTH", Integer.parseInt(flight[2].trim())),
new Bin("FL_DATE", flight[3].trim()),
new Bin("AIRLINE_ID", Integer.parseInt(flight[4].trim())),
new Bin("CARRIER", flight[5].trim()),
new Bin("FL_NUM", Integer.parseInt(flight[6].trim())),
new Bin("ORI_AIRPORT_ID", Integer.parseInt(flight[7].trim())),
new Bin("ORIGIN", flight[8].trim()),
new Bin("ORI_CITY_NAME", flight[9].trim()),
new Bin("ORI_STATE_ABR", flight[10].trim()),
new Bin("DEST", flight[11].trim()),
new Bin("DEST_CITY_NAME", flight[12].trim()),
new Bin("DEST_STATE_ABR", flight[13].trim()),
new Bin("DEP_TIME", Integer.parseInt(flight[14].trim())),
new Bin("ARR_TIME", Integer.parseInt(flight[15].trim())),
new Bin("ELAPSED_TIME", Integer.parseInt(flight[16].trim())),
new Bin("AIR_TIME", Integer.parseInt(flight[17].trim())),
new Bin("DISTANCE", Integer.parseInt(flight[18].trim()))
);
System.out.println("Flight [ID= " + flight[0]
+ " , year=" + flight[1]
+ " , DAY_OF_MONTH=" + flight[2]
+ " , FL_DATE=" + flight[3]
+ " , AIRLINE_ID=" + flight[4]
+ " , CARRIER=" + flight[5]
+ " , FL_NUM=" + flight[6]
+ " , ORIGIN_AIRPORT_ID=" + flight[7]
+ "]");
}
br.close();
return "You successfully uploaded " + name;
} catch (Exception e) {
return "You failed to upload " + name + " => " + e.getMessage();
}
} else {
return "You failed to upload " + name + " because the file was empty.";
}
}
}
A new method handleFileUpload()
responds to a POST and reads the upload stream one line at a time. Each line is parsed and a Key object and several Bin objects are built to form the Aerospike record. Finally the Aerospike put()
method is called to store the record in the Aerospike cluster.
Another new method provideUploadInfo()
responds to a GET and returns a message indicating uploads are possible.
Uploading client application
Uploading can be done anyway you desire. But you can use the following stand alone java class to upload data to this service.
src/test/java/com.aerospike.client.rest/FlightsUploader.java
package com.aerospike.client.rest;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.FileSystemResource;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
public class FilghtsUploader {
private static final String TEST_FILE = "flights_from.csv";
@Before
public void setUp() throws Exception {
}
@Test
public void upload() {
RestTemplate template = new RestTemplate();
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
parts.add("name", TEST_FILE);
parts.add("file", new FileSystemResource(TEST_FILE));
String response = template.postForObject("http://localhost:8080/uploadFlights",
parts, String.class);
System.out.println(response);
}
}
Flights Data
This is real data from 2012. It contains approximately 1 million records, so remember that it will take a few minutes to upload.
Building and running the Service
The Maven pom.xml will package the service into a single jar. Use the command:
mvn clean package
This will generate a stand alone web service application packaged into a runnable jar file in the target subdirectory. This jar file includes an instance of Tomcat, so you can simply run the jar without installing it in an Application Server.
java -jar aerospike-restful-example-1.0.0.jar
Summary
Congratulations! You have just developed a simple RESTful service using Spring and connecting it to an Aerospike cluster.
Complete Example Code
Spring Boot is a powerful jump start into Spring. It allows you to build powerful applications with production grade services with little effort on your part.Aerospike is a high available, low latency NoSQL database that scales linearly. It is an in-memory database optimized to use both DRAM and native Flash.Aerospike boasts latencies of 1 to 3 ms consistently across throughput loads on a correctly sized cluster. Aerospike also has high reliability and strong consistency with no downtime.
Our oldest customer has many terabytes of data and has never been offline, even during Hurricane Sandy in New York City where one of their data centers went down from the storm.
What you will build
This guide will take you through creating a simple RESTful web service with Spring Boot. You will build a service that accepts an HTTP GET request:
http://localhost:8080/as/test/flights/getAll/1234
It responds with the following JSON:
{
"expiration": 121023390,
"bins": {
"DISTANCE": 2446,
"DEST_CITY_NAME": "New York",
"DEST": "JFK",
"YEAR": 2012,
"ORI_AIRPORT_ID": "14679",
"DEP_TIME": "802",
"DAY_OF_MONTH": 12,
"DEST_STATE_ABR": "NY",
"ORIGIN": "SAN",
"FL_NUM": 160,
"CARRIER": "AA",
"ORI_STATE_ABR": "CA",
"FL_DATE": "2012/01/12",
"AIR_TIME": 291,
"ORI_CITY_NAME": "San Diego",
"ELAPSED_TIME": 321,
"ARR_TIME": "1623",
"AIRLINE_ID": 19805
},
"generation": 1
}
The data you will use is commercial flight details, included in the GitHub repository is a zip file named .zip. Unzip this file and you will find a file called flights_from.csv. It contains approximately 1 million flight records.
There are also many features added to your application out-of-the-box for managing the service in a production (or other) environment. This functionally comes from Spring, see the Spring guide: Building a RESTful web service.
What you will need
About 30 minutes
A favorite text editor or IDE
JDK 7 or later
Set up the project
You can use any build system you like when building applications with Spring, but the Maven code is included here. If you are unfamiliar with Maven refer to the Spring guide: Building Java Projects with Maven.
You will also need to build and install the Aerospike Java client into your local Maven repository. Download the source distribution, unzip/untar it and run the following Maven commands:
mvn install:install-file -Dfile=client/depends/gnu-crypto.jar -DgroupId=org.gnu -DartifactId=gnu-crypto -Dversion=2.0.1 -Dpackaging=jar
mvn clean
mvn package
Create the directory structure
In the project of your choosing, create the following subdirectory structure:
↳src
↳main
↳java
↳com
↳aerospike
↳client
↳rest
Create a Maven pom
Create a maven pom.xml file in the root directory of your project with the following code:
4.0.0
com.aerospike
aerospike-restful-example
1.0.0
org.springframework.boot
spring-boot-starter-parent
0.5.0.M4
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
com.aerospike
aerospike-client
3.0.9
commons-cli
commons-cli
1.2
com.aerospike.client.rest.AerospikeRESTfulService
maven-compiler-plugin
2.3.2
org.springframework.boot
spring-boot-maven-plugin
spring-snapshots
Spring Snapshots
http://repo.spring.io/libs-snapshot
true
spring-snapshots
Spring Snapshots
http://repo.spring.io/libs-snapshot
true
It looks scary, but it really isn’t.
Create a JSON translator class
The Aerospike API will return a Record object and it will contain the generation, expiry and bin values of the record. But you want to have these values returned in JSON format. The easiest way to achieve this is to use a translator class.
Create a translator class with the following code. It is a generic class that will translate an Aerospike Record object to a JSONObject.
src/main/java/com/aerospike/client/rest/JSONRecord.java
package com.aerospike.client.rest;
import java.util.Map;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.aerospike.client.Record;
/**
* JSONRecord is used to convert an Aerospike Record
* returned from the cluster to JSON format
*
*/
@SuppressWarnings("serial")
public class JSONRecord extends JSONObject {
@SuppressWarnings("unchecked")
public JSONRecord(Record record){
put("generation", record.generation);
put("expiration", record.expiration);
put("bins", new JSONObject(record.bins));
if (record.duplicates != null){
JSONArray duplicates = new JSONArray();
for (Map<String, Object> duplicate : record.duplicates){
duplicates.add(new JSONObject(duplicate));
}
put("duplicates", duplicates);
}
}
}
This class is not complicated and is very generic. You may wish to specialize your JSON translation for specific records.
Create a resource controller
In Spring, REST endpoints are Spring MVC controllers. The following code handles a GET request for /as/{namespace}/{set}/getAll/1234
and returns the Flight record whose key is 1234
, where {namespace}
is the path variable for the Aerospike namespace and {set}
is the path variable for the Aerospike set.
package com.aerospike.client.rest; import java.io.BufferedReader; import java.io.InputStreamReader; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; 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.multipart.MultipartFile; import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.policy.Policy; import com.aerospike.client.policy.WritePolicy; @Controller public class RESTController { @Autowired AerospikeClient client; @RequestMapping(value="/as/{namespace}/{set}/getAll/{key}", method=RequestMethod.GET) public @ResponseBody JSONRecord getAll(@PathVariable("namespace") String namespace, @PathVariable("set") String set, @PathVariable("key") String keyvalue) throws Exception { Policy policy = new Policy(); Key key = new Key(namespace, set, keyvalue); Record result = client.get(policy, key); return new JSONRecord(result); } }
The difference between a human-facing controller and a REST endpoint controller is the the response body will contain data, in your case a JSON object that represents the record read from Aerospike.
The @ResponseBody
annotation tells Spring MVC to write the returned object into the response body.
Create an executable main class
Implement the main method to create a Spring MVC controller. The easiest way to do this is to use the SpringApplication
helper class.
src/main/java/com/aerospike/client/rest/AerospikeRESTfulService.java
package com.aerospike.client.rest;
import java.util.Properties;
import javax.servlet.MultipartConfigElement;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import com.aerospike.client.AerospikeClient;
import com.aerospike.client.AerospikeException;
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class AerospikeRESTfulService {
@Bean
public AerospikeClient asClient() throws AerospikeException {
Properties as = System.getProperties();
return new AerospikeClient(as.getProperty("seedHost"),
Integer.parseInt(as.getProperty("port")));
}
@Bean
public MultipartConfigElement multipartConfigElement() {
return new MultipartConfigElement("");
}
public static void main(String[] args) throws ParseException {
Options options = new Options();
options.addOption("h", "host", true,
"Server hostname (default: localhost)");
options.addOption("p", "port", true, "Server port (default: 3000)");
// parse the command line args
CommandLineParser parser = new PosixParser();
CommandLine cl = parser.parse(options, args, false);
// set properties
Properties as = System.getProperties();
String host = cl.getOptionValue("h", "localhost");
as.put("seedHost", host);
String portString = cl.getOptionValue("p", "3000");
as.put("port", portString);
// start app
SpringApplication.run(AerospikeRESTfulService.class, args);
}
}
The @EnableAutoConfiguration annotation has been added: it provides a load of defaults (like the embedded servlet container) depending on the contents of your classpath, and other things.
It is also annotated with @ComponentScan, which tells Spring to scan the hello package for those controllers (along with any other annotated component classes).
Finally this class is annotated with @Configuration. This allows you to configure an instance of the AerospikeClient as a Spring bean.
There is also a MultipartConfigElement
bean defined. This allows you to process POST operations with this service.
Most of the body of the main method simply reads command line arguments and sets system properties to specify the seed host and port of the Aerospike cluster.
Too easy!
Uploading data
You may want to upload data to this service. To do this you need to add an additional method to the RESTController class to process the uploaded file. In this example, it will be a CSV file containing flight records.
src/main/java/com/aerospike/client/rest/RESTController.java
@Controller
public class RESTController {
. . . (code omitted) . . .
/*
* CSV flights file upload
*/
@RequestMapping(value="/uploadFlights", method=RequestMethod.GET)
public @ResponseBody String provideUploadInfo() {
return "You can upload a file by posting to this same URL.";
}
@RequestMapping(value="/uploadFlights", method=RequestMethod.POST)
public @ResponseBody String handleFileUpload(@RequestParam("name") String name,
@RequestParam("file") MultipartFile file){
if (!file.isEmpty()) {
try {
WritePolicy wp = new WritePolicy();
String line = "";
BufferedReader br = new BufferedReader(new InputStreamReader(file.getInputStream()));
while ((line = br.readLine()) != null) {
// use comma as separator
String[] flight = line.split(",");
/*
* write the record to Aerospike
* NOTE: Bin names must not exceed 14 characters
*/
client.put(wp,
new Key("test", "flights",flight[0].trim() ),
new Bin("YEAR", Integer.parseInt(flight[1].trim())),
new Bin("DAY_OF_MONTH", Integer.parseInt(flight[2].trim())),
new Bin("FL_DATE", flight[3].trim()),
new Bin("AIRLINE_ID", Integer.parseInt(flight[4].trim())),
new Bin("CARRIER", flight[5].trim()),
new Bin("FL_NUM", Integer.parseInt(flight[6].trim())),
new Bin("ORI_AIRPORT_ID", Integer.parseInt(flight[7].trim())),
new Bin("ORIGIN", flight[8].trim()),
new Bin("ORI_CITY_NAME", flight[9].trim()),
new Bin("ORI_STATE_ABR", flight[10].trim()),
new Bin("DEST", flight[11].trim()),
new Bin("DEST_CITY_NAME", flight[12].trim()),
new Bin("DEST_STATE_ABR", flight[13].trim()),
new Bin("DEP_TIME", Integer.parseInt(flight[14].trim())),
new Bin("ARR_TIME", Integer.parseInt(flight[15].trim())),
new Bin("ELAPSED_TIME", Integer.parseInt(flight[16].trim())),
new Bin("AIR_TIME", Integer.parseInt(flight[17].trim())),
new Bin("DISTANCE", Integer.parseInt(flight[18].trim()))
);
System.out.println("Flight [ID= " + flight[0]
+ " , year=" + flight[1]
+ " , DAY_OF_MONTH=" + flight[2]
+ " , FL_DATE=" + flight[3]
+ " , AIRLINE_ID=" + flight[4]
+ " , CARRIER=" + flight[5]
+ " , FL_NUM=" + flight[6]
+ " , ORIGIN_AIRPORT_ID=" + flight[7]
+ "]");
}
br.close();
return "You successfully uploaded " + name;
} catch (Exception e) {
return "You failed to upload " + name + " => " + e.getMessage();
}
} else {
return "You failed to upload " + name + " because the file was empty.";
}
}
}
A new method handleFileUpload()
responds to a POST and reads the upload stream one line at a time. Each line is parsed and a Key object and several Bin objects are built to form the Aerospike record. Finally the Aerospike put()
method is called to store the record in the Aerospike cluster.
Another new method provideUploadInfo()
responds to a GET and returns a message indicating uploads are possible.
Uploading client application
Uploading can be done anyway you desire. But you can use the following stand alone java class to upload data to this service.
src/test/java/com.aerospike.client.rest/FlightsUploader.java
package com.aerospike.client.rest;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.FileSystemResource;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
public class FilghtsUploader {
private static final String TEST_FILE = "flights_from.csv";
@Before
public void setUp() throws Exception {
}
@Test
public void upload() {
RestTemplate template = new RestTemplate();
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
parts.add("name", TEST_FILE);
parts.add("file", new FileSystemResource(TEST_FILE));
String response = template.postForObject("http://localhost:8080/uploadFlights",
parts, String.class);
System.out.println(response);
}
}
Flights Data
This is real data from 2012. It contains approximately 1 million records, so remember that it will take a few minutes to upload.
Building and running the Service
The Maven pom.xml will package the service into a single jar. Use the command:
mvn clean package
This will generate a stand alone web service application packaged into a runnable jar file in the target subdirectory. This jar file includes an instance of Tomcat, so you can simply run the jar without installing it in an Application Server.
java -jar aerospike-restful-example-1.0.0.jar
Summary
Congratulations! You have just developed a simple RESTful service using Spring and connecting it to an Aerospike cluster.
Complete Example Code