In some use-cases, having a lean, single-tier server-side architecture is desireable. Typically, such architectures expose a RESTful API implementing client code and the UI using something like AngularJS.
In Java, the standard API for RESTful applications is JAX-RS, which is part of JEE 7, along with a standard JSON implementation. But you can use JAX-RS also outside of a JEE container. The following example shows how to set up a simple license server using these technologies:
- Maven for building and running
- Jetty as a lightweight Servlet implementation
- Jersey, the JAX-RS (JSR 311 & JSR 339) reference implementation
- jOOQ as a data access layer
For the example, we’ll use a PostgreSQL database.
Example code
Now, before you go and copy-paste all the code off this blog post, consider downloading it from here, instead:
https://github.com/jOOQ/jOOQ/tree/master/jOOQ-examples/jOOQ-jax-rs-example
The sample code is licensed under the terms of the Apache Software License 2.0.
Creating the license server database
We’ll keep the example simple and use a LICENSE
table to store all license keys and associated information, whereas a LOG_VERIFY
table is used to log access to the license server. Here’s the DDL:
CREATE TABLE LICENSE_SERVER.LICENSE (
ID SERIAL8 NOT NULL,
-- The date when the license was issued
LICENSE_DATE TIMESTAMP NOT NULL,
-- The e-mail address of the licensee
LICENSEE TEXT NOT NULL,
-- The license key
LICENSE TEXT NOT NULL,
-- The licensed version(s), a regular expression
VERSION VARCHAR(50) NOT NULL DEFAULT '.*',
CONSTRAINT PK_LICENSE PRIMARY KEY (ID),
CONSTRAINT UK_LICENSE UNIQUE (LICENSE)
);
CREATE TABLE LICENSE_SERVER.LOG_VERIFY (
ID SERIAL8 NOT NULL,
-- The licensee whose license is being verified
LICENSEE TEXT NOT NULL,
-- The license key that is being verified
LICENSE TEXT NOT NULL,
-- The request IP verifying the license
REQUEST_IP VARCHAR(50) NOT NULL,
-- The version that is being verified
VERSION VARCHAR(50) NOT NULL,
-- Whether the verification was successful
MATCH BOOLEAN NOT NULL,
CONSTRAINT PK_LOG_VERIFY PRIMARY KEY (ID)
);
To make things a bit more interesting (and secure), we’ll also push license key generation into the database, by generating it from a stored function as such:
CREATE OR REPLACE FUNCTION
LICENSE_SERVER.GENERATE_KEY(
IN license_date TIMESTAMP WITH TIME ZONE,
IN email TEXT
) RETURNS VARCHAR
AS $$
BEGIN
RETURN 'license-key';
END;
$$ LANGUAGE PLPGSQL;
The actual algorithm might be using a secret salt to hash the function arguments. For the sake of a tutorial, a constant string will suffice.
Setting up the project
We’re going to be setting up the jOOQ code generator using Maven
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.jooq</groupId>
<artifactId>jooq-webservices</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.26</version>
<configuration>
<reload>manual</reload>
<stopKey>stop</stopKey>
<stopPort>9966</stopPort>
</configuration>
</plugin>
<plugin>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<version>3.2.0</version>
<!-- See GitHub for details -->
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-spring</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.2-1003-jdbc4</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
</dependencies>
</project>
With the above setup, we’re now pretty ready to start developing our license service as a JAX-RS service.
The license service class
Once we’ve run the jOOQ code generator using Maven, we can write the following service class:
/**
* The license server.
*/
@Path("/license/")
@Component
@Scope("request")
public class LicenseService {
/**
* <code>/license/generate</code> generates
* and returns a new license key.
*
* @param mail The input email of the licensee.
*/
@GET
@Produces("text/plain")
@Path("/generate")
public String generate(
final @QueryParam("mail") String mail
) {
return run(new CtxRunnable() {
@Override
public String run(DSLContext ctx) {
Timestamp licenseDate = new Timestamp(
System.currentTimeMillis());
return
ctx.insertInto(LICENSE)
.set(LICENSE.LICENSE_, generateKey(
inline(licenseDate), inline(mail)))
.set(LICENSE.LICENSE_DATE, licenseDate)
.set(LICENSE.LICENSEE, mail)
.returning()
.fetchOne()
.getLicense();
}
});
}
/**
* <code>/license/verify</code> checks if a given
* licensee has access to version using a license.
*
* @param request
* The servlet request from the JAX-RS context.
* @param mail
* The input email address of the licensee.
* @param license
* The license used by the licensee.
* @param version
* The product version being accessed.
*/
@GET
@Produces("text/plain")
@Path("/verify")
public String verify(
final @Context HttpServletRequest request,
final @QueryParam("mail") String mail,
final @QueryParam("license") String license,
final @QueryParam("version") String version
) {
return run(new CtxRunnable() {
@Override
public String run(DSLContext ctx) {
String v = (version == null
|| version.equals(""))
? ""
: version;
return
ctx.insertInto(LOG_VERIFY)
.set(LOG_VERIFY.LICENSE, license)
.set(LOG_VERIFY.LICENSEE, mail)
.set(LOG_VERIFY.REQUEST_IP,
request.getRemoteAddr())
.set(LOG_VERIFY.MATCH, field(
selectCount()
.from(LICENSE)
.where(LICENSE.LICENSEE.eq(mail))
.and(LICENSE.LICENSE_.eq(license))
.and(val(v).likeRegex(LICENSE.VERSION))
.asField().gt(0)))
.set(LOG_VERIFY.VERSION, v)
.returning(LOG_VERIFY.MATCH)
.fetchOne()
.getValue(LOG_VERIFY.MATCH, String.class);
}
});
}
// [...]
}
The INSERT INTO LOG_VERIFY
query is actually rather interesting. In plain SQL, it would look like this:
INSERT INTO LOG_VERIFY (
LICENSE,
LICENSEE,
REQUEST_IP,
MATCH,
VERSION
)
VALUES (
:license,
:mail,
:remoteAddr,
(SELECT COUNT(*) FROM LICENSE
WHERE LICENSEE = :mail
AND LICENSE = :license
AND :version ~ VERSION) > 0,
:version
)
RETURNING MATCH;
Apart from the foregoing, the LicenseService
also contains a couple of simple utilities:
/**
* This method encapsulates a transaction and
* initialises a jOOQ DSLcontext. This could also be
* achieved with Spring and DBCP for connection
* pooling.
*/
private String run(CtxRunnable runnable) {
Connection c = null;
try {
Class.forName("org.postgresql.Driver");
c = getConnection(
"jdbc:postgresql:postgres",
"postgres",
System.getProperty("pw", "test"));
DSLContext ctx =
DSL.using(new DefaultConfiguration()
.set(new DefaultConnectionProvider(c))
.set(SQLDialect.POSTGRES)
.set(new Settings()
.withExecuteLogging(false)));
return runnable.run(ctx);
}
catch (Exception e) {
e.printStackTrace();
Response.status(Status.SERVICE_UNAVAILABLE);
return "Service Unavailable";
}
finally {
JDBCUtils.safeClose(c);
}
}
private interface CtxRunnable {
String run(DSLContext ctx);
}
Configuring Spring and Jetty
All we need now is to configure Spring…
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan
base-package="org.jooq.example.jaxrs" />
</beans>
… and Jetty …
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<servlet>
<servlet-name>Jersey Spring Web Application</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Spring Web Application</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
… and we’re done! We can now run the server with the following command:
mvn jetty:run
Or if you need a custom port:
mvn jetty:run -Djetty.port=8088
Using the license server
You can now use the license server at the following URLs
http://localhost:8088/jooq-jax-rs-example/license/generate?mail=test@example.com -> license-key http://localhost:8088/jooq-jax-rs-example/license/verify?mail=test@example.com&license=license-key&version=3.2.0 -> true http://localhost:8088/jooq-jax-rs-example/license/verify?mail=test@example.com&license=wrong&version=3.2.0 -> false
Let’s verify what happened, in the database:
select * from license_server.license -- id | licensee | license | version ------------------------------------------------ -- 3 | test@example.com | license-key | .* select * from license_server.log_verify -- id | licensee | license | match ---------------------------------------------- -- 2 | test@example.com | license-key | t -- 5 | test@example.com | wrong | f
Downloading the complete example
The complete example can be downloaded for free and under the terms of the Apache Software License 2.0 from here: