Creating a Simple Java REST Service Using HTTP-RPC
We take a look at how to implement a Java-based web service using the HTTP-RPC framework. Read on to find out how and for some example code.
Join the DZone community and get the full member experience.
Join For FreeNOTE 10/22/2016 Support for implementing REST services was dropped from the HTTP-RPC project in version 3.6. However, many of the features described in this article have been migrated to the JTemplate project.
HTTP-RPC is an open-source framework for simplifying development of REST applications. It allows developers to create and access web services using a convenient, RPC-like metaphor while preserving fundamental REST principles such as statelessness and uniform resource access. The project currently includes support for implementing REST services in Java and consuming services in Java, Objective-C/Swift, or JavaScript.
HTTP-RPC services are accessed by applying an HTTP verb such as GET
or POST
to a target resource. Arguments are provided either via the query string or in the request body, like an HTML form. Results are generally returned as JSON, although operations that do not return a value are also supported.
For example, the following request might retrieve the sum of two numbers, whose values are specified by the a
and b
query arguments:
GET /math/sum?a=2&b=4
The service would return the value 6 in response.
This article provides an overview of how HTTP-RPC can be used to create a simple REST service.
WebService Class
WebService
is an abstract base class for HTTP-RPC web services. Service operations are defined by adding public methods to a concrete service implementation.
The @RPC
annotation is used to flag a method as remotely accessible. This annotation associates an HTTP verb and a resource path with the method. All public annotated methods automatically become available for remote execution when the service is published.
For example, the following class might be used to implement the simple addition operation discussed in the previous section:
public class MathService extends WebService {
@RPC(method="GET", path="sum")
public double getSum(double a, double b) {
return a + b;
}
}
Arguments may be any numeric or boolean type, String
, java.net.URL
, or java.util.List
. URL
arguments represent binary content and can only be used with POST requests. List
arguments represent multi-value parameters. They may be used with any request type, but elements must be a supported simple type; e.g. List<Double>
or List<URL>
.
Methods may return any numeric or boolean type, CharSequence
, java.util.List
or java.util.Map
. Results are mapped to their JSON equivalents as follows:
- numeric primitive/
Number
: number boolean/Boolean
: true/falseCharSequence
: stringjava.util.List
: arrayjava.util.Map
: object
Methods may also return void
to indicate that they do not produce a value.
List
and Map
types are not required to support random access; iterability is sufficient. This allows service implementations to stream collection data rather than buffering it in memory before it is written. Additionally, collection types that implement the AutoCloseable
interface will be automatically closed after their contents have been written to the output stream, ensuring that system resources are not leaked.
ProductService Class
The example service provides CRUD (“create, read, update, and delete”) access to a simple product database. It extends WebService
and provides a collection of methods for managing the database’s content:
public class ProductService extends WebService {
...
}
The underlying product data is stored in the Products
table from the BIRT sample database:
CREATE TABLE Products (
productCode VARCHAR(50) NOT NULL,
productName VARCHAR(70) NOT NULL,
productLine VARCHAR(50) NOT NULL,
productScale VARCHAR(10) NOT NULL,
productVendor VARCHAR(50) NOT NULL,
productDescription TEXT NOT NULL,
quantityInStock SMALLINT NOT NULL,
buyPrice DOUBLE NOT NULL,
MSRP DOUBLE NOT NULL,
PRIMARY KEY (productCode)
);
GET
The getProducts()
method returns the product list. It is associated with the HTTP GET operation:
@RPC(method="GET", path="products")
public ResultSetAdapter getProducts() throws SQLException {
Statement statement = getConnection().createStatement();
return new ResultSetAdapter(statement.executeQuery("SELECT * FROM Products"));
}
The ResultSetAdapter
class allows the result of a SQL query to be efficiently returned from a service method. This class implements the List
interface and makes each row in a JDBC result set appear as an instance of Map
, rendering the data suitable for serialization to JSON. It also implements AutoCloseable
, to ensure that the underlying result set is closed once all of the response data has been written.
Further, ResultSetAdapter
is forward-scrolling only; its contents are not accessible via the get()
and size()
methods. This allows query results to be returned to the caller directly, without any intermediate buffering.
A response produced by the method might look something like this, where each object in the array represents a row from the result set:
[
{
"productCode": "S10_1678",
"productName": "1969 Harley Davidson Ultimate Chopper",
"productLine": "Motorcycles",
"productScale": "1:10",
"productVendor": "Min Lin Diecast",
"productDescription": "This replica features working kickstand...",
"quantityInStock": 7932,
"buyPrice": 48.81,
"MSRP": 95.7
},
...
]
POST
The addProduct()
method is associated with the HTTP POST operation. It inserts a new row into the Products
table. Product information is provided by the method arguments:
@RPC(method="POST", path="products")
public void addProduct(String productCode, String productName,
String productLine, String productScale, String productVendor, String productDescription,
int quantityInStock, double buyPrice, double MSRP) throws SQLException {
String sql = "INSERT INTO Products (productCode, productName,"
+ " productLine, productScale, productVendor, productDescription,"
+ " quantityInStock, buyPrice, MSRP) "
+ "VALUES (:productCode, :productName,"
+ " :productLine, :productScale, :productVendor, :productDescription,"
+ " :quantityInStock, :buyPrice, :MSRP)";
Parameters parameters = Parameters.parse(sql);
PreparedStatement statement = getConnection().prepareStatement(parameters.getSQL());
parameters.apply(statement, mapOf(
entry("productCode", productCode),
entry("productName", productName),
entry("productLine", productLine),
entry("productScale", productScale),
entry("productVendor", productVendor),
entry("productDescription", productDescription),
entry("quantityInStock", quantityInStock),
entry("buyPrice", buyPrice),
entry("MSRP", MSRP)
));
statement.execute();
}
The Parameters
class provides a means for executing prepared statements using JPQL-like named parameter values rather than indexed arguments. The Parameters#getSQL()
method returns the parsed SQL in standard JDBC syntax. This value is used to create the actual prepared statement.
mapOf()
and entry()
are static convenience methods provided by the WebService
class for simplifying map creation.
PUT
The updateProduct()
method is associated with the HTTP PUT operation. It allows a caller to update the inventory level of a given product:
@RPC(method="PUT", path="products")
public void updateProduct(String productCode, int quantityInStock) throws SQLException {
String sql = "UPDATE Products SET quantityInStock = :quantityInStock WHERE productCode = :productCode";
Parameters parameters = Parameters.parse(sql);
PreparedStatement statement = getConnection().prepareStatement(parameters.getSQL());
parameters.apply(statement, mapOf(
entry("productCode", productCode),
entry("quantityInStock", quantityInStock)
));
statement.execute();
}
This method could be overloaded to provide additional update capabilities, such as changing a price or product description.
DELETE
Finally, deleteProduct()
is associated with the HTTP DELETE method. It simply deletes a row from the Products
table:
@RPC(method="DELETE", path="products")
public void deleteProduct(String productCode) throws SQLException {
String sql = "DELETE FROM Products WHERE productCode = :productCode";
Parameters parameters = Parameters.parse(sql);
PreparedStatement statement = getConnection().prepareStatement(parameters.getSQL());
parameters.apply(statement, mapOf(entry("productCode", productCode)));
statement.execute();
}
More Information
This article introduced the HTTP-RPC framework and provided an example of how it can be used to create a simple REST service. The complete source code for the sample service can be found here.
The latest version of HTTP-RPC can be downloaded here. For more information, see the project README.
Published at DZone with permission of Greg Brown, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments