В некоторых случаях целесообразно иметь одноуровневую архитектуру на стороне сервера. Как правило, такие архитектуры предоставляют RESTful API, реализующий клиентский код и пользовательский интерфейс, используя что-то вроде AngularJS .
В Java стандартным API для приложений RESTful является JAX-RS , который является частью JEE 7 вместе со стандартной реализацией JSON . Но вы можете использовать JAX-RS и вне контейнера JEE. В следующем примере показано, как настроить простой сервер лицензий с использованием этих технологий:
- Maven для строительства и запуска
- Jetty как легкая реализация сервлета
- Джерси , эталонная реализация JAX-RS ( JSR 311 & JSR 339 )
- JOOQ как слой доступа к данным
Для примера мы будем использовать базу данных PostgreSQL.
Пример кода
Теперь, прежде чем пойти и скопировать весь код из этого сообщения в блоге, рассмотрите возможность его загрузки отсюда:
https://github.com/jOOQ/jOOQ/tree/master/jOOQ-examples/jOOQ-jax-rs-example
Образец кода лицензируется в соответствии с условиями Apache Software License 2.0 .
Создание базы данных сервера лицензий
Мы оставим пример простым и используем LICENSE
таблицу для хранения всех лицензионных ключей и связанной информации, тогда как LOG_VERIFY
таблица используется для регистрации доступа к серверу лицензий. Вот 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) );
Чтобы сделать вещи немного более интересными (и безопасными), мы также добавим генерацию лицензионного ключа в базу данных, генерируя ее из хранимой функции как таковой:
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;
Фактический алгоритм может использовать секретную соль для хеширования аргументов функции. Для обучения достаточно константной строки.
Настройка проекта
Мы собираемся настроить генератор кода jOOQ с помощью 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>
С помощью описанной выше настройки мы теперь довольно готовы приступить к разработке нашей службы лицензирования как службы JAX-RS.
Класс обслуживания лицензии
После того, как мы запустить генератор кода jOOQ с помощью Maven , мы можем написать следующий класс обслуживания:
/** * 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); } }); } // [...] }
INSERT INTO LOG_VERIFY
Запрос на самом деле довольно интересно. В простом SQL это будет выглядеть так:
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;
Помимо вышесказанного, он LicenseService
также содержит несколько простых утилит:
/** * 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/[email protected] -> license-key http://localhost:8088/jooq-jax-rs-example/license/[email protected]&license=license-key&version=3.2.0 -> true http://localhost:8088/jooq-jax-rs-example/license/[email protected]&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 | [email protected] | license-key | .* select * from license_server.log_verify -- id | licensee | license | match ---------------------------------------------- -- 2 | [email protected] | license-key | t -- 5 | [email protected] | 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