Over a million developers have joined DZone.

Using jOOQ with JAX-RS to Build a Simple License Server

· Java Zone

Navigate the Maze of the End-User Experience and pick up this APM Essential guide, brought to you in partnership with CA Technologies

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="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>
        <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:

https://github.com/jOOQ/jOOQ/tree/master/jOOQ-examples/jOOQ-jax-rs-example

Thrive in the application economy with an APM model that is strategic. Be E.P.I.C. with CA APM.  Brought to you in partnership with CA Technologies.

Topics:

Published at DZone with permission of Lukas Eder, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}