All versions: 3.11 | 3.10 | 3.9 | 3.8 | 3.7 | 3.6 | 3.5 | 3.4 | 3.3 | Development versions: 3.12

In some use-cases, having a lean, single-tier server-side architecture is desirable. 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.

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,

  LICENSE_DATE TIMESTAMP    NOT NULL,              -- The date when the license was issued
  LICENSEE     TEXT         NOT NULL,              -- The e-mail address of the licensee
  LICENSE      TEXT         NOT NULL,              -- The license key
  VERSION      VARCHAR(50)  NOT NULL DEFAULT '.*', -- The licensed version(s), a regular expression
  
  CONSTRAINT PK_LICENSE PRIMARY KEY (ID),
  CONSTRAINT UK_LICENSE UNIQUE (LICENSE)
);

CREATE TABLE LICENSE_SERVER.LOG_VERIFY (
  ID           SERIAL8      NOT NULL,

  LICENSEE     TEXT         NOT NULL,              -- The licensee whose license is being verified
  LICENSE      TEXT         NOT NULL,              -- The license key that is being verified
  REQUEST_IP   VARCHAR(50)  NOT NULL,              -- The request IP verifying the license
  VERSION      VARCHAR(50)  NOT NULL,              -- The version that is being verified
  MATCH        BOOLEAN      NOT NULL,              -- Whether the verification was successful
  
  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="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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>
                <!-- Use org.jooq            for the Open Source Edition
                         org.jooq.pro        for commercial editions, 
                         org.jooq.pro-java-6 for commercial editions with Java 6 support,
                         org.jooq.trial      for the free trial edition 
                         
                     Note: Only the Open Source Edition is hosted on Maven Central. 
                           Import the others manually from your distribution -->
                <groupId>org.jooq</groupId>
                <artifactId>jooq-codegen-maven</artifactId>
                <version>3.11.4</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>
            <!-- Use org.jooq            for the Open Source Edition
                     org.jooq.pro        for commercial editions, 
                     org.jooq.pro-java-6 for commercial editions with Java 6 support,
                     org.jooq.trial      for the free trial edition 
                     
                 Note: Only the Open Source Edition is hosted on Maven Central. 
                       Import the others manually from your distribution -->
            <groupId>org.jooq</groupId>
            <artifactId>jooq</artifactId>
            <version>3.11.4</version>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>9.4.1212</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 address 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());
                
                // Use the jOOQ query DSL API to generate a license key
                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;
                
                // Use the jOOQ query DSL API to generate a log entry
                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) {
        try (Connection 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 - Please contact support@datageekery.com for help";
        }
    }
    
    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 | license_date            | licensee         | license     | version
--------------------------------------------------------------------------
--  3 | 2013-11-22 14:26:07.768 | test@example.com | license-key | .*

select * from license_server.log_verify
-- id | licensee         | license     | request_ip      | version | match
--------------------------------------------------------------------------
--  2 | test@example.com | license-key | 0:0:0:0:0:0:0:1 | 3.2.0   | t
--  5 | test@example.com | wrong       | 0:0:0:0:0:0:0:1 | 3.2.0   | 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:
https://github.com/jOOQ/jOOQ/tree/master/jOOQ-examples/jOOQ-jax-rs-example

The jOOQ Logo