diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..d839ee94a --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/.idea +/out +/target +*.iml +log +*.patch +/doc \ No newline at end of file diff --git a/README.md b/README.md index ed139bed8..0d353c552 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,204 @@ -#### Написание с нуля полнофункционального многомодульного Maven проекта: -веб приложения (Tomcat, JSP, jQuery), -многопоточного почтового сервиса (JavaMail, java.util.concurrent.*) и вспомогательных модулей связанных по Веб и REST сервисам (SOAP, JAX-WS, Axis, JAX-RS) -c сохранением данных в RMDBS и динамическим конфигурирование модулей по JMX. - -## Сервис-ориентированная архитектура, Микросервисы -- JMS, альтернативы -- Варианты разворачивания сервисов. Работа с базой. Связывание сервисов. - -## Maven. Многомодульный Maven проект -- Build Lifecycle -- Dependency Mechanism -- Зависимости, профили, написание плагина -- The Reactor. Snapshots - -## Создание/тестирование веб-приложения. -- Сборка, запуск, локальный и удаленный debug проекта, способы деплоя в Tomcat -- tomcat7-maven-plugin - -### Веб-сервисы -- Веб-сервисы. SOAP. Преимущества/недостатки веб-сервисов. Расширения. -- Реализация веб-сервисов в Java. JAX-RPC, JAX-WS, CFX, Axis. Стили WSDL -- Создание API и реализации веб-сервиса MailService. -- Деплой и тестирование через SoapUI. - -## Доработка веб-сервиса. Кастомизация WSDL. -- Работа с JAXB. -- Передача по SOAP Exception -- Включение wsdl в сервис для публикации. -- Генерация java кода по WSDL - -## Реализация клиент веб-сервиса. -- Публикация веб сервиса из main(). Дабавление wsdl -- Выделение из wsdl общей части -- Создание клиента почтового сервиса. -- Тестирование с помощью JUnit 4 -- Интеграционное тестирование, maven-failsafe-plugin - -## JAX-WS Handlers -- Logical/protocol handlers. -- Логирование SOAP на стороне клиента. -- Логирование и статистика трафика опубликованного веб-сервиса. -- wsimport binding. -- SoapHandler аутентификация. -Добавляем файлы вложения. Mail-Service. - -## Создаем вложения почты -- Генерация обновленного WSDL через wsgen -- Веб-сервисы: JAX-WS attachment with MTOM -- Тестирование вложений через SoapUi. - -## Загрузка файлов. -- Стандарт MIME. Обрабатываем вложения на форме: commons-fileupload -- Загрузка файла вместе в полями формы. -- Вызов клиента с вложениями. - -## Персистентность. -- NoSQL or RDBMS. Обзор NoSQL систем. CAP -- Обзор Java persistence solution: commons-dbutils, Spring JdbcTemplate, MyBatis, JOOQ, ORM (Hibernate, TopLink, ElipseLink, EBean used in Playframework). JPA. JPA Performance Benchmark -- Работа с базой: создание базы, настройка IDEA Database. -- Работа с DB через DataSource, настройка tomcat. HikariCP -- Настройка работы с DataSource из JUnit. - -## REST веб сервис. -- JAX-RS. Интеграция с Jersey -- Поддержка Json. Jackson +# Многомодульный maven. Многопоточность. XML. Веб сервисы. Удаленное взаимодействие +## Регистрация +## [Программа проекта](#Программа-проекта-1) +### [Изменения проекта (Release Notes)](ReleaseNotes.md) + +### _Разработка полнофункционального многомодульного Maven проекта_ +#### состоящего из 3-х веб приложений: + +![image](https://cloud.githubusercontent.com/assets/13649199/23876457/ab01ff0a-084e-11e7-964f-49c90579fac9.png) + +- **приложение импорта** из XML (JAXB, StAX, XPath, XSLT) +- **многопоточного почтового веб-сервиса** (JavaMail, java.util.concurrent, JAX-WS, MTOM, хендлеры авторизации, логирования и статистики) +- **веб приложения отправки почты с вложениями** + - по SOAP (JAX-WS, MTOM) + - по JAX-RS (Jersey) + - по JMS ([ActiveMQ](http://activemq.apache.org/)) + - через [AKKA](http://akka.io/) + - через [Redis](https://redis.io/) +- сохранение данных в PostgreSQL используя [jDBI](http://jdbi.org/) +- миграция базы [LiquiBase](http://www.liquibase.org/) +- использование в проекте [Guava](https://github.com/google/guava/wiki), [Thymleaf](http://www.thymeleaf.org/), [Lombook](https://projectlombok.org/), [StreamEx](https://github.com/amaembo/streamex), +[Typesafe Config](https://github.com/typesafehub/config), [Java Microbenchmark JMH](http://openjdk.java.net/projects/code-tools/jmh) + +### Требование к участникам +Опыт программирования на Java. Базовые знания Maven. + +### Необходимое ПО +- JDK8 +- Git +- IntelliJ IDEA + +# Первое занятие: многопоточность. + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. Вступление. Многопоточность и параллельность. +![Concurrent vs Parallel](https://joearms.github.io/images/con_and_par.jpg) + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. Структура памяти Java. Ленивая инициализация. +> В видео в `LazySingleton` ошибка: должно быть как в коде проекта `instance == null` + +### Структура памяти: куча, стек, permanent/metaspace + - JVM изнутри - оптимизация и профилирование. + - Stack and Heap + - Дополнительно: + - Из каких частей состоит память java процесса. + - Permanent область памяти + - Java thread stack + - Размер Java объектов + +### Ленивая инициализация +- Реализация Singleton в JAVA +- Double checked locking +- Initialization-on-demand holder idiom +- Подводные камни Singleton + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Реализация многопоточности в Java +- Параллелизм в Java +- Монитор (синхронизация) +- Compare-and-swap +- Java Memory Model +- Синхронизация потоков +- Обзор java.util.concurrent.* +- Как работает ConcurrentHashMap +- Справочник по синхронизаторам java.util.concurrent.* +- Использование ThreadLocal переменных +- Николай Алименков — Прикладная многопоточность +- Can thread switching happen in the synchronized block? + +#### Tproger: Многопоточное программирование в Java 8 +- 1. Параллельное выполнение кода с помощью потоков +- 2. Синхронизация доступа к изменяемым объектам +- 3. Атомарные переменные и конкурентные таблицы -## Асинхронность. -- @OneWay vs Java Execution framework -- Добавление в клиенте асинхронных вызовов. -- Асинхронные сервлеты 3.x в Tomcat +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Реализация многопоточной отправки писем. Execution Framework +> правка к видео: `22: completionService.submit(..)` -## Динамическое конфигурирование. JMX -- Maven Groovy cкрптинг. groovy-maven-plugin -- Настройка Tomcat на удаленное администрирование по JMX +### ![](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Все изменения в проекте будут делаться на основе патчей +#### Скачайте [1_1_MailService.patch](https://drive.google.com/open?id=0B9Ye2auQ_NsFTE5ZV3pzWElxTWM), положите его в проект, правой мышкой на нем сделайте Apply Patch ... -## Отправка email в многопоточном приложении -- Initialization on demand holder / Double-checked locking -- java.util.concurrent.*: Executors , Synchronizers, Concurrent Collections, Lock +---------------------------- -## Проблема MemoryLeak. Поиск утечки памяти. +### Ресурсы (основы) +- Intuit, Потоки выполнения. Синхронизация +- Алексей Владыкин, Основы многопоточность в Java +- Виталий Чибриков, Java. Многопоточность +- Computer Science Center, курс Параллельное программирование +- Юрий Ткач, курс Advanced Java - Concurrency +- Головач, курс Java Multithreading + +--- +## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Задание первого занятия + +Вычекать этот проект: +```git clone https://github.com/JavaOPs/masterjava.git``` + +- Применить оптимизацию к MatrixUtil.singleThreadMultiply +- Реализовать метод `MatrixUtil.concurrentMultiply`, позволяющий многопоточно перемножать квадратные матрицы N*N. +- Количество дочерних потоков ограничено `MainMatrix.THREAD_NUMBER`. +- Добиться того, чтобы на матрице 1000*1000 многопоточная реализация была быстрее однопоточной + +----- +## ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Подсказки по HW1 +- не делайте 1000 000 тасок, лучше их сделать крупнее +- у меня разница между 4 и 1000 тасками по времени незаметна, поэтому делайте просто и не делайте сложно +- наконец: можно не считать значение элемента результирующей матрицы C за раз, а накапливать (`concurrentMultiply3`). Мои результаты: +``` +Benchmark (matrixSize) Mode Cnt Score Error Units +MatrixBenchmark.singleThreadMultiplyOpt 1000 ss 100 837,867 ± 25,530 ms/op +MatrixBenchmark.concurrentMultiply2 1000 ss 100 394,294 ± 21,657 ms/op +MatrixBenchmark.concurrentMultiply3 1000 ss 100 186,827 ± 11,882 ms/op +``` +----- +# Программа проекта + +## Занятие 2 +- Разбор ДЗ (многопоточная реализация умножения матриц) +- Java Microbenchmark JMH (от Алексея Шипилева) +- Обзор Guava +- Формат XML. Создание схемы XSD. +- Работа с XML в Java + - JAXB, JAXP + - StAX + - XPath + - XSLT + +## Занятие 3 +- Разбор ДЗ (работа с XML) +- [Обзор Guava](https://drive.google.com/open?id=0B9Ye2auQ_NsFeFB5a29JQ2tRNHM) +- Монады. flatMap +- SOA и Микросервисы +- Многомодульный Maven проект + +## Занятие 4 +- Разбор ДЗ (реализация структуры проекта, загрузка и разбор xml) +- Thymleaf +- Maven. Поиск и разрешение конфликтов зависимостей +- Подключаем логирование с общими настройкам +- Библиотеки и фреймворки для работы с JDBC. +- Модуль persist + +## Занятие 5 +- Разбор ДЗ + - Сохранение в базу в batch-моде с обработкой конфликтов + - Вставка в несколько потоков +- Конфигурирование приложения (Typesafe config) +- Lombook + +## Занятие 6 +- Разбор ДЗ (доработка модели и модуля export) +- Миграция DB +- Веб-сервисы (REST/SOAP) + - Java реализации SOAP + - Имплементируем Mail Service + +## Занятие 7 +- Разбор ДЗ + - реализация MailSender + - сохранение результатов отправки в DB + - импорт Проектов и Групп +- Стили WSDL. Кастомизация WSDL +- Публикация кастомизированного WSDL. Автогенерация. +- Деплой в Tomcat +- Создание клиента почтового сервиса +- Реализация массовой и групповой отправки почты. HW7 + +## Занятие 8 +- Разбор ДЗ + - Делаем общий mailService.wsdl + - Обновление WSDL + - Отправка почты из модуля webapp +- Доступ к переменным maven в приложении +- SOAP Exception. Выделение общей части схемы +- Коррекция схемы + +## Занятие 9 +- Добавление мавен плагинов (copy-rename-maven-plugin, maven-antrun-plugin, liquibase-maven-plugin) +- Разбор ДЗ + - Реализация вложений в веб-сервисе + - Подключение MTOM + - Реализация загрузки вложений в модуле webapp + - Реализация вложений в почте +- JAX-WS Message Context. Авторизация +- JAX-WS Handlers (логирование SOAP) +- Домашнее задание. Статистика + +## Занятие 10 +- Разбор ДЗ + - Реализация SOAP handlers + - Конфигурирование сервисов +- JavaEE +- JAX-RS. Интеграция с Jersey +- JMS. Интеграция с [ActiveMQ](http://activemq.apache.org/) + +## Занятие 11 +- Авторизация в контейнере Tomcat +- Отправка почты с вложениями + - по JAX-RS + - по JMS + -Рефакторинг. Эксепшены в Java 8 лямбда +- Concurrent and distributed applications toolkit AKKA +- Отсылка почты через AKKA Actors (Typed и Untyped Actors) +- Асинхронные сервлеты 3.0 +- Домашнее задание + - Разбор решения с асинхронными сервлетами +- [Выбор языка программирования](https://drive.google.com/open?id=0B9Ye2auQ_NsFZUVNakNxeUtGeFE) diff --git a/ReleaseNotes.md b/ReleaseNotes.md new file mode 100644 index 000000000..e4eaf5b8e --- /dev/null +++ b/ReleaseNotes.md @@ -0,0 +1,19 @@ +# MasterJava Release Notes + +### Masterjava 2 +- Добавил в проект + - [Миграция базы через liquiBase](http://www.liquibase.org/quickstart.html) + - Реализация модели/DAO/JUnit + - Миграция DB + - Maven плагины copy-rename-maven-plugin, maven-antrun-plugin, liquibase-maven-plugin + - Авторизация в контейнере Tomcat + - Concurrent and distributed applications toolkit AKKA (отсылка почты через AKKA Actors) + - Асинхронные сервлеты 3.0 + - [Выбор языка программирования](https://drive.google.com/file/d/0B9Ye2auQ_NsFZUVNakNxeUtGeFE) + +- Pефакторинг + - Реализация модуля export: Thymeleaf и Upload + - Закэшировал `TemplateEngine` + - Загрузка файлов через API Servlet 3.0 + - Сохранение в базу в batch-моде с обработкой конфликтов + - При обновлении пользователей теперь используем `INSERT ON CONFLICT` diff --git a/common/pom.xml b/common/pom.xml new file mode 100644 index 000000000..33d3da8ed --- /dev/null +++ b/common/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + + ru.javaops + parent + ../parent/pom.xml + 1.0-SNAPSHOT + + + common + 1.0-SNAPSHOT + jar + Common + + + + \ No newline at end of file diff --git a/common/src/main/java/ru/javaops/masterjava/config/Configs.java b/common/src/main/java/ru/javaops/masterjava/config/Configs.java new file mode 100644 index 000000000..d11483da2 --- /dev/null +++ b/common/src/main/java/ru/javaops/masterjava/config/Configs.java @@ -0,0 +1,19 @@ +package ru.javaops.masterjava.config; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; + +/** + * gkislin + * 01.11.2016 + */ +public class Configs { + + public static Config getConfig(String resource) { + return ConfigFactory.parseResources(resource).resolve(); + } + + public static Config getConfig(String resource, String domain) { + return getConfig(resource).getConfig(domain); + } +} diff --git a/config_templates/context.xml b/config_templates/context.xml new file mode 100644 index 000000000..40bb8aa0c --- /dev/null +++ b/config_templates/context.xml @@ -0,0 +1,49 @@ + + + + + + + + WEB-INF/web.xml + ${catalina.base}/conf/web.xml + + + + + + + + + diff --git a/config_templates/logback-test.xml b/config_templates/logback-test.xml new file mode 100644 index 000000000..739dd6dcf --- /dev/null +++ b/config_templates/logback-test.xml @@ -0,0 +1,21 @@ + + + + true + + + + + UTF-8 + %-5level %logger{0} [%file:%line] %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/config_templates/logback.xml b/config_templates/logback.xml new file mode 100644 index 000000000..352ee14a0 --- /dev/null +++ b/config_templates/logback.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + ${LOG_DIR}/${project.build.finalName}.log + + UTF-8 + %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] %-5level %logger{0} [%file:%line] - %msg%n + + + + ${LOG_DIR}/archived/${project.build.finalName}.%d{yyyy-MM-dd}.%i.log + + + 5MB + + + + + + + UTF-8 + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} [%file:%line] - %msg%n + + + + + + + + + + diff --git a/config_templates/sql/initDB.sql b/config_templates/sql/initDB.sql new file mode 100644 index 000000000..09b463d69 --- /dev/null +++ b/config_templates/sql/initDB.sql @@ -0,0 +1,16 @@ +DROP TABLE IF EXISTS users; +DROP SEQUENCE IF EXISTS user_seq; +DROP TYPE IF EXISTS user_flag; + +CREATE TYPE user_flag AS ENUM ('active', 'deleted', 'superuser'); + +CREATE SEQUENCE user_seq START 100000; + +CREATE TABLE users ( + id INTEGER PRIMARY KEY DEFAULT nextval('user_seq'), + full_name TEXT NOT NULL, + email TEXT NOT NULL, + flag user_flag NOT NULL +); + +CREATE UNIQUE INDEX email_idx ON users (email); \ No newline at end of file diff --git a/config_templates/sql/lb_apply.bat b/config_templates/sql/lb_apply.bat new file mode 100644 index 000000000..80f23598b --- /dev/null +++ b/config_templates/sql/lb_apply.bat @@ -0,0 +1,8 @@ +set LB_HOME=c:\java\liquibase-3.5.3 +call %LB_HOME%\liquibase.bat --driver=org.postgresql.Driver ^ +--classpath=%LB_HOME%\lib ^ +--changeLogFile=databaseChangeLog.sql ^ +--url="jdbc:postgresql://localhost:5432/masterjava" ^ +--username=user ^ +--password=password ^ +migrate \ No newline at end of file diff --git a/parent-web/pom.xml b/parent-web/pom.xml new file mode 100644 index 000000000..7756bfb42 --- /dev/null +++ b/parent-web/pom.xml @@ -0,0 +1,105 @@ + + + 4.0.0 + + + ru.javaops + parent + ../parent/pom.xml + 1.0-SNAPSHOT + + + parent-web + pom + 1.0-SNAPSHOT + Parent Web + + + + + org.apache.maven.plugins + maven-war-plugin + 3.0.0 + + false + + + + + + org.codehaus.cargo + cargo-maven2-plugin + 1.6.2 + + + tomcat8x + + UTF-8 + + + + org.postgresql + postgresql + + + + + + + ${masterjava.config}/context.xml + conf/Catalina/localhost/ + context.xml.default + + + + + + ${project.groupId} + ${project.artifactId} + war + + ${project.build.finalName} + + + + + + + + + + + ${masterjava.config} + true + + logback.xml + + + + src/main/resources + + + + + + + ${project.groupId} + common-web + ${project.version} + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + + + + + + + \ No newline at end of file diff --git a/parent/pom.xml b/parent/pom.xml new file mode 100644 index 000000000..97699af6e --- /dev/null +++ b/parent/pom.xml @@ -0,0 +1,132 @@ + + + 4.0.0 + + ru.javaops + parent + pom + 1.0-SNAPSHOT + Parent + + + 1.8 + UTF-8 + UTF-8 + + 1.2.2 + 1.7.25 + + /apps/masterjava/config/ + false + + + + install + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.20.1 + + -Dfile.encoding=UTF-8 + + + + org.apache.maven.plugins + maven-enforcer-plugin + 1.4.1 + + + enforce + + + + + + + enforce + + + + + + + + + ${masterjava.config} + + logback-test.xml + + + + src/test/resources + + + + + + + com.google.guava + guava + 21.0 + + + one.util + streamex + 0.6.2 + + + + com.typesafe + config + 1.3.1 + + + + org.projectlombok + lombok + 1.16.16 + provided + + + + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + runtime + + + + ch.qos.logback + logback-classic + ${logback.version} + + + + + junit + junit + 4.12 + test + + + + + + + + + + \ No newline at end of file diff --git a/persist/pom.xml b/persist/pom.xml new file mode 100644 index 000000000..855bb12f3 --- /dev/null +++ b/persist/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + + ru.javaops + parent + ../parent/pom.xml + 1.0-SNAPSHOT + + + persist + 1.0-SNAPSHOT + Persist + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.0.2 + + + + test-jar + + + + + + + + + ${project.groupId} + common + ${project.version} + + + org.jdbi + jdbi + 2.78 + + + com.bertoncelj.jdbi.entitymapper + jdbi-entity-mapper + 1.0.0 + + + org.jdbi + jdbi + + + + + org.postgresql + postgresql + 9.4.1211 + + + \ No newline at end of file diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/DBIProvider.java b/persist/src/main/java/ru/javaops/masterjava/persist/DBIProvider.java new file mode 100644 index 000000000..59579cbb6 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/DBIProvider.java @@ -0,0 +1,54 @@ +package ru.javaops.masterjava.persist; + +import lombok.extern.slf4j.Slf4j; +import org.skife.jdbi.v2.DBI; +import org.skife.jdbi.v2.logging.SLF4JLog; +import org.skife.jdbi.v2.tweak.ConnectionFactory; +import ru.javaops.masterjava.persist.dao.AbstractDao; + +import javax.naming.InitialContext; +import javax.sql.DataSource; + +/** + * gkislin + * 01.11.2016 + */ +@Slf4j +public class DBIProvider { + + private volatile static ConnectionFactory connectionFactory = null; + + private static class DBIHolder { + static final DBI jDBI; + + static { + final DBI dbi; + if (connectionFactory != null) { + log.info("Init jDBI with connectionFactory"); + dbi = new DBI(connectionFactory); + } else { + try { + log.info("Init jDBI with JNDI"); + InitialContext ctx = new InitialContext(); + dbi = new DBI((DataSource) ctx.lookup("java:/comp/env/jdbc/masterjava")); + } catch (Exception ex) { + throw new IllegalStateException("PostgreSQL initialization failed", ex); + } + } + jDBI = dbi; + jDBI.setSQLLog(new SLF4JLog()); + } + } + + public static void init(ConnectionFactory connectionFactory) { + DBIProvider.connectionFactory = connectionFactory; + } + + public static DBI getDBI() { + return DBIHolder.jDBI; + } + + public static T getDao(Class daoClass) { + return DBIHolder.jDBI.onDemand(daoClass); + } +} diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/dao/AbstractDao.java b/persist/src/main/java/ru/javaops/masterjava/persist/dao/AbstractDao.java new file mode 100644 index 000000000..f7e97e459 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/AbstractDao.java @@ -0,0 +1,11 @@ +package ru.javaops.masterjava.persist.dao; + +/** + * gkislin + * 27.10.2016 + *

+ *

+ */ +public interface AbstractDao { + void clean(); +} diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/dao/CityDao.java b/persist/src/main/java/ru/javaops/masterjava/persist/dao/CityDao.java new file mode 100644 index 000000000..71fe79128 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/CityDao.java @@ -0,0 +1,38 @@ +package ru.javaops.masterjava.persist.dao; + +import com.bertoncelj.jdbi.entitymapper.EntityMapperFactory; +import one.util.streamex.StreamEx; +import org.skife.jdbi.v2.sqlobject.*; +import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapperFactory; +import ru.javaops.masterjava.persist.model.City; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +@RegisterMapperFactory(EntityMapperFactory.class) +public abstract class CityDao implements AbstractDao { + + @SqlUpdate("TRUNCATE city CASCADE ") + @Override + public abstract void clean(); + + @SqlQuery("SELECT * FROM city ORDER BY name") + public abstract List getAll(); + + public Map getAsMap() { + return StreamEx.of(getAll()).toMap(City::getRef, c -> c); + } + + @SqlUpdate("INSERT INTO city (ref, name) VALUES (:ref, :name)") + @GetGeneratedKeys + public abstract int insertGeneratedId(@BindBean City city); + + public void insert(City city) { + int id = insertGeneratedId(city); + city.setId(id); + } + + @SqlBatch("INSERT INTO city (ref, name) VALUES (:ref, :name)") + public abstract void insertBatch(@BindBean Collection cities); +} diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/dao/GroupDao.java b/persist/src/main/java/ru/javaops/masterjava/persist/dao/GroupDao.java new file mode 100644 index 000000000..7eb23fd59 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/GroupDao.java @@ -0,0 +1,37 @@ +package ru.javaops.masterjava.persist.dao; + +import com.bertoncelj.jdbi.entitymapper.EntityMapperFactory; +import one.util.streamex.StreamEx; +import org.skife.jdbi.v2.sqlobject.BindBean; +import org.skife.jdbi.v2.sqlobject.GetGeneratedKeys; +import org.skife.jdbi.v2.sqlobject.SqlQuery; +import org.skife.jdbi.v2.sqlobject.SqlUpdate; +import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapperFactory; +import ru.javaops.masterjava.persist.model.Group; + +import java.util.List; +import java.util.Map; + +@RegisterMapperFactory(EntityMapperFactory.class) +public abstract class GroupDao implements AbstractDao { + + @SqlUpdate("TRUNCATE groups CASCADE ") + @Override + public abstract void clean(); + + @SqlQuery("SELECT * FROM groups ORDER BY name") + public abstract List getAll(); + + public Map getAsMap() { + return StreamEx.of(getAll()).toMap(Group::getName, g -> g); + } + + @SqlUpdate("INSERT INTO groups (name, type, project_id) VALUES (:name, CAST(:type AS group_type), :projectId)") + @GetGeneratedKeys + public abstract int insertGeneratedId(@BindBean Group groups); + + public void insert(Group groups) { + int id = insertGeneratedId(groups); + groups.setId(id); + } +} diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/dao/ProjectDao.java b/persist/src/main/java/ru/javaops/masterjava/persist/dao/ProjectDao.java new file mode 100644 index 000000000..e5b8a9b1f --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/ProjectDao.java @@ -0,0 +1,37 @@ +package ru.javaops.masterjava.persist.dao; + +import com.bertoncelj.jdbi.entitymapper.EntityMapperFactory; +import one.util.streamex.StreamEx; +import org.skife.jdbi.v2.sqlobject.BindBean; +import org.skife.jdbi.v2.sqlobject.GetGeneratedKeys; +import org.skife.jdbi.v2.sqlobject.SqlQuery; +import org.skife.jdbi.v2.sqlobject.SqlUpdate; +import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapperFactory; +import ru.javaops.masterjava.persist.model.Project; + +import java.util.List; +import java.util.Map; + +@RegisterMapperFactory(EntityMapperFactory.class) +public abstract class ProjectDao implements AbstractDao { + + @SqlUpdate("TRUNCATE project CASCADE ") + @Override + public abstract void clean(); + + @SqlQuery("SELECT * FROM project ORDER BY name") + public abstract List getAll(); + + public Map getAsMap() { + return StreamEx.of(getAll()).toMap(Project::getName, g -> g); + } + + @SqlUpdate("INSERT INTO project (name, description) VALUES (:name, :description)") + @GetGeneratedKeys + public abstract int insertGeneratedId(@BindBean Project project); + + public void insert(Project project) { + int id = insertGeneratedId(project); + project.setId(id); + } +} diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java b/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java new file mode 100644 index 000000000..d5fa6676a --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java @@ -0,0 +1,71 @@ +package ru.javaops.masterjava.persist.dao; + +import com.bertoncelj.jdbi.entitymapper.EntityMapperFactory; +import one.util.streamex.IntStreamEx; +import org.skife.jdbi.v2.sqlobject.*; +import org.skife.jdbi.v2.sqlobject.customizers.BatchChunkSize; +import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapperFactory; +import ru.javaops.masterjava.persist.DBIProvider; +import ru.javaops.masterjava.persist.model.User; + +import java.util.List; + +/** + * gkislin + * 27.10.2016 + *

+ *

+ */ +@RegisterMapperFactory(EntityMapperFactory.class) +public abstract class UserDao implements AbstractDao { + + public User insert(User user) { + if (user.isNew()) { + int id = insertGeneratedId(user); + user.setId(id); + } else { + insertWitId(user); + } + return user; + } + + @SqlQuery("SELECT nextval('user_seq')") + abstract int getNextVal(); + + @Transaction + public int getSeqAndSkip(int step) { + int id = getNextVal(); + DBIProvider.getDBI().useHandle(h -> h.execute("ALTER SEQUENCE user_seq RESTART WITH " + (id + step))); + return id; + } + + @SqlUpdate("INSERT INTO users (full_name, email, flag, city_id) VALUES (:fullName, :email, CAST(:flag AS USER_FLAG), :cityId) ") + @GetGeneratedKeys + abstract int insertGeneratedId(@BindBean User user); + + @SqlUpdate("INSERT INTO users (id, full_name, email, flag, city_id) VALUES (:id, :fullName, :email, CAST(:flag AS USER_FLAG), :cityId) ") + abstract void insertWitId(@BindBean User user); + + @SqlQuery("SELECT * FROM users ORDER BY full_name, email LIMIT :it") + public abstract List getWithLimit(@Bind int limit); + + // http://stackoverflow.com/questions/13223820/postgresql-delete-all-content + @SqlUpdate("TRUNCATE users CASCADE") + @Override + public abstract void clean(); + + // https://habrahabr.ru/post/264281/ + @SqlBatch("INSERT INTO users (id, full_name, email, flag, city_id) VALUES (:id, :fullName, :email, CAST(:flag AS USER_FLAG), :cityId)" + + "ON CONFLICT DO NOTHING") +// "ON CONFLICT (email) DO UPDATE SET full_name=:fullName, flag=CAST(:flag AS USER_FLAG)") + public abstract int[] insertBatch(@BindBean List users, @BatchChunkSize int chunkSize); + + + public List insertAndGetConflictEmails(List users) { + int[] result = insertBatch(users, users.size()); + return IntStreamEx.range(0, users.size()) + .filter(i -> result[i] == 0) + .mapToObj(index -> users.get(index).getEmail()) + .toList(); + } +} diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserGroupDao.java b/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserGroupDao.java new file mode 100644 index 000000000..2ca4a8ed7 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserGroupDao.java @@ -0,0 +1,38 @@ +package ru.javaops.masterjava.persist.dao; + +import com.bertoncelj.jdbi.entitymapper.EntityMapperFactory; +import one.util.streamex.StreamEx; +import org.skife.jdbi.v2.sqlobject.*; +import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapperFactory; +import ru.javaops.masterjava.persist.model.UserGroup; + +import java.util.List; +import java.util.Set; + +/** + * gkislin + * 27.10.2016 + *

+ *

+ */ +@RegisterMapperFactory(EntityMapperFactory.class) +public abstract class UserGroupDao implements AbstractDao { + + @SqlUpdate("TRUNCATE user_group CASCADE") + @Override + public abstract void clean(); + + @SqlBatch("INSERT INTO user_group (user_id, group_id) VALUES (:userId, :groupId)") + public abstract void insertBatch(@BindBean List userGroups); + + @SqlQuery("SELECT user_id FROM user_group WHERE group_id=:it") + public abstract Set getUserIds(@Bind int groupId); + + public static List toUserGroups(int userId, Integer... groupIds) { + return StreamEx.of(groupIds).map(groupId -> new UserGroup(userId, groupId)).toList(); + } + + public static Set getByGroupId(int groupId, List userGroups) { + return StreamEx.of(userGroups).filter(ug -> ug.getGroupId() == groupId).map(UserGroup::getUserId).toSet(); + } +} \ No newline at end of file diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/model/BaseEntity.java b/persist/src/main/java/ru/javaops/masterjava/persist/model/BaseEntity.java new file mode 100644 index 000000000..8a840ad6c --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/BaseEntity.java @@ -0,0 +1,34 @@ +package ru.javaops.masterjava.persist.model; + +import lombok.*; + +/** + * gkislin + * 28.10.2016 + */ +@NoArgsConstructor +@AllArgsConstructor +@ToString +abstract public class BaseEntity { + + @Getter + @Setter + protected Integer id; + + public boolean isNew() { + return id == null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BaseEntity baseEntity = (BaseEntity) o; + return id != null && id.equals(baseEntity.id); + } + + @Override + public int hashCode() { + return id == null ? 0 : id; + } +} diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/model/City.java b/persist/src/main/java/ru/javaops/masterjava/persist/model/City.java new file mode 100644 index 000000000..6c968930d --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/City.java @@ -0,0 +1,21 @@ +package ru.javaops.masterjava.persist.model; + +import lombok.*; + +@Data +@EqualsAndHashCode(callSuper = true) +@RequiredArgsConstructor +@NoArgsConstructor +@ToString(callSuper = true) +public class City extends BaseEntity { + + @NonNull + private String ref; + @NonNull + private String name; + + public City(Integer id, String ref, String name) { + this(ref, name); + this.id = id; + } +} \ No newline at end of file diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/model/Group.java b/persist/src/main/java/ru/javaops/masterjava/persist/model/Group.java new file mode 100644 index 000000000..2482b1fa2 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/Group.java @@ -0,0 +1,21 @@ +package ru.javaops.masterjava.persist.model; + +import com.bertoncelj.jdbi.entitymapper.Column; +import lombok.*; + +@Data +@EqualsAndHashCode(callSuper = true) +@RequiredArgsConstructor +@NoArgsConstructor +@ToString(callSuper = true) +public class Group extends BaseEntity { + + @NonNull private String name; + @NonNull private GroupType type; + @NonNull @Column("project_id") private int projectId; + + public Group(Integer id, String name, GroupType type, int projectId) { + this(name, type, projectId); + this.id = id; + } +} diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/model/GroupType.java b/persist/src/main/java/ru/javaops/masterjava/persist/model/GroupType.java new file mode 100644 index 000000000..ab80bfc76 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/GroupType.java @@ -0,0 +1,7 @@ +package ru.javaops.masterjava.persist.model; + +public enum GroupType { + REGISTERING, + CURRENT, + FINISHED; +} diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/model/Project.java b/persist/src/main/java/ru/javaops/masterjava/persist/model/Project.java new file mode 100644 index 000000000..e3bcb6dcc --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/Project.java @@ -0,0 +1,19 @@ +package ru.javaops.masterjava.persist.model; + +import lombok.*; + +@Data +@EqualsAndHashCode(callSuper = true) +@RequiredArgsConstructor +@NoArgsConstructor +@ToString(callSuper = true) +public class Project extends BaseEntity { + + @NonNull private String name; + @NonNull private String description; + + public Project(Integer id, String name, String description) { + this(name, description); + this.id = id; + } +} diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/model/User.java b/persist/src/main/java/ru/javaops/masterjava/persist/model/User.java new file mode 100644 index 000000000..3eb24a74c --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/User.java @@ -0,0 +1,23 @@ +package ru.javaops.masterjava.persist.model; + +import com.bertoncelj.jdbi.entitymapper.Column; +import lombok.*; + +@Data +@EqualsAndHashCode(callSuper = true) +@RequiredArgsConstructor +@NoArgsConstructor +@ToString(callSuper = true) +public class User extends BaseEntity { + @Column("full_name") + private @NonNull String fullName; + private @NonNull String email; + private @NonNull UserFlag flag; + @Column("city_id") + private @NonNull Integer cityId; + + public User(Integer id, String fullName, String email, UserFlag flag, Integer cityId) { + this(fullName, email, flag, cityId); + this.id=id; + } +} \ No newline at end of file diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/model/UserFlag.java b/persist/src/main/java/ru/javaops/masterjava/persist/model/UserFlag.java new file mode 100644 index 000000000..bc2f69183 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/UserFlag.java @@ -0,0 +1,11 @@ +package ru.javaops.masterjava.persist.model; + +/** + * gkislin + * 13.10.2016 + */ +public enum UserFlag { + active, + deleted, + superuser; +} diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/model/UserGroup.java b/persist/src/main/java/ru/javaops/masterjava/persist/model/UserGroup.java new file mode 100644 index 000000000..d7eaee36b --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/UserGroup.java @@ -0,0 +1,12 @@ +package ru.javaops.masterjava.persist.model; + +import com.bertoncelj.jdbi.entitymapper.Column; +import lombok.*; + +@Data +@RequiredArgsConstructor +@NoArgsConstructor +public class UserGroup { + @NonNull @Column("user_id") private Integer userId; + @NonNull @Column("group_id") private Integer groupId; +} \ No newline at end of file diff --git a/persist/src/main/resources/persist.conf b/persist/src/main/resources/persist.conf new file mode 100644 index 000000000..fe150292e --- /dev/null +++ b/persist/src/main/resources/persist.conf @@ -0,0 +1,7 @@ +db { + url = "jdbc:postgresql://localhost:5432/masterjava" + user = user + password = password +} + +include required(file("/apps/masterjava/config/persist.conf")) diff --git a/persist/src/test/java/ru/javaops/masterjava/persist/CityTestData.java b/persist/src/test/java/ru/javaops/masterjava/persist/CityTestData.java new file mode 100644 index 000000000..084b5cd1a --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/CityTestData.java @@ -0,0 +1,32 @@ +package ru.javaops.masterjava.persist; + +import com.google.common.collect.ImmutableMap; +import ru.javaops.masterjava.persist.dao.CityDao; +import ru.javaops.masterjava.persist.model.City; + +import java.util.Map; + +/** + * gkislin + * 14.11.2016 + */ +public class CityTestData { + public static final City KIEV = new City("kiv", "Киев"); + public static final City MINSK = new City("mnsk", "Минск"); + public static final City MOSCOW = new City("mow", "Москва"); + public static final City SPB = new City("spb", "Санкт-Петербург"); + + public static final Map CITIES = ImmutableMap.of( + KIEV.getRef(), KIEV, + MINSK.getRef(), MINSK, + MOSCOW.getRef(), MOSCOW, + SPB.getRef(), SPB); + + public static void setUp() { + CityDao dao = DBIProvider.getDao(CityDao.class); + dao.clean(); + DBIProvider.getDBI().useTransaction((conn, status) -> { + CITIES.values().forEach(dao::insert); + }); + } +} diff --git a/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java b/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java new file mode 100644 index 000000000..b391650fc --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java @@ -0,0 +1,28 @@ +package ru.javaops.masterjava.persist; + +import com.typesafe.config.Config; +import ru.javaops.masterjava.config.Configs; + +import java.sql.DriverManager; + +/** + * gkislin + * 27.10.2016 + */ +public class DBITestProvider { + public static void initDBI() { + Config db = Configs.getConfig("persist.conf","db"); + initDBI(db.getString("url"), db.getString("user"), db.getString("password")); + } + + public static void initDBI(String dbUrl, String dbUser, String dbPassword) { + DBIProvider.init(() -> { + try { + Class.forName("org.postgresql.Driver"); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("PostgreSQL driver not found", e); + } + return DriverManager.getConnection(dbUrl, dbUser, dbPassword); + }); + } +} \ No newline at end of file diff --git a/persist/src/test/java/ru/javaops/masterjava/persist/GroupTestData.java b/persist/src/test/java/ru/javaops/masterjava/persist/GroupTestData.java new file mode 100644 index 000000000..01aba6bb9 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/GroupTestData.java @@ -0,0 +1,55 @@ +package ru.javaops.masterjava.persist; + +import com.google.common.collect.ImmutableMap; +import ru.javaops.masterjava.persist.dao.GroupDao; +import ru.javaops.masterjava.persist.model.Group; + +import java.util.Map; + +import static ru.javaops.masterjava.persist.ProjectTestData.MASTERJAVA_ID; +import static ru.javaops.masterjava.persist.ProjectTestData.TOPJAVA_ID; +import static ru.javaops.masterjava.persist.model.GroupType.CURRENT; +import static ru.javaops.masterjava.persist.model.GroupType.FINISHED; + +/** + * gkislin + * 14.11.2016 + */ +public class GroupTestData { + public static Group TOPJAVA_06; + public static Group TOPJAVA_07; + public static Group TOPJAVA_08; + public static Group MASTERJAVA_01; + public static Map GROUPS; + + public static int TOPJAVA_06_ID; + public static int TOPJAVA_07_ID; + public static int TOPJAVA_08_ID; + public static int MASTERJAVA_01_ID; + + + public static void init() { + ProjectTestData.setUp(); + TOPJAVA_06 = new Group("topjava06", FINISHED, TOPJAVA_ID); + TOPJAVA_07 = new Group("topjava07", FINISHED, TOPJAVA_ID); + TOPJAVA_08 = new Group("topjava08", CURRENT, TOPJAVA_ID); + MASTERJAVA_01 = new Group("masterjava01", CURRENT, MASTERJAVA_ID); + GROUPS = ImmutableMap.of( + TOPJAVA_06.getName(), TOPJAVA_06, + TOPJAVA_07.getName(), TOPJAVA_07, + TOPJAVA_08.getName(), TOPJAVA_08, + MASTERJAVA_01.getName(), MASTERJAVA_01); + } + + public static void setUp() { + GroupDao dao = DBIProvider.getDao(GroupDao.class); + dao.clean(); + DBIProvider.getDBI().useTransaction((conn, status) -> { + GROUPS.values().forEach(dao::insert); + }); + TOPJAVA_06_ID = TOPJAVA_06.getId(); + TOPJAVA_07_ID = TOPJAVA_07.getId(); + TOPJAVA_08_ID = TOPJAVA_08.getId(); + MASTERJAVA_01_ID = MASTERJAVA_01.getId(); + } +} diff --git a/persist/src/test/java/ru/javaops/masterjava/persist/ProjectTestData.java b/persist/src/test/java/ru/javaops/masterjava/persist/ProjectTestData.java new file mode 100644 index 000000000..b26b49178 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/ProjectTestData.java @@ -0,0 +1,32 @@ +package ru.javaops.masterjava.persist; + +import com.google.common.collect.ImmutableMap; +import ru.javaops.masterjava.persist.dao.ProjectDao; +import ru.javaops.masterjava.persist.model.Project; + +import java.util.Map; + +/** + * gkislin + * 14.11.2016 + */ +public class ProjectTestData { + public static final Project TOPJAVA = new Project("topjava", "Topjava"); + public static final Project MASTERJAVA = new Project("masterjava", "Masterjava"); + public static final Map PROJECTS = ImmutableMap.of( + TOPJAVA.getName(), TOPJAVA, + MASTERJAVA.getName(), MASTERJAVA); + + public static int TOPJAVA_ID; + public static int MASTERJAVA_ID; + + public static void setUp() { + ProjectDao dao = DBIProvider.getDao(ProjectDao.class); + dao.clean(); + DBIProvider.getDBI().useTransaction((conn, status) -> { + PROJECTS.values().forEach(dao::insert); + }); + TOPJAVA_ID = TOPJAVA.getId(); + MASTERJAVA_ID = MASTERJAVA.getId(); + } +} diff --git a/persist/src/test/java/ru/javaops/masterjava/persist/UserGroupTestData.java b/persist/src/test/java/ru/javaops/masterjava/persist/UserGroupTestData.java new file mode 100644 index 000000000..77f64ea59 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/UserGroupTestData.java @@ -0,0 +1,45 @@ +package ru.javaops.masterjava.persist; + +import ru.javaops.masterjava.persist.dao.UserGroupDao; +import ru.javaops.masterjava.persist.model.UserGroup; + +import java.util.List; +import java.util.Set; + +import static ru.javaops.masterjava.persist.dao.UserGroupDao.toUserGroups; +import static ru.javaops.masterjava.persist.GroupTestData.*; + +/** + * gkislin + * 14.11.2016 + */ +public class UserGroupTestData { + + public static List USER_GROUPS; + + public static void init() { + UserTestData.init(); + UserTestData.setUp(); + + GroupTestData.init(); + GroupTestData.setUp(); + + USER_GROUPS = toUserGroups(UserTestData.ADMIN.getId(), TOPJAVA_07_ID, TOPJAVA_08_ID, MASTERJAVA_01_ID); + USER_GROUPS.addAll(toUserGroups(UserTestData.FULL_NAME.getId(), TOPJAVA_07_ID, MASTERJAVA_01_ID)); + USER_GROUPS.addAll(toUserGroups(UserTestData.USER1.getId(), TOPJAVA_06_ID, MASTERJAVA_01_ID)); + USER_GROUPS.add(new UserGroup(UserTestData.USER2.getId(), MASTERJAVA_01_ID)); + USER_GROUPS.add(new UserGroup(UserTestData.USER3.getId(), MASTERJAVA_01_ID)); + } + + public static void setUp() { + UserGroupDao dao = DBIProvider.getDao(UserGroupDao.class); + dao.clean(); + DBIProvider.getDBI().useTransaction((conn, status) -> { + dao.insertBatch(USER_GROUPS); + }); + } + + public static Set getByGroupId(int groupId) { + return UserGroupDao.getByGroupId(groupId, USER_GROUPS); + } +} diff --git a/persist/src/test/java/ru/javaops/masterjava/persist/UserTestData.java b/persist/src/test/java/ru/javaops/masterjava/persist/UserTestData.java new file mode 100644 index 000000000..31c83da80 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/UserTestData.java @@ -0,0 +1,44 @@ +package ru.javaops.masterjava.persist; + +import com.google.common.collect.ImmutableList; +import ru.javaops.masterjava.persist.dao.UserDao; +import ru.javaops.masterjava.persist.model.User; +import ru.javaops.masterjava.persist.model.UserFlag; + +import java.util.List; + +import static ru.javaops.masterjava.persist.CityTestData.*; + +/** + * gkislin + * 14.11.2016 + */ +public class UserTestData { + public static User ADMIN; + public static User DELETED; + public static User FULL_NAME; + public static User USER1; + public static User USER2; + public static User USER3; + public static List FIST5_USERS; + + public static void init() { + CityTestData.setUp(); + ADMIN = new User("Admin", "admin@javaops.ru", UserFlag.superuser, SPB.getId()); + DELETED = new User("Deleted", "deleted@yandex.ru", UserFlag.deleted, SPB.getId()); + FULL_NAME = new User("Full Name", "gmail@gmail.com", UserFlag.active, KIEV.getId()); + USER1 = new User("User1", "user1@gmail.com", UserFlag.active, MOSCOW.getId()); + USER2 = new User("User2", "user2@yandex.ru", UserFlag.active, KIEV.getId()); + USER3 = new User("User3", "user3@yandex.ru", UserFlag.active, MINSK.getId()); + FIST5_USERS = ImmutableList.of(ADMIN, DELETED, FULL_NAME, USER1, USER2); + } + + public static void setUp() { + UserDao dao = DBIProvider.getDao(UserDao.class); + dao.clean(); + DBIProvider.getDBI().useTransaction((conn, status) -> { + FIST5_USERS.forEach(dao::insert); + dao.insert(USER3); + }); + } +} diff --git a/persist/src/test/java/ru/javaops/masterjava/persist/dao/AbstractDaoTest.java b/persist/src/test/java/ru/javaops/masterjava/persist/dao/AbstractDaoTest.java new file mode 100644 index 000000000..3e8a891a5 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/dao/AbstractDaoTest.java @@ -0,0 +1,35 @@ +package ru.javaops.masterjava.persist.dao; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Rule; +import org.junit.rules.TestRule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; +import ru.javaops.masterjava.persist.DBIProvider; +import ru.javaops.masterjava.persist.DBITestProvider; + +@Slf4j +public abstract class AbstractDaoTest { + static { + DBITestProvider.initDBI(); + } + + @Rule + public TestRule testWatcher = new TestWatcher() { + @Override + protected void starting(Description description) { + log.info("\n\n+++ Start " + description.getDisplayName()); + } + + @Override + protected void finished(Description description) { + log.info("\n+++ Finish " + description.getDisplayName() + '\n'); + } + }; + + protected DAO dao; + + protected AbstractDaoTest(Class daoClass) { + this.dao = DBIProvider.getDao(daoClass); + } +} diff --git a/persist/src/test/java/ru/javaops/masterjava/persist/dao/CityDaoTest.java b/persist/src/test/java/ru/javaops/masterjava/persist/dao/CityDaoTest.java new file mode 100644 index 000000000..02d771475 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/dao/CityDaoTest.java @@ -0,0 +1,30 @@ +package ru.javaops.masterjava.persist.dao; + +import org.junit.Before; +import org.junit.Test; +import ru.javaops.masterjava.persist.CityTestData; +import ru.javaops.masterjava.persist.model.City; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static ru.javaops.masterjava.persist.CityTestData.CITIES; + +public class CityDaoTest extends AbstractDaoTest { + + public CityDaoTest() { + super(CityDao.class); + } + + @Before + public void setUp() throws Exception { + CityTestData.setUp(); + } + + @Test + public void getAll() throws Exception { + final Map cities = dao.getAsMap(); + assertEquals(CITIES, cities); + System.out.println(cities.values()); + } +} \ No newline at end of file diff --git a/persist/src/test/java/ru/javaops/masterjava/persist/dao/GroupDaoTest.java b/persist/src/test/java/ru/javaops/masterjava/persist/dao/GroupDaoTest.java new file mode 100644 index 000000000..467fad4b0 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/dao/GroupDaoTest.java @@ -0,0 +1,36 @@ +package ru.javaops.masterjava.persist.dao; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import ru.javaops.masterjava.persist.GroupTestData; +import ru.javaops.masterjava.persist.model.Group; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static ru.javaops.masterjava.persist.GroupTestData.GROUPS; + +public class GroupDaoTest extends AbstractDaoTest { + + public GroupDaoTest() { + super(GroupDao.class); + } + + @BeforeClass + public static void init() throws Exception { + GroupTestData.init(); + } + + @Before + public void setUp() throws Exception { + GroupTestData.setUp(); + } + + @Test + public void getAll() throws Exception { + final Map projects = dao.getAsMap(); + assertEquals(GROUPS, projects); + System.out.println(projects.values()); + } +} \ No newline at end of file diff --git a/persist/src/test/java/ru/javaops/masterjava/persist/dao/ProjectDaoTest.java b/persist/src/test/java/ru/javaops/masterjava/persist/dao/ProjectDaoTest.java new file mode 100644 index 000000000..1577859b0 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/dao/ProjectDaoTest.java @@ -0,0 +1,30 @@ +package ru.javaops.masterjava.persist.dao; + +import org.junit.Before; +import org.junit.Test; +import ru.javaops.masterjava.persist.ProjectTestData; +import ru.javaops.masterjava.persist.model.Project; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static ru.javaops.masterjava.persist.ProjectTestData.PROJECTS; + +public class ProjectDaoTest extends AbstractDaoTest { + + public ProjectDaoTest() { + super(ProjectDao.class); + } + + @Before + public void setUp() throws Exception { + ProjectTestData.setUp(); + } + + @Test + public void getAll() throws Exception { + final Map projects = dao.getAsMap(); + assertEquals(PROJECTS, projects); + System.out.println(projects.values()); + } +} \ No newline at end of file diff --git a/persist/src/test/java/ru/javaops/masterjava/persist/dao/UserDaoTest.java b/persist/src/test/java/ru/javaops/masterjava/persist/dao/UserDaoTest.java new file mode 100644 index 000000000..1dd6ed408 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/dao/UserDaoTest.java @@ -0,0 +1,53 @@ +package ru.javaops.masterjava.persist.dao; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import ru.javaops.masterjava.persist.UserTestData; +import ru.javaops.masterjava.persist.model.User; + +import java.util.List; + +import static ru.javaops.masterjava.persist.UserTestData.FIST5_USERS; + +/** + * gkislin + * 27.10.2016 + */ +public class UserDaoTest extends AbstractDaoTest { + + public UserDaoTest() { + super(UserDao.class); + } + + @BeforeClass + public static void init() throws Exception { + UserTestData.init(); + } + + @Before + public void setUp() throws Exception { + UserTestData.setUp(); + } + + @Test + public void getWithLimit() { + List users = dao.getWithLimit(5); + Assert.assertEquals(FIST5_USERS, users); + } + + @Test + public void insertBatch() throws Exception { + dao.clean(); + dao.insertBatch(FIST5_USERS, 3); + Assert.assertEquals(5, dao.getWithLimit(100).size()); + } + + @Test + public void getSeqAndSkip() throws Exception { + int seq1 = dao.getSeqAndSkip(5); + int seq2 = dao.getSeqAndSkip(1); + Assert.assertEquals(5, seq2 - seq1); + } +} \ No newline at end of file diff --git a/persist/src/test/java/ru/javaops/masterjava/persist/dao/UserGroupDaoTest.java b/persist/src/test/java/ru/javaops/masterjava/persist/dao/UserGroupDaoTest.java new file mode 100644 index 000000000..a46f6dbd3 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/dao/UserGroupDaoTest.java @@ -0,0 +1,39 @@ +package ru.javaops.masterjava.persist.dao; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import ru.javaops.masterjava.persist.UserGroupTestData; + +import java.util.Set; + +import static ru.javaops.masterjava.persist.GroupTestData.MASTERJAVA_01_ID; +import static ru.javaops.masterjava.persist.GroupTestData.TOPJAVA_07_ID; +import static ru.javaops.masterjava.persist.UserGroupTestData.getByGroupId; + +public class UserGroupDaoTest extends AbstractDaoTest { + + public UserGroupDaoTest() { + super(UserGroupDao.class); + } + + @BeforeClass + public static void init() throws Exception { + UserGroupTestData.init(); + } + + @Before + public void setUp() throws Exception { + UserGroupTestData.setUp(); + } + + @Test + public void getAll() throws Exception { + Set userIds = dao.getUserIds(MASTERJAVA_01_ID); + Assert.assertEquals(getByGroupId(MASTERJAVA_01_ID), userIds); + + userIds = dao.getUserIds(TOPJAVA_07_ID); + Assert.assertEquals(getByGroupId(TOPJAVA_07_ID), userIds); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..e6aa92b8b --- /dev/null +++ b/pom.xml @@ -0,0 +1,25 @@ + + 4.0.0 + + ru.javaops + masterjava + pom + + 1.0-SNAPSHOT + + MasterJava Root + https://github.com/JavaOPs/masterjava + + + parent + parent-web + + common + persist + test + + web + services + + diff --git a/services/mail-api/pom.xml b/services/mail-api/pom.xml new file mode 100644 index 000000000..25c53388e --- /dev/null +++ b/services/mail-api/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + + ru.javaops + parent + ../../parent/pom.xml + 1.0-SNAPSHOT + + + mail-api + 1.0-SNAPSHOT + Mail API + + + + ${project.groupId} + common + ${project.version} + + + + \ No newline at end of file diff --git a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Addressee.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Addressee.java new file mode 100644 index 000000000..f30f68fc4 --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Addressee.java @@ -0,0 +1,17 @@ +package ru.javaops.masterjava.service.mail; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * gkislin + * 15.11.2016 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Addressee { + private String email; + private String name; +} diff --git a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailService.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailService.java new file mode 100644 index 000000000..2ebaf8d76 --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailService.java @@ -0,0 +1,21 @@ +package ru.javaops.masterjava.service.mail; + +import javax.jws.WebMethod; +import javax.jws.WebParam; +import javax.jws.WebService; +import java.util.List; + +/** + * gkislin + * 15.11.2016 + */ +@WebService +public interface MailService { + + @WebMethod + void sendMail( + @WebParam(name = "to") List to, + @WebParam(name = "cc") List cc, + @WebParam(name = "subject") String subject, + @WebParam(name = "body") String body); +} \ No newline at end of file diff --git a/services/mail-service/pom.xml b/services/mail-service/pom.xml new file mode 100644 index 000000000..2b4be6bad --- /dev/null +++ b/services/mail-service/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + + ru.javaops + parent-web + ../../parent-web/pom.xml + 1.0-SNAPSHOT + + + mail-service + 1.0-SNAPSHOT + war + Mail Service + + + + ${project.groupId} + mail-api + ${project.version} + + + \ No newline at end of file diff --git a/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailSender.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailSender.java new file mode 100644 index 000000000..a6147371a --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailSender.java @@ -0,0 +1,16 @@ +package ru.javaops.masterjava.service.mail; + +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +/** + * gkislin + * 15.11.2016 + */ +@Slf4j +public class MailSender { + static void sendMail(List to, List cc, String subject, String body) { + log.info("Send mail to \'" + to + "\' cc \'" + cc + "\' subject \'" + subject + (log.isDebugEnabled()?"\nbody=" + body:"")); + } +} diff --git a/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceExecutor.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceExecutor.java new file mode 100644 index 000000000..e26f302d2 --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceExecutor.java @@ -0,0 +1,141 @@ +package ru.javaops.masterjava.service.mail; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.*; +import java.util.stream.Collectors; + +public class MailServiceExecutor { + private static final String OK = "OK"; + + private static final String INTERRUPTED_BY_FAULTS_NUMBER = "+++ Interrupted by faults number"; + private static final String INTERRUPTED_BY_TIMEOUT = "+++ Interrupted by timeout"; + private static final String INTERRUPTED_EXCEPTION = "+++ InterruptedException"; + + private final ExecutorService mailExecutor = Executors.newFixedThreadPool(8); + + public GroupResult sendToList(final String template, final Set emails) throws Exception { + final CompletionService completionService = new ExecutorCompletionService<>(mailExecutor); + + List> futures = emails.stream() + .map(email -> completionService.submit(() -> sendToUser(template, email))) + .collect(Collectors.toList()); + + return new Callable() { + private int success = 0; + private List failed = new ArrayList<>(); + + @Override + public GroupResult call() { + while (!futures.isEmpty()) { + try { + Future future = completionService.poll(10, TimeUnit.SECONDS); + if (future == null) { + return cancelWithFail(INTERRUPTED_BY_TIMEOUT); + } + futures.remove(future); + MailResult mailResult = future.get(); + if (mailResult.isOk()) { + success++; + } else { + failed.add(mailResult); + if (failed.size() >= 5) { + return cancelWithFail(INTERRUPTED_BY_FAULTS_NUMBER); + } + } + } catch (ExecutionException e) { + return cancelWithFail(e.getCause().toString()); + } catch (InterruptedException e) { + return cancelWithFail(INTERRUPTED_EXCEPTION); + } + } +/* + for (Future future : futures) { + MailResult mailResult; + try { + mailResult = future.get(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + return cancelWithFail(INTERRUPTED_EXCEPTION); + } catch (ExecutionException e) { + return cancelWithFail(e.getCause().toString()); + } catch (TimeoutException e) { + return cancelWithFail(INTERRUPTED_BY_TIMEOUT); + } + if (mailResult.isOk()) { + success++; + } else { + failed.add(mailResult); + if (failed.size() >= 5) { + return cancelWithFail(INTERRUPTED_BY_FAULTS_NUMBER); + } + } + } +*/ + return new GroupResult(success, failed, null); + } + + private GroupResult cancelWithFail(String cause) { + futures.forEach(f -> f.cancel(true)); + return new GroupResult(success, failed, cause); + } + }.call(); + } + + // dummy realization + public MailResult sendToUser(String template, String email) throws Exception { + try { + Thread.sleep(500); //delay + } catch (InterruptedException e) { + // log cancel; + return null; + } + return Math.random() < 0.7 ? MailResult.ok(email) : MailResult.error(email, "Error"); + } + + public static class MailResult { + private final String email; + private final String result; + + private static MailResult ok(String email) { + return new MailResult(email, OK); + } + + private static MailResult error(String email, String error) { + return new MailResult(email, error); + } + + public boolean isOk() { + return OK.equals(result); + } + + private MailResult(String email, String cause) { + this.email = email; + this.result = cause; + } + + @Override + public String toString() { + return '(' + email + ',' + result + ')'; + } + } + + public static class GroupResult { + private final int success; // number of successfully sent email + private final List failed; // failed emails with causes + private final String failedCause; // global fail cause + + public GroupResult(int success, List failed, String failedCause) { + this.success = success; + this.failed = failed; + this.failedCause = failedCause; + } + + @Override + public String toString() { + return "Success: " + success + '\n' + + "Failed: " + failed.toString() + '\n' + + (failedCause == null ? "" : "Failed cause" + failedCause); + } + } +} \ No newline at end of file diff --git a/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java new file mode 100644 index 000000000..66cd43bea --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java @@ -0,0 +1,15 @@ +package ru.javaops.masterjava.service.mail; + +import javax.jws.WebService; +import java.util.List; + +/** + * gkislin + * 15.11.2016 + */ +@WebService(endpointInterface = "ru.javaops.masterjava.service.mail.MailService") +public class MailServiceImpl implements MailService { + public void sendMail(List to, List cc, String subject, String body) { + MailSender.sendMail(to, cc, subject, body); + } +} \ No newline at end of file diff --git a/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java new file mode 100644 index 000000000..4ef6f6d4f --- /dev/null +++ b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java @@ -0,0 +1,25 @@ +package ru.javaops.masterjava.service.mail; + +import com.google.common.collect.ImmutableList; + +import javax.xml.namespace.QName; +import javax.xml.ws.Service; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * User: gkislin + * Date: 28.05.2014 + */ +public class MailServiceClient { + + public static void main(String[] args) throws MalformedURLException { + QName qname = new QName("http://mail.service.masterjava.javaops.ru/", "MailServiceImplService"); + Service service = Service.create( + new URL("http://localhost:8080/mail/mailService?wsdl"), + new QName("http://mail.service.masterjava.javaops.ru/", "MailServiceImplService")); + + MailService mailService = service.getPort(MailService.class); + mailService.sendMail(ImmutableList.of(new Addressee("gkislin@yandex.ru", null)), null, "Subject", "Body"); + } +} diff --git a/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServicePublisher.java b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServicePublisher.java new file mode 100644 index 000000000..88de00ae3 --- /dev/null +++ b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServicePublisher.java @@ -0,0 +1,14 @@ +package ru.javaops.masterjava.service.mail; + +import javax.xml.ws.Endpoint; + +/** + * User: gkislin + * Date: 28.05.2014 + */ +public class MailServicePublisher { + + public static void main(String[] args) { + Endpoint.publish("http://localhost:8080/mail/mailService", new MailServiceImpl()); + } +} diff --git a/services/pom.xml b/services/pom.xml new file mode 100644 index 000000000..bb7824194 --- /dev/null +++ b/services/pom.xml @@ -0,0 +1,15 @@ + + 4.0.0 + + ru.javaops + services + pom + 1.0-SNAPSHOT + + MasterJava Services + + mail-api + mail-service + + diff --git a/test/pom.xml b/test/pom.xml new file mode 100644 index 000000000..bfab1855d --- /dev/null +++ b/test/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + + ru.javaops + parent + ../parent/pom.xml + 1.0-SNAPSHOT + + + test + 1.0-SNAPSHOT + Test + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.2 + + + package + + shade + + + benchmarks + + + org.openjdk.jmh.Main + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + + + ${project.groupId} + common + ${project.version} + + + org.openjdk.jmh + jmh-core + 1.15 + + + org.openjdk.jmh + jmh-generator-annprocess + 1.15 + provided + + + \ No newline at end of file diff --git a/test/src/main/java/ru/javaops/masterjava/matrix/MainMatrix.java b/test/src/main/java/ru/javaops/masterjava/matrix/MainMatrix.java new file mode 100644 index 000000000..4f30e499a --- /dev/null +++ b/test/src/main/java/ru/javaops/masterjava/matrix/MainMatrix.java @@ -0,0 +1,52 @@ +package ru.javaops.masterjava.matrix; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * gkislin + * 03.07.2016 + */ +public class MainMatrix { + private static final int MATRIX_SIZE = 1000; + private static final int THREAD_NUMBER = 10; + + private final static ExecutorService executor = Executors.newFixedThreadPool(MainMatrix.THREAD_NUMBER); + + public static void main(String[] args) throws ExecutionException, InterruptedException { + final int[][] matrixA = MatrixUtil.create(MATRIX_SIZE); + final int[][] matrixB = MatrixUtil.create(MATRIX_SIZE); + + double singleThreadSum = 0.; + double concurrentThreadSum = 0.; + int count = 1; + while (count < 6) { + System.out.println("Pass " + count); + long start = System.currentTimeMillis(); + final int[][] matrixC = MatrixUtil.singleThreadMultiplyOpt(matrixA, matrixB); + double duration = (System.currentTimeMillis() - start) / 1000.; + out("Single thread time, sec: %.3f", duration); + singleThreadSum += duration; + + start = System.currentTimeMillis(); + final int[][] concurrentMatrixC = MatrixUtil.concurrentMultiply2(matrixA, matrixB, executor); + duration = (System.currentTimeMillis() - start) / 1000.; + out("Concurrent thread time, sec: %.3f", duration); + concurrentThreadSum += duration; + + if (!MatrixUtil.compare(matrixC, concurrentMatrixC)) { + System.err.println("Comparison failed"); + break; + } + count++; + } + executor.shutdown(); + out("\nAverage single thread time, sec: %.3f", singleThreadSum / 5.); + out("Average concurrent thread time, sec: %.3f", concurrentThreadSum / 5.); + } + + private static void out(String format, double ms) { + System.out.println(String.format(format, ms)); + } +} diff --git a/test/src/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java b/test/src/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java new file mode 100644 index 000000000..80f1558ea --- /dev/null +++ b/test/src/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java @@ -0,0 +1,77 @@ +package ru.javaops.masterjava.matrix; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.TimeValue; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * gkislin + * 23.09.2016 + */ +@Warmup(iterations = 10) +@Measurement(iterations = 10) +@BenchmarkMode({Mode.SingleShotTime}) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +@Threads(1) +@Fork(10) +@Timeout(time = 5, timeUnit = TimeUnit.MINUTES) +public class MatrixBenchmark { + // Matrix size + @Param({"1000"}) + private int matrixSize; + + private static final int THREAD_NUMBER = 10; + private final static ExecutorService executor = Executors.newFixedThreadPool(THREAD_NUMBER); + + private static int[][] matrixA; + private static int[][] matrixB; + + @Setup + public void setUp() { + matrixA = MatrixUtil.create(matrixSize); + matrixB = MatrixUtil.create(matrixSize); + } + + public static void main(String[] args) throws RunnerException { + Options options = new OptionsBuilder() + .include(MatrixBenchmark.class.getSimpleName()) + .threads(1) + .forks(10) + .timeout(TimeValue.minutes(5)) + .build(); + new Runner(options).run(); + } + +// @Benchmark + public int[][] singleThreadMultiplyOpt() throws Exception { + return MatrixUtil.singleThreadMultiplyOpt(matrixA, matrixB); + } + +// @Benchmark + public int[][] concurrentMultiplyStreams() throws Exception { + return MatrixUtil.concurrentMultiplyStreams(matrixA, matrixB, executor); + } + + @Benchmark + public int[][] concurrentMultiply2() throws Exception { + return MatrixUtil.concurrentMultiply2(matrixA, matrixB, executor); + } + + @Benchmark + public int[][] concurrentMultiply3() throws Exception { + return MatrixUtil.concurrentMultiply3(matrixA, matrixB, executor); + } + + @TearDown + public void tearDown() { + executor.shutdown(); + } +} diff --git a/test/src/main/java/ru/javaops/masterjava/matrix/MatrixUtil.java b/test/src/main/java/ru/javaops/masterjava/matrix/MatrixUtil.java new file mode 100644 index 000000000..39f077898 --- /dev/null +++ b/test/src/main/java/ru/javaops/masterjava/matrix/MatrixUtil.java @@ -0,0 +1,154 @@ +package ru.javaops.masterjava.matrix; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * gkislin + * 03.07.2016 + */ +public class MatrixUtil { + + public static int[][] concurrentMultiplyStreams(int[][] matrixA, int[][] matrixB, ExecutorService executor) + throws InterruptedException, ExecutionException { + + final int matrixSize = matrixA.length; + final int[][] matrixC = new int[matrixSize][matrixSize]; + + List> tasks = IntStream.range(0, matrixSize) + .parallel() + .mapToObj(i -> new Callable() { + private final int[] tempColumn = new int[matrixSize]; + + @Override + public Void call() throws Exception { + for (int c = 0; c < matrixSize; c++) { + tempColumn[c] = matrixB[c][i]; + } + for (int j = 0; j < matrixSize; j++) { + int row[] = matrixA[j]; + int sum = 0; + for (int k = 0; k < matrixSize; k++) { + sum += tempColumn[k] * row[k]; + } + matrixC[j][i] = sum; + } + return null; + } + }) + .collect(Collectors.toList()); + + executor.invokeAll(tasks); + return matrixC; + } + + public static int[][] concurrentMultiply2(int[][] matrixA, int[][] matrixB, ExecutorService executor) throws InterruptedException, ExecutionException { + final int matrixSize = matrixA.length; + final int[][] matrixC = new int[matrixSize][]; + + final int[][] matrixBT = new int[matrixSize][matrixSize]; + for (int i = 0; i < matrixSize; i++) { + for (int j = 0; j < matrixSize; j++) { + matrixBT[i][j] = matrixB[j][i]; + } + } + + List> tasks = new ArrayList<>(matrixSize); + for (int j = 0; j < matrixSize; j++) { + final int row = j; + tasks.add(() -> { + final int[] rowC = new int[matrixSize]; + for (int col = 0; col < matrixSize; col++) { + final int[] rowA = matrixA[row]; + final int[] columnB = matrixBT[col]; + int sum = 0; + for (int k = 0; k < matrixSize; k++) { + sum += rowA[k] * columnB[k]; + } + rowC[col] = sum; + } + matrixC[row] = rowC; + return null; + }); + } + executor.invokeAll(tasks); + return matrixC; + } + + public static int[][] concurrentMultiply3(int[][] matrixA, int[][] matrixB, ExecutorService executor) throws InterruptedException { + final int matrixSize = matrixA.length; + final int[][] matrixC = new int[matrixSize][matrixSize]; + final CountDownLatch latch = new CountDownLatch(matrixSize); + + for (int row = 0; row < matrixSize; row++) { + final int[] rowA = matrixA[row]; + final int[] rowC = matrixC[row]; + + executor.submit(() -> { + for (int idx = 0; idx < matrixSize; idx++) { + final int elA = rowA[idx]; + final int[] rowB = matrixB[idx]; + for (int col = 0; col < matrixSize; col++) { + rowC[col] += elA * rowB[col]; + } + } + latch.countDown(); + }); + } + latch.await(); + return matrixC; + } + + public static int[][] singleThreadMultiplyOpt(int[][] matrixA, int[][] matrixB) { + final int matrixSize = matrixA.length; + final int[][] matrixC = new int[matrixSize][matrixSize]; + + for (int col = 0; col < matrixSize; col++) { + final int[] columnB = new int[matrixSize]; + for (int k = 0; k < matrixSize; k++) { + columnB[k] = matrixB[k][col]; + } + + for (int row = 0; row < matrixSize; row++) { + int sum = 0; + final int[] rowA = matrixA[row]; + for (int k = 0; k < matrixSize; k++) { + sum += rowA[k] * columnB[k]; + } + matrixC[row][col] = sum; + } + } + return matrixC; + } + + public static int[][] create(int size) { + int[][] matrix = new int[size][size]; + Random rn = new Random(); + + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + matrix[i][j] = rn.nextInt(10); + } + } + return matrix; + } + + public static boolean compare(int[][] matrixA, int[][] matrixB) { + final int matrixSize = matrixA.length; + for (int i = 0; i < matrixSize; i++) { + for (int j = 0; j < matrixSize; j++) { + if (matrixA[i][j] != matrixB[i][j]) { + return false; + } + } + } + return true; + } +} \ No newline at end of file diff --git a/web/common-web/pom.xml b/web/common-web/pom.xml new file mode 100644 index 000000000..da4f46174 --- /dev/null +++ b/web/common-web/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + ru.javaops + parent + ../../parent/pom.xml + 1.0-SNAPSHOT + + + common-web + 1.0-SNAPSHOT + Common Web + + + + ${project.groupId} + common + ${project.version} + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + + org.thymeleaf + thymeleaf + 3.0.3.RELEASE + + + org.slf4j + slf4j-api + + + + + \ No newline at end of file diff --git a/web/common-web/src/main/java/ru/javaops/masterjava/common/web/ThymeleafListener.java b/web/common-web/src/main/java/ru/javaops/masterjava/common/web/ThymeleafListener.java new file mode 100644 index 000000000..16948aa44 --- /dev/null +++ b/web/common-web/src/main/java/ru/javaops/masterjava/common/web/ThymeleafListener.java @@ -0,0 +1,20 @@ +package ru.javaops.masterjava.common.web; + +import org.thymeleaf.TemplateEngine; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; + +@WebListener +public class ThymeleafListener implements ServletContextListener { + + public static TemplateEngine engine; + + public void contextInitialized(ServletContextEvent sce) { + engine = ThymeleafUtil.getTemplateEngine(sce.getServletContext()); + } + + public void contextDestroyed(ServletContextEvent sce) { + } +} diff --git a/web/common-web/src/main/java/ru/javaops/masterjava/common/web/ThymeleafUtil.java b/web/common-web/src/main/java/ru/javaops/masterjava/common/web/ThymeleafUtil.java new file mode 100644 index 000000000..bf87ed3eb --- /dev/null +++ b/web/common-web/src/main/java/ru/javaops/masterjava/common/web/ThymeleafUtil.java @@ -0,0 +1,24 @@ +package ru.javaops.masterjava.common.web; + +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.templatemode.TemplateMode; +import org.thymeleaf.templateresolver.ServletContextTemplateResolver; + +import javax.servlet.ServletContext; + +public class ThymeleafUtil { + + private ThymeleafUtil() { + } + + public static TemplateEngine getTemplateEngine(ServletContext context) { + final ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(context); + templateResolver.setTemplateMode(TemplateMode.HTML); + templateResolver.setPrefix("/WEB-INF/templates/"); + templateResolver.setSuffix(".html"); + templateResolver.setCacheTTLMs(1000L); + final TemplateEngine engine = new TemplateEngine(); + engine.setTemplateResolver(templateResolver); + return engine; + } +} diff --git a/web/export/pom.xml b/web/export/pom.xml new file mode 100644 index 000000000..1d7e0c298 --- /dev/null +++ b/web/export/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + + ru.javaops + parent-web + ../../parent-web/pom.xml + 1.0-SNAPSHOT + + + export + 1.0-SNAPSHOT + war + Export + + + export + + + + + ${project.groupId} + persist + ${project.version} + + + com.j2html + j2html + 0.7 + + + com.google.guava + guava + + + + + ${project.groupId} + persist + ${project.version} + test-jar + test + + + + \ No newline at end of file diff --git a/web/export/src/main/java/ru/javaops/masterjava/export/CityImporter.java b/web/export/src/main/java/ru/javaops/masterjava/export/CityImporter.java new file mode 100644 index 000000000..d503ecea2 --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/export/CityImporter.java @@ -0,0 +1,35 @@ +package ru.javaops.masterjava.export; + +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import ru.javaops.masterjava.persist.DBIProvider; +import ru.javaops.masterjava.persist.dao.CityDao; +import ru.javaops.masterjava.persist.model.City; +import ru.javaops.masterjava.xml.util.StaxStreamProcessor; + +import javax.xml.stream.XMLStreamException; +import java.util.ArrayList; +import java.util.Map; + +/** + * gkislin + * 15.11.2016 + */ +@Slf4j +public class CityImporter { + private final CityDao cityDao = DBIProvider.getDao(CityDao.class); + public Map process(StaxStreamProcessor processor) throws XMLStreamException { + val map = cityDao.getAsMap(); + val newCities = new ArrayList(); + + while (processor.startElement("City", "Cities")) { + val ref = processor.getAttribute("id"); + if (!map.containsKey(ref)) { + newCities.add(new City(null, ref, processor.getText())); + } + } + log.info("Insert batch " + newCities); + cityDao.insertBatch(newCities); + return cityDao.getAsMap(); + } +} \ No newline at end of file diff --git a/web/export/src/main/java/ru/javaops/masterjava/export/PayloadImporter.java b/web/export/src/main/java/ru/javaops/masterjava/export/PayloadImporter.java new file mode 100644 index 000000000..6e762a431 --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/export/PayloadImporter.java @@ -0,0 +1,31 @@ +package ru.javaops.masterjava.export; + +import lombok.Value; +import lombok.val; +import ru.javaops.masterjava.xml.util.StaxStreamProcessor; + +import javax.xml.stream.XMLStreamException; +import java.io.InputStream; +import java.util.List; + +public class PayloadImporter { + private final CityImporter cityImporter = new CityImporter(); + private final UserImporter userImporter = new UserImporter(); + + @Value + public static class FailedEmail { + public String emailOrRange; + public String reason; + + @Override + public String toString() { + return emailOrRange + " : " + reason; + } + } + + public List process(InputStream is, int chunkSize) throws XMLStreamException { + final StaxStreamProcessor processor = new StaxStreamProcessor(is); + val cities = cityImporter.process(processor); + return userImporter.process(processor, cities, chunkSize); + } +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/export/UploadServlet.java b/web/export/src/main/java/ru/javaops/masterjava/export/UploadServlet.java new file mode 100644 index 000000000..9c0147f9b --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/export/UploadServlet.java @@ -0,0 +1,68 @@ +package ru.javaops.masterjava.export; + +import com.google.common.collect.ImmutableMap; +import lombok.extern.slf4j.Slf4j; +import org.thymeleaf.context.WebContext; + +import javax.servlet.ServletException; +import javax.servlet.annotation.MultipartConfig; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.Part; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import static ru.javaops.masterjava.common.web.ThymeleafListener.engine; + +@WebServlet("/") +@MultipartConfig +@Slf4j +public class UploadServlet extends HttpServlet { + private static final int CHUNK_SIZE = 2000; + + private final PayloadImporter payloadImporter = new PayloadImporter(); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + outExport(req, resp, "", CHUNK_SIZE); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String message; + int chunkSize = CHUNK_SIZE; + try { +// http://docs.oracle.com/javaee/6/tutorial/doc/glraq.html + chunkSize = Integer.parseInt(req.getParameter("chunkSize")); + if (chunkSize < 1) { + message = "Chunk Size must be > 1"; + } else { + Part filePart = req.getPart("fileToUpload"); + try (InputStream is = filePart.getInputStream()) { + List failed = payloadImporter.process(is, chunkSize); + log.info("Failed users: " + failed); + final WebContext webContext = + new WebContext(req, resp, req.getServletContext(), req.getLocale(), + ImmutableMap.of("failed", failed)); + engine.process("result", webContext, resp.getWriter()); + return; + } + } + } catch (Exception e) { + log.info(e.getMessage(), e); + message = e.toString(); + } + outExport(req, resp, message, chunkSize); + } + + private void outExport(HttpServletRequest req, HttpServletResponse resp, String message, int chunkSize) throws IOException { + resp.setCharacterEncoding("utf-8"); + final WebContext webContext = + new WebContext(req, resp, req.getServletContext(), req.getLocale(), + ImmutableMap.of("message", message, "chunkSize", chunkSize)); + engine.process("export", webContext, resp.getWriter()); + } +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/export/UserImporter.java b/web/export/src/main/java/ru/javaops/masterjava/export/UserImporter.java new file mode 100644 index 000000000..dcaf97ea0 --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/export/UserImporter.java @@ -0,0 +1,103 @@ +package ru.javaops.masterjava.export; + +import lombok.extern.slf4j.Slf4j; +import one.util.streamex.StreamEx; +import ru.javaops.masterjava.export.PayloadImporter.FailedEmail; +import ru.javaops.masterjava.persist.DBIProvider; +import ru.javaops.masterjava.persist.dao.UserDao; +import ru.javaops.masterjava.persist.model.City; +import ru.javaops.masterjava.persist.model.User; +import ru.javaops.masterjava.persist.model.UserFlag; +import ru.javaops.masterjava.xml.util.StaxStreamProcessor; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/** + * gkislin + * 14.10.2016 + */ +@Slf4j +public class UserImporter { + + private static final int NUMBER_THREADS = 4; + private final ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_THREADS); + private final UserDao userDao = DBIProvider.getDao(UserDao.class); + + public List process(StaxStreamProcessor processor, Map cities, int chunkSize) throws XMLStreamException { + log.info("Start proseccing with chunkSize=" + chunkSize); + + return new Callable>() { + class ChunkFuture { + String emailRange; + Future> future; + + public ChunkFuture(List chunk, Future> future) { + this.future = future; + this.emailRange = chunk.get(0).getEmail(); + if (chunk.size() > 1) { + this.emailRange += '-' + chunk.get(chunk.size() - 1).getEmail(); + } + } + } + + @Override + public List call() throws XMLStreamException { + List futures = new ArrayList<>(); + + int id = userDao.getSeqAndSkip(chunkSize); + List chunk = new ArrayList<>(chunkSize); + List failed = new ArrayList<>(); + + while (processor.doUntil(XMLEvent.START_ELEMENT, "User")) { + final String email = processor.getAttribute("email"); + String cityRef = processor.getAttribute("city"); + City city = cities.get(cityRef); + if (city == null) { + failed.add(new FailedEmail(email, "City '" + cityRef + "' is not present in DB")); + } else { + final UserFlag flag = UserFlag.valueOf(processor.getAttribute("flag")); + final String fullName = processor.getReader().getElementText(); + final User user = new User(id++, fullName, email, flag, city.getId()); + chunk.add(user); + if (chunk.size() == chunkSize) { + futures.add(submit(chunk)); + chunk = new ArrayList<>(chunkSize); + id = userDao.getSeqAndSkip(chunkSize); + } + } + } + + if (!chunk.isEmpty()) { + futures.add(submit(chunk)); + } + + futures.forEach(cf -> { + try { + failed.addAll(StreamEx.of(cf.future.get()).map(email -> new FailedEmail(email, "already present")).toList()); + log.info(cf.emailRange + " successfully executed"); + } catch (Exception e) { + log.error(cf.emailRange + " failed", e); + failed.add(new FailedEmail(cf.emailRange, e.toString())); + } + }); + return failed; + } + + private ChunkFuture submit(List chunk) { + ChunkFuture chunkFuture = new ChunkFuture(chunk, + executorService.submit(() -> userDao.insertAndGetConflictEmails(chunk)) + ); + log.info("Submit " + chunkFuture.emailRange); + return chunkFuture; + } + }.call(); + } +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/xml/schema/CityType.java b/web/export/src/main/java/ru/javaops/masterjava/xml/schema/CityType.java new file mode 100644 index 000000000..029e352cb --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/xml/schema/CityType.java @@ -0,0 +1,94 @@ + +package ru.javaops.masterjava.xml.schema; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlID; +import javax.xml.bind.annotation.XmlSchemaType; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.XmlValue; +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + + +/** + *

Java class for cityType complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType name="cityType">
+ *   <simpleContent>
+ *     <extension base="<http://www.w3.org/2001/XMLSchema>string">
+ *       <attribute name="id" use="required" type="{http://www.w3.org/2001/XMLSchema}ID" />
+ *     </extension>
+ *   </simpleContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "cityType", namespace = "http://javaops.ru", propOrder = { + "value" +}) +public class CityType { + + @XmlValue + protected String value; + @XmlAttribute(name = "id", required = true) + @XmlJavaTypeAdapter(CollapsedStringAdapter.class) + @XmlID + @XmlSchemaType(name = "ID") + protected String id; + + /** + * Gets the value of the value property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getValue() { + return value; + } + + /** + * Sets the value of the value property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setValue(String value) { + this.value = value; + } + + /** + * Gets the value of the id property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getId() { + return id; + } + + /** + * Sets the value of the id property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setId(String value) { + this.id = value; + } + +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/xml/schema/FlagType.java b/web/export/src/main/java/ru/javaops/masterjava/xml/schema/FlagType.java new file mode 100644 index 000000000..eda39fa9a --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/xml/schema/FlagType.java @@ -0,0 +1,54 @@ + +package ru.javaops.masterjava.xml.schema; + +import javax.xml.bind.annotation.XmlEnum; +import javax.xml.bind.annotation.XmlEnumValue; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for flagType. + * + *

The following schema fragment specifies the expected content contained within this class. + *

+ *

+ * <simpleType name="flagType">
+ *   <restriction base="{http://www.w3.org/2001/XMLSchema}string">
+ *     <enumeration value="active"/>
+ *     <enumeration value="deleted"/>
+ *     <enumeration value="superuser"/>
+ *   </restriction>
+ * </simpleType>
+ * 
+ * + */ +@XmlType(name = "flagType", namespace = "http://javaops.ru") +@XmlEnum +public enum FlagType { + + @XmlEnumValue("active") + ACTIVE("active"), + @XmlEnumValue("deleted") + DELETED("deleted"), + @XmlEnumValue("superuser") + SUPERUSER("superuser"); + private final String value; + + FlagType(String v) { + value = v; + } + + public String value() { + return value; + } + + public static FlagType fromValue(String v) { + for (FlagType c: FlagType.values()) { + if (c.value.equals(v)) { + return c; + } + } + throw new IllegalArgumentException(v); + } + +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/xml/schema/GroupType.java b/web/export/src/main/java/ru/javaops/masterjava/xml/schema/GroupType.java new file mode 100644 index 000000000..d5041640b --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/xml/schema/GroupType.java @@ -0,0 +1,40 @@ + +package ru.javaops.masterjava.xml.schema; + +import javax.xml.bind.annotation.XmlEnum; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for groupType. + * + *

The following schema fragment specifies the expected content contained within this class. + *

+ *

+ * <simpleType name="groupType">
+ *   <restriction base="{http://www.w3.org/2001/XMLSchema}string">
+ *     <enumeration value="REGISTERING"/>
+ *     <enumeration value="CURRENT"/>
+ *     <enumeration value="FINISHED"/>
+ *   </restriction>
+ * </simpleType>
+ * 
+ * + */ +@XmlType(name = "groupType", namespace = "http://javaops.ru") +@XmlEnum +public enum GroupType { + + REGISTERING, + CURRENT, + FINISHED; + + public String value() { + return name(); + } + + public static GroupType fromValue(String v) { + return valueOf(v); + } + +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java b/web/export/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java new file mode 100644 index 000000000..bfb393299 --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java @@ -0,0 +1,109 @@ + +package ru.javaops.masterjava.xml.schema; + +import javax.xml.bind.JAXBElement; +import javax.xml.bind.annotation.XmlElementDecl; +import javax.xml.bind.annotation.XmlRegistry; +import javax.xml.namespace.QName; + + +/** + * This object contains factory methods for each + * Java content interface and Java element interface + * generated in the ru.javaops.masterjava.xml.schema package. + *

An ObjectFactory allows you to programatically + * construct new instances of the Java representation + * for XML content. The Java representation of XML + * content can consist of schema derived interfaces + * and classes representing the binding of schema + * type definitions, element declarations and model + * groups. Factory methods for each of these are + * provided in this class. + * + */ +@XmlRegistry +public class ObjectFactory { + + private final static QName _City_QNAME = new QName("http://javaops.ru", "City"); + + /** + * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: ru.javaops.masterjava.xml.schema + * + */ + public ObjectFactory() { + } + + /** + * Create an instance of {@link Project } + * + */ + public Project createProject() { + return new Project(); + } + + /** + * Create an instance of {@link Payload } + * + */ + public Payload createPayload() { + return new Payload(); + } + + /** + * Create an instance of {@link Project.Group } + * + */ + public Project.Group createProjectGroup() { + return new Project.Group(); + } + + /** + * Create an instance of {@link User } + * + */ + public User createUser() { + return new User(); + } + + /** + * Create an instance of {@link Payload.Projects } + * + */ + public Payload.Projects createPayloadProjects() { + return new Payload.Projects(); + } + + /** + * Create an instance of {@link Payload.Cities } + * + */ + public Payload.Cities createPayloadCities() { + return new Payload.Cities(); + } + + /** + * Create an instance of {@link Payload.Users } + * + */ + public Payload.Users createPayloadUsers() { + return new Payload.Users(); + } + + /** + * Create an instance of {@link CityType } + * + */ + public CityType createCityType() { + return new CityType(); + } + + /** + * Create an instance of {@link JAXBElement }{@code <}{@link CityType }{@code >}} + * + */ + @XmlElementDecl(namespace = "http://javaops.ru", name = "City") + public JAXBElement createCity(CityType value) { + return new JAXBElement(_City_QNAME, CityType.class, null, value); + } + +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java b/web/export/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java new file mode 100644 index 000000000..f4a8070e9 --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java @@ -0,0 +1,332 @@ + +package ru.javaops.masterjava.xml.schema; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType>
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element name="Projects">
+ *           <complexType>
+ *             <complexContent>
+ *               <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                 <sequence maxOccurs="unbounded">
+ *                   <element ref="{http://javaops.ru}Project"/>
+ *                 </sequence>
+ *               </restriction>
+ *             </complexContent>
+ *           </complexType>
+ *         </element>
+ *         <element name="Cities">
+ *           <complexType>
+ *             <complexContent>
+ *               <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                 <sequence maxOccurs="unbounded">
+ *                   <element ref="{http://javaops.ru}City"/>
+ *                 </sequence>
+ *               </restriction>
+ *             </complexContent>
+ *           </complexType>
+ *         </element>
+ *         <element name="Users">
+ *           <complexType>
+ *             <complexContent>
+ *               <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                 <sequence maxOccurs="unbounded" minOccurs="0">
+ *                   <element ref="{http://javaops.ru}User"/>
+ *                 </sequence>
+ *               </restriction>
+ *             </complexContent>
+ *           </complexType>
+ *         </element>
+ *       </sequence>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "projects", + "cities", + "users" +}) +@XmlRootElement(name = "Payload", namespace = "http://javaops.ru") +public class Payload { + + @XmlElement(name = "Projects", namespace = "http://javaops.ru", required = true) + protected Payload.Projects projects; + @XmlElement(name = "Cities", namespace = "http://javaops.ru", required = true) + protected Payload.Cities cities; + @XmlElement(name = "Users", namespace = "http://javaops.ru", required = true) + protected Payload.Users users; + + /** + * Gets the value of the projects property. + * + * @return + * possible object is + * {@link Payload.Projects } + * + */ + public Payload.Projects getProjects() { + return projects; + } + + /** + * Sets the value of the projects property. + * + * @param value + * allowed object is + * {@link Payload.Projects } + * + */ + public void setProjects(Payload.Projects value) { + this.projects = value; + } + + /** + * Gets the value of the cities property. + * + * @return + * possible object is + * {@link Payload.Cities } + * + */ + public Payload.Cities getCities() { + return cities; + } + + /** + * Sets the value of the cities property. + * + * @param value + * allowed object is + * {@link Payload.Cities } + * + */ + public void setCities(Payload.Cities value) { + this.cities = value; + } + + /** + * Gets the value of the users property. + * + * @return + * possible object is + * {@link Payload.Users } + * + */ + public Payload.Users getUsers() { + return users; + } + + /** + * Sets the value of the users property. + * + * @param value + * allowed object is + * {@link Payload.Users } + * + */ + public void setUsers(Payload.Users value) { + this.users = value; + } + + + /** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+     * <complexType>
+     *   <complexContent>
+     *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *       <sequence maxOccurs="unbounded">
+     *         <element ref="{http://javaops.ru}City"/>
+     *       </sequence>
+     *     </restriction>
+     *   </complexContent>
+     * </complexType>
+     * 
+ * + * + */ + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "city" + }) + public static class Cities { + + @XmlElement(name = "City", namespace = "http://javaops.ru", required = true) + protected List city; + + /** + * Gets the value of the city property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the city property. + * + *

+ * For example, to add a new item, do as follows: + *

+         *    getCity().add(newItem);
+         * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link CityType } + * + * + */ + public List getCity() { + if (city == null) { + city = new ArrayList(); + } + return this.city; + } + + } + + + /** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+     * <complexType>
+     *   <complexContent>
+     *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *       <sequence maxOccurs="unbounded">
+     *         <element ref="{http://javaops.ru}Project"/>
+     *       </sequence>
+     *     </restriction>
+     *   </complexContent>
+     * </complexType>
+     * 
+ * + * + */ + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "project" + }) + public static class Projects { + + @XmlElement(name = "Project", namespace = "http://javaops.ru", required = true) + protected List project; + + /** + * Gets the value of the project property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the project property. + * + *

+ * For example, to add a new item, do as follows: + *

+         *    getProject().add(newItem);
+         * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link Project } + * + * + */ + public List getProject() { + if (project == null) { + project = new ArrayList(); + } + return this.project; + } + + } + + + /** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+     * <complexType>
+     *   <complexContent>
+     *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *       <sequence maxOccurs="unbounded" minOccurs="0">
+     *         <element ref="{http://javaops.ru}User"/>
+     *       </sequence>
+     *     </restriction>
+     *   </complexContent>
+     * </complexType>
+     * 
+ * + * + */ + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "user" + }) + public static class Users { + + @XmlElement(name = "User", namespace = "http://javaops.ru") + protected List user; + + /** + * Gets the value of the user property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the user property. + * + *

+ * For example, to add a new item, do as follows: + *

+         *    getUser().add(newItem);
+         * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link User } + * + * + */ + public List getUser() { + if (user == null) { + user = new ArrayList(); + } + return this.user; + } + + } + +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/xml/schema/Project.java b/web/export/src/main/java/ru/javaops/masterjava/xml/schema/Project.java new file mode 100644 index 000000000..7e9cd961a --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/xml/schema/Project.java @@ -0,0 +1,223 @@ + +package ru.javaops.masterjava.xml.schema; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlID; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlSchemaType; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + + +/** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType>
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element name="description" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ *         <sequence maxOccurs="unbounded">
+ *           <element name="Group">
+ *             <complexType>
+ *               <complexContent>
+ *                 <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                   <attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}ID" />
+ *                   <attribute name="type" use="required" type="{http://javaops.ru}groupType" />
+ *                 </restriction>
+ *               </complexContent>
+ *             </complexType>
+ *           </element>
+ *         </sequence>
+ *       </sequence>
+ *       <attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "description", + "group" +}) +@XmlRootElement(name = "Project", namespace = "http://javaops.ru") +public class Project { + + @XmlElement(namespace = "http://javaops.ru", required = true) + protected String description; + @XmlElement(name = "Group", namespace = "http://javaops.ru", required = true) + protected List group; + @XmlAttribute(name = "name", required = true) + protected String name; + + /** + * Gets the value of the description property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getDescription() { + return description; + } + + /** + * Sets the value of the description property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setDescription(String value) { + this.description = value; + } + + /** + * Gets the value of the group property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the group property. + * + *

+ * For example, to add a new item, do as follows: + *

+     *    getGroup().add(newItem);
+     * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link Project.Group } + * + * + */ + public List getGroup() { + if (group == null) { + group = new ArrayList(); + } + return this.group; + } + + /** + * Gets the value of the name property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getName() { + return name; + } + + /** + * Sets the value of the name property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setName(String value) { + this.name = value; + } + + + /** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+     * <complexType>
+     *   <complexContent>
+     *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *       <attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}ID" />
+     *       <attribute name="type" use="required" type="{http://javaops.ru}groupType" />
+     *     </restriction>
+     *   </complexContent>
+     * </complexType>
+     * 
+ * + * + */ + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "") + public static class Group { + + @XmlAttribute(name = "name", required = true) + @XmlJavaTypeAdapter(CollapsedStringAdapter.class) + @XmlID + @XmlSchemaType(name = "ID") + protected String name; + @XmlAttribute(name = "type", required = true) + protected GroupType type; + + /** + * Gets the value of the name property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getName() { + return name; + } + + /** + * Sets the value of the name property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setName(String value) { + this.name = value; + } + + /** + * Gets the value of the type property. + * + * @return + * possible object is + * {@link GroupType } + * + */ + public GroupType getType() { + return type; + } + + /** + * Sets the value of the type property. + * + * @param value + * allowed object is + * {@link GroupType } + * + */ + public void setType(GroupType value) { + this.type = value; + } + + } + +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/xml/schema/User.java b/web/export/src/main/java/ru/javaops/masterjava/xml/schema/User.java new file mode 100644 index 000000000..9f19c0ad7 --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/xml/schema/User.java @@ -0,0 +1,182 @@ + +package ru.javaops.masterjava.xml.schema; + +import javax.xml.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + + +/** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType>
+ *   <simpleContent>
+ *     <extension base="<http://www.w3.org/2001/XMLSchema>string">
+ *       <attribute name="email" type="{http://javaops.ru}emailAddressType" />
+ *       <attribute name="flag" use="required" type="{http://javaops.ru}flagType" />
+ *       <attribute name="city" use="required" type="{http://www.w3.org/2001/XMLSchema}IDREF" />
+ *       <attribute name="groupRefs" type="{http://www.w3.org/2001/XMLSchema}IDREFS" />
+ *     </extension>
+ *   </simpleContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "value" +}) +@XmlRootElement(name = "User", namespace = "http://javaops.ru") +public class User { + + @XmlValue + protected String value; + @XmlAttribute(name = "email") + protected String email; + @XmlAttribute(name = "flag", required = true) + protected FlagType flag; + @XmlAttribute(name = "city", required = true) + @XmlIDREF + @XmlSchemaType(name = "IDREF") + protected Object city; + @XmlAttribute(name = "groupRefs") + @XmlIDREF + @XmlSchemaType(name = "IDREFS") + protected List groupRefs; + + /** + * Gets the value of the value property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getValue() { + return value; + } + + /** + * Sets the value of the value property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setValue(String value) { + this.value = value; + } + + /** + * Gets the value of the email property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getEmail() { + return email; + } + + /** + * Sets the value of the email property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setEmail(String value) { + this.email = value; + } + + /** + * Gets the value of the flag property. + * + * @return + * possible object is + * {@link FlagType } + * + */ + public FlagType getFlag() { + return flag; + } + + /** + * Sets the value of the flag property. + * + * @param value + * allowed object is + * {@link FlagType } + * + */ + public void setFlag(FlagType value) { + this.flag = value; + } + + /** + * Gets the value of the city property. + * + * @return + * possible object is + * {@link Object } + * + */ + public Object getCity() { + return city; + } + + /** + * Sets the value of the city property. + * + * @param value + * allowed object is + * {@link Object } + * + */ + public void setCity(Object value) { + this.city = value; + } + + /** + * Gets the value of the groupRefs property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the groupRefs property. + * + *

+ * For example, to add a new item, do as follows: + *

+     *    getGroupRefs().add(newItem);
+     * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link Object } + * + * + */ + public List getGroupRefs() { + if (groupRefs == null) { + groupRefs = new ArrayList(); + } + return this.groupRefs; + } + + @Override + public String toString() { + return value + '(' + email + ')'; + } + + +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java b/web/export/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java new file mode 100644 index 000000000..7401011e9 --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java @@ -0,0 +1,43 @@ +package ru.javaops.masterjava.xml.util; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.PropertyException; +import javax.xml.validation.Schema; +import java.io.StringWriter; +import java.io.Writer; + +/** + * @author GKislin + * Date: 18.11.2008 + */ +public class JaxbMarshaller { + private Marshaller marshaller; + + public JaxbMarshaller(JAXBContext ctx) throws JAXBException { + marshaller = ctx.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); + marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true); + } + + public void setProperty(String prop, Object value) throws PropertyException { + marshaller.setProperty(prop, value); + } + + public synchronized void setSchema(Schema schema) { + marshaller.setSchema(schema); + } + + public String marshal(Object instance) throws JAXBException { + StringWriter sw = new StringWriter(); + marshal(instance, sw); + return sw.toString(); + } + + public synchronized void marshal(Object instance, Writer writer) throws JAXBException { + marshaller.marshal(instance, writer); + } + +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java b/web/export/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java new file mode 100644 index 000000000..5f8289b4b --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java @@ -0,0 +1,89 @@ +package ru.javaops.masterjava.xml.util; + +import org.xml.sax.SAXException; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.PropertyException; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import java.io.*; + + +/** + * Marshalling/Unmarshalling JAXB helper + * XML Facade + * @author Grigory Kislin + */ +public class JaxbParser { + + protected JaxbMarshaller jaxbMarshaller; + protected JaxbUnmarshaller jaxbUnmarshaller; + protected Schema schema; + + public JaxbParser(Class... classesToBeBound) { + try { + init(JAXBContext.newInstance(classesToBeBound)); + } catch (JAXBException e) { + throw new IllegalArgumentException(e); + } + } + + // http://stackoverflow.com/questions/30643802/what-is-jaxbcontext-newinstancestring-contextpath + public JaxbParser(String context) { + try { + init(JAXBContext.newInstance(context)); + } catch (JAXBException e) { + throw new IllegalArgumentException(e); + } + } + + private void init(JAXBContext ctx) throws JAXBException { + jaxbMarshaller = new JaxbMarshaller(ctx); + jaxbUnmarshaller = new JaxbUnmarshaller(ctx); + } + + // Unmarshaller + public T unmarshal(InputStream is) throws JAXBException { + return (T) jaxbUnmarshaller.unmarshal(is); + } + + public T unmarshal(Reader reader) throws JAXBException { + return (T) jaxbUnmarshaller.unmarshal(reader); + } + + public T unmarshal(String str) throws JAXBException { + return (T) jaxbUnmarshaller.unmarshal(str); + } + + // Marshaller + public void setMarshallerProperty(String prop, Object value) { + try { + jaxbMarshaller.setProperty(prop, value); + } catch (PropertyException e) { + throw new IllegalArgumentException(e); + } + } + + public String marshal(Object instance) throws JAXBException { + return jaxbMarshaller.marshal(instance); + } + + public void marshal(Object instance, Writer writer) throws JAXBException { + jaxbMarshaller.marshal(instance, writer); + } + + public void setSchema(Schema schema) { + this.schema = schema; + jaxbUnmarshaller.setSchema(schema); + jaxbMarshaller.setSchema(schema); + } + + public void validate(String str) throws IOException, SAXException { + validate(new StringReader(str)); + } + + public void validate(Reader reader) throws IOException, SAXException { + schema.newValidator().validate(new StreamSource(reader)); + } +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java b/web/export/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java new file mode 100644 index 000000000..afc34a6e4 --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java @@ -0,0 +1,37 @@ +package ru.javaops.masterjava.xml.util; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.validation.Schema; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; + +/** + * @author GKislin + * Date: 18.11.2008 + */ +public class JaxbUnmarshaller { + private Unmarshaller unmarshaller; + + public JaxbUnmarshaller(JAXBContext ctx) throws JAXBException { + unmarshaller = ctx.createUnmarshaller(); + } + + public synchronized void setSchema(Schema schema) { + unmarshaller.setSchema(schema); + } + + public synchronized Object unmarshal(InputStream is) throws JAXBException { + return unmarshaller.unmarshal(is); + } + + public synchronized Object unmarshal(Reader reader) throws JAXBException { + return unmarshaller.unmarshal(reader); + } + + public Object unmarshal(String str) throws JAXBException { + return unmarshal(new StringReader(str)); + } +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/xml/util/Schemas.java b/web/export/src/main/java/ru/javaops/masterjava/xml/util/Schemas.java new file mode 100644 index 000000000..711e9eabe --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/xml/util/Schemas.java @@ -0,0 +1,51 @@ +package ru.javaops.masterjava.xml.util; + +import com.google.common.io.Resources; +import org.xml.sax.SAXException; + +import javax.xml.XMLConstants; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import java.io.File; +import java.io.StringReader; +import java.net.URL; + + +/** + * @author Grigory Kislin + */ +public class Schemas { + + // SchemaFactory is not thread-safe + private static final SchemaFactory SCHEMA_FACTORY = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + + public static synchronized Schema ofString(String xsd) { + try { + return SCHEMA_FACTORY.newSchema(new StreamSource(new StringReader(xsd))); + } catch (SAXException e) { + throw new IllegalArgumentException(e); + } + } + + public static synchronized Schema ofClasspath(String resource) { + // http://digitalsanctum.com/2012/11/30/how-to-read-file-contents-in-java-the-easy-way-with-guava/ + return ofURL(Resources.getResource(resource)); + } + + public static synchronized Schema ofURL(URL url) { + try { + return SCHEMA_FACTORY.newSchema(url); + } catch (SAXException e) { + throw new IllegalArgumentException(e); + } + } + + public static synchronized Schema ofFile(File file) { + try { + return SCHEMA_FACTORY.newSchema(file); + } catch (SAXException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java b/web/export/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java new file mode 100644 index 000000000..b93e0bc02 --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java @@ -0,0 +1,83 @@ +package ru.javaops.masterjava.xml.util; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.events.XMLEvent; +import java.io.InputStream; + +/** + * gkislin + * 23.09.2016 + */ +public class StaxStreamProcessor implements AutoCloseable { + private static final XMLInputFactory FACTORY = XMLInputFactory.newInstance(); + + private final XMLStreamReader reader; + + public StaxStreamProcessor(InputStream is) throws XMLStreamException { + reader = FACTORY.createXMLStreamReader(is); + } + + public boolean startElement(String element, String parent) throws XMLStreamException { + while (reader.hasNext()) { + int event = reader.next(); + if (parent != null && isElementEnd(event, parent)) { + return false; + } + if (isElementStart(event, element)) { + return true; + } + } + return false; + } + + private boolean isElementStart(int event, String el) { + return event == XMLEvent.START_ELEMENT && el.equals(reader.getLocalName()); + } + + private boolean isElementEnd(int event, String el) { + return event == XMLEvent.END_ELEMENT && el.equals(reader.getLocalName()); + } + + public XMLStreamReader getReader() { + return reader; + } + + public String getAttribute(String name) throws XMLStreamException { + return reader.getAttributeValue(null, name); + } + + public boolean doUntil(int stopEvent, String value) throws XMLStreamException { + while (reader.hasNext()) { + int event = reader.next(); + if (event == stopEvent && value.equals(getValue(event))) { + return true; + } + } + return false; + } + + public String getValue(int event) throws XMLStreamException { + return (event == XMLEvent.CHARACTERS) ? reader.getText() : reader.getLocalName(); + } + + public String getElementValue(String element) throws XMLStreamException { + return doUntil(XMLEvent.START_ELEMENT, element) ? reader.getElementText() : null; + } + + public String getText() throws XMLStreamException { + return reader.getElementText(); + } + + @Override + public void close() { + if (reader != null) { + try { + reader.close(); + } catch (XMLStreamException e) { + // empty + } + } + } +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/xml/util/XPathProcessor.java b/web/export/src/main/java/ru/javaops/masterjava/xml/util/XPathProcessor.java new file mode 100644 index 000000000..d6456a5de --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/xml/util/XPathProcessor.java @@ -0,0 +1,62 @@ +package ru.javaops.masterjava.xml.util; + +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import java.io.IOException; +import java.io.InputStream; + +/** + * gkislin + * 23.09.2016 + */ +public class XPathProcessor { + private static final DocumentBuilderFactory DOCUMENT_FACTORY = DocumentBuilderFactory.newInstance(); + private static final DocumentBuilder DOCUMENT_BUILDER; + + private static final XPathFactory XPATH_FACTORY = XPathFactory.newInstance(); + private static final XPath XPATH = XPATH_FACTORY.newXPath(); + + static { + DOCUMENT_FACTORY.setNamespaceAware(true); + try { + DOCUMENT_BUILDER = DOCUMENT_FACTORY.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new IllegalStateException(e); + } + } + + private final Document doc; + + public XPathProcessor(InputStream is) { + try { + doc = DOCUMENT_BUILDER.parse(is); + } catch (SAXException | IOException e) { + throw new IllegalArgumentException(e); + } + } + + public static synchronized XPathExpression getExpression(String exp) { + try { + return XPATH.compile(exp); + } catch (XPathExpressionException e) { + throw new IllegalArgumentException(e); + } + } + + public T evaluate(XPathExpression expression, QName type) { + try { + return (T) expression.evaluate(doc, type); + } catch (XPathExpressionException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java b/web/export/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java new file mode 100644 index 000000000..4a395ba3e --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java @@ -0,0 +1,52 @@ +package ru.javaops.masterjava.xml.util; + +import javax.xml.transform.*; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import java.io.*; +import java.nio.charset.StandardCharsets; + +/** + * User: GKislin + * Date: 18.05.2009 + */ + +public class XsltProcessor { + private static TransformerFactory FACTORY = TransformerFactory.newInstance(); + private final Transformer xformer; + + public XsltProcessor(InputStream xslInputStream) { + this(new BufferedReader(new InputStreamReader(xslInputStream, StandardCharsets.UTF_8))); + } + + public XsltProcessor(Reader xslReader) { + try { + Templates template = FACTORY.newTemplates(new StreamSource(xslReader)); + xformer = template.newTransformer(); + } catch (TransformerConfigurationException e) { + throw new IllegalStateException("XSLT transformer creation failed: " + e.toString(), e); + } + } + + public String transform(InputStream xmlInputStream) throws TransformerException { + StringWriter out = new StringWriter(); + transform(xmlInputStream, out); + return out.getBuffer().toString(); + } + + public void transform(InputStream xmlInputStream, Writer result) throws TransformerException { + transform(new BufferedReader(new InputStreamReader(xmlInputStream, StandardCharsets.UTF_8)), result); + } + + public void transform(Reader sourceReader, Writer result) throws TransformerException { + xformer.transform(new StreamSource(sourceReader), new StreamResult(result)); + } + + public static String getXsltHeader(String xslt) { + return "\n"; + } + + public void setParameter(String name, String value) { + xformer.setParameter(name, value); + } +} diff --git a/web/export/src/main/resources/cities.xsl b/web/export/src/main/resources/cities.xsl new file mode 100644 index 000000000..1c509124b --- /dev/null +++ b/web/export/src/main/resources/cities.xsl @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/web/export/src/main/resources/groups.xsl b/web/export/src/main/resources/groups.xsl new file mode 100644 index 000000000..bdc88113a --- /dev/null +++ b/web/export/src/main/resources/groups.xsl @@ -0,0 +1,38 @@ + + + + + + + + + + <xsl:value-of select="$projectName"/> groups + + + +

+ groups +

+ + + + + + + + + + + +
GroupType
+ + + +
+ + +
+
\ No newline at end of file diff --git a/web/export/src/main/resources/payload.xsd b/web/export/src/main/resources/payload.xsd new file mode 100644 index 000000000..3d545ec54 --- /dev/null +++ b/web/export/src/main/resources/payload.xsd @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/export/src/main/webapp/WEB-INF/templates/exception.html b/web/export/src/main/webapp/WEB-INF/templates/exception.html new file mode 100644 index 000000000..6123126d2 --- /dev/null +++ b/web/export/src/main/webapp/WEB-INF/templates/exception.html @@ -0,0 +1,19 @@ + + + + Export XML + + + +
+
+
+

Application error:

+

exception.message

+
    +
  • +
+
+
+ + \ No newline at end of file diff --git a/web/export/src/main/webapp/WEB-INF/templates/export.html b/web/export/src/main/webapp/WEB-INF/templates/export.html new file mode 100644 index 000000000..704600594 --- /dev/null +++ b/web/export/src/main/webapp/WEB-INF/templates/export.html @@ -0,0 +1,22 @@ + + + + Export XML + + +
+ +

Message

+

Select xml file to upload

+

+ + +

+
+

+

+ +

+
+ + \ No newline at end of file diff --git a/web/export/src/main/webapp/WEB-INF/templates/result.html b/web/export/src/main/webapp/WEB-INF/templates/result.html new file mode 100644 index 000000000..e8c63fbfa --- /dev/null +++ b/web/export/src/main/webapp/WEB-INF/templates/result.html @@ -0,0 +1,14 @@ + + + + Failed users + + +

Export XML

+

Failed users

+
    + +
  • +
+ + \ No newline at end of file diff --git a/web/export/src/test/java/ru/javaops/masterjava/MainXml.java b/web/export/src/test/java/ru/javaops/masterjava/MainXml.java new file mode 100644 index 000000000..3f91cfe3e --- /dev/null +++ b/web/export/src/test/java/ru/javaops/masterjava/MainXml.java @@ -0,0 +1,139 @@ +package ru.javaops.masterjava; + +import com.google.common.base.Splitter; +import com.google.common.io.Resources; +import j2html.tags.ContainerTag; +import one.util.streamex.StreamEx; +import ru.javaops.masterjava.xml.schema.ObjectFactory; +import ru.javaops.masterjava.xml.schema.Payload; +import ru.javaops.masterjava.xml.schema.Project; +import ru.javaops.masterjava.xml.schema.User; +import ru.javaops.masterjava.xml.util.JaxbParser; +import ru.javaops.masterjava.xml.util.Schemas; +import ru.javaops.masterjava.xml.util.StaxStreamProcessor; +import ru.javaops.masterjava.xml.util.XsltProcessor; + +import javax.xml.stream.events.XMLEvent; +import java.io.InputStream; +import java.io.Writer; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; +import java.util.stream.Collectors; + +import static com.google.common.base.Strings.nullToEmpty; +import static j2html.TagCreator.*; + + +/** + * User: gkislin + */ +public class MainXml { + private static final Comparator USER_COMPARATOR = Comparator.comparing(User::getValue).thenComparing(User::getEmail); + + public static void main(String[] args) throws Exception { + if (args.length != 1) { + System.out.println("Required argument: projectName"); + System.exit(1); + } + String projectName = args[0]; + URL payloadUrl = Resources.getResource("payload.xml"); + + Set users = parseByJaxb(projectName, payloadUrl); + users.forEach(System.out::println); + + String html = toHtml(users, projectName); + System.out.println(html); + try (Writer writer = Files.newBufferedWriter(Paths.get("out/users.html"))) { + writer.write(html); + } + + System.out.println(); + users = processByStax(projectName, payloadUrl); + users.forEach(System.out::println); + + html = transform(projectName, payloadUrl); + try (Writer writer = Files.newBufferedWriter(Paths.get("out/groups.html"))) { + writer.write(html); + } + } + + private static Set parseByJaxb(String projectName, URL payloadUrl) throws Exception { + JaxbParser parser = new JaxbParser(ObjectFactory.class); + parser.setSchema(Schemas.ofClasspath("payload.xsd")); + Payload payload; + try (InputStream is = payloadUrl.openStream()) { + payload = parser.unmarshal(is); + } + + Project project = StreamEx.of(payload.getProjects().getProject()) + .filter(p -> p.getName().equals(projectName)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("Invalid project name '" + projectName + '\'')); + + final Set groups = new HashSet<>(project.getGroup()); // identity compare + return StreamEx.of(payload.getUsers().getUser()) + .filter(u -> !Collections.disjoint(groups, u.getGroupRefs())) + .collect(Collectors.toCollection(() -> new TreeSet<>(USER_COMPARATOR))); + } + + private static Set processByStax(String projectName, URL payloadUrl) throws Exception { + + try (InputStream is = payloadUrl.openStream()) { + StaxStreamProcessor processor = new StaxStreamProcessor(is); + final Set groupNames = new HashSet<>(); + + while (processor.startElement("Project", "Projects")) { + if (projectName.equals(processor.getAttribute("name"))) { + while (processor.startElement("Group", "Project")) { + groupNames.add(processor.getAttribute("name")); + } + break; + } + } + + if (groupNames.isEmpty()) { + throw new IllegalArgumentException("Invalid " + projectName + " or no groups"); + } + + // Users loop + Set users = new TreeSet<>(USER_COMPARATOR); + + while (processor.startElement("User", null)) { + String groupRefs = processor.getAttribute("groupRefs"); + if (!Collections.disjoint(groupNames, Splitter.on(' ').splitToList(nullToEmpty(groupRefs)))) { + User user = new User(); + user.setEmail(processor.getAttribute("email")); + user.setValue(processor.getText()); + users.add(user); + } + } + return users; + } + } + + private static String toHtml(Set users, String projectName) { + final ContainerTag table = table().with( + tr().with(th("FullName"), th("email"))) + .attr("border", "1") + .attr("cellpadding", "8") + .attr("cellspacing", "0"); + + users.forEach(u -> table.with(tr().with(td(u.getValue()), td(u.getEmail())))); + + return html().with( + head().with(title(projectName + " users")), + body().with(h1(projectName + " users"), table) + ).render(); + } + + private static String transform(String projectName, URL payloadUrl) throws Exception { + URL xsl = Resources.getResource("groups.xsl"); + try (InputStream xmlStream = payloadUrl.openStream(); InputStream xslStream = xsl.openStream()) { + XsltProcessor processor = new XsltProcessor(xslStream); + processor.setParameter("projectName", projectName); + return processor.transform(xmlStream); + } + } +} diff --git a/web/export/src/test/java/ru/javaops/masterjava/export/CityImporterTest.java b/web/export/src/test/java/ru/javaops/masterjava/export/CityImporterTest.java new file mode 100644 index 000000000..4376358a4 --- /dev/null +++ b/web/export/src/test/java/ru/javaops/masterjava/export/CityImporterTest.java @@ -0,0 +1,28 @@ +package ru.javaops.masterjava.export; + +import com.google.common.io.Resources; +import org.junit.Test; +import ru.javaops.masterjava.persist.DBITestProvider; +import ru.javaops.masterjava.persist.model.City; +import ru.javaops.masterjava.xml.util.StaxStreamProcessor; + +import java.util.Map; + +/** + * gkislin + * 04.09.2017 + */ +public class CityImporterTest { + static { + DBITestProvider.initDBI(); + } + + @Test + public void process() throws Exception { + try (StaxStreamProcessor processor = + new StaxStreamProcessor(Resources.getResource("payload.xml").openStream())) { + Map cityMap = new CityImporter().process(processor); + System.out.println(cityMap); + } + } +} \ No newline at end of file diff --git a/web/export/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java b/web/export/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java new file mode 100644 index 000000000..ab1e365e5 --- /dev/null +++ b/web/export/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java @@ -0,0 +1,44 @@ +package ru.javaops.masterjava.xml.util; + +import com.google.common.io.Resources; +import org.junit.Test; +import ru.javaops.masterjava.xml.schema.CityType; +import ru.javaops.masterjava.xml.schema.ObjectFactory; +import ru.javaops.masterjava.xml.schema.Payload; + +import javax.xml.bind.JAXBElement; +import javax.xml.namespace.QName; + +/** + * gkislin + * 23.09.2016 + */ +public class JaxbParserTest { + private static final JaxbParser JAXB_PARSER = new JaxbParser(ObjectFactory.class); + + static { + JAXB_PARSER.setSchema(Schemas.ofClasspath("payload.xsd")); + } + + @Test + public void testPayload() throws Exception { +// JaxbParserTest.class.getResourceAsStream("/city.xml") + Payload payload = JAXB_PARSER.unmarshal( + Resources.getResource("payload.xml").openStream()); + String strPayload = JAXB_PARSER.marshal(payload); + JAXB_PARSER.validate(strPayload); + System.out.println(strPayload); + } + + @Test + public void testCity() throws Exception { + JAXBElement cityElement = JAXB_PARSER.unmarshal( + Resources.getResource("city.xml").openStream()); + CityType city = cityElement.getValue(); + JAXBElement cityElement2 = + new JAXBElement<>(new QName("http://javaops.ru", "City"), CityType.class, city); + String strCity = JAXB_PARSER.marshal(cityElement2); + JAXB_PARSER.validate(strCity); + System.out.println(strCity); + } +} \ No newline at end of file diff --git a/web/export/src/test/java/ru/javaops/masterjava/xml/util/StaxStreamProcessorTest.java b/web/export/src/test/java/ru/javaops/masterjava/xml/util/StaxStreamProcessorTest.java new file mode 100644 index 000000000..229e2da64 --- /dev/null +++ b/web/export/src/test/java/ru/javaops/masterjava/xml/util/StaxStreamProcessorTest.java @@ -0,0 +1,40 @@ +package ru.javaops.masterjava.xml.util; + +import com.google.common.io.Resources; +import org.junit.Test; + +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.events.XMLEvent; + +/** + * gkislin + * 23.09.2016 + */ +public class StaxStreamProcessorTest { + @Test + public void readCities() throws Exception { + try (StaxStreamProcessor processor = + new StaxStreamProcessor(Resources.getResource("payload.xml").openStream())) { + XMLStreamReader reader = processor.getReader(); + while (reader.hasNext()) { + int event = reader.next(); + if (event == XMLEvent.START_ELEMENT) { + if ("City".equals(reader.getLocalName())) { + System.out.println(reader.getElementText()); + } + } + } + } + } + + @Test + public void readCities2() throws Exception { + try (StaxStreamProcessor processor = + new StaxStreamProcessor(Resources.getResource("payload.xml").openStream())) { + String city; + while ((city = processor.getElementValue("City")) != null) { + System.out.println(city); + } + } + } +} \ No newline at end of file diff --git a/web/export/src/test/java/ru/javaops/masterjava/xml/util/XPathProcessorTest.java b/web/export/src/test/java/ru/javaops/masterjava/xml/util/XPathProcessorTest.java new file mode 100644 index 000000000..18b6efdc2 --- /dev/null +++ b/web/export/src/test/java/ru/javaops/masterjava/xml/util/XPathProcessorTest.java @@ -0,0 +1,30 @@ +package ru.javaops.masterjava.xml.util; + +import com.google.common.io.Resources; +import org.junit.Test; +import org.w3c.dom.NodeList; + +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import java.io.InputStream; +import java.util.stream.IntStream; + +/** + * gkislin + * 23.09.2016 + */ +public class XPathProcessorTest { + @Test + public void getCities() throws Exception { + try (InputStream is = + Resources.getResource("payload.xml").openStream()) { + XPathProcessor processor = new XPathProcessor(is); + XPathExpression expression = + XPathProcessor.getExpression("/*[name()='Payload']/*[name()='Cities']/*[name()='City']/text()"); + NodeList nodes = processor.evaluate(expression, XPathConstants.NODESET); + IntStream.range(0, nodes.getLength()).forEach( + i -> System.out.println(nodes.item(i).getNodeValue()) + ); + } + } +} \ No newline at end of file diff --git a/web/export/src/test/java/ru/javaops/masterjava/xml/util/XsltProcessorTest.java b/web/export/src/test/java/ru/javaops/masterjava/xml/util/XsltProcessorTest.java new file mode 100644 index 000000000..403a14c87 --- /dev/null +++ b/web/export/src/test/java/ru/javaops/masterjava/xml/util/XsltProcessorTest.java @@ -0,0 +1,22 @@ +package ru.javaops.masterjava.xml.util; + +import com.google.common.io.Resources; +import org.junit.Test; + +import java.io.InputStream; + +/** + * gkislin + * 23.09.2016 + */ +public class XsltProcessorTest { + @Test + public void transform() throws Exception { + try (InputStream xslInputStream = Resources.getResource("cities.xsl").openStream(); + InputStream xmlInputStream = Resources.getResource("payload.xml").openStream()) { + + XsltProcessor processor = new XsltProcessor(xslInputStream); + System.out.println(processor.transform(xmlInputStream)); + } + } +} diff --git a/web/export/src/test/resources/city.xml b/web/export/src/test/resources/city.xml new file mode 100644 index 000000000..8b0abcf8a --- /dev/null +++ b/web/export/src/test/resources/city.xml @@ -0,0 +1,4 @@ +Санкт-Петербург + \ No newline at end of file diff --git a/web/export/src/test/resources/payload.xml b/web/export/src/test/resources/payload.xml new file mode 100644 index 000000000..fadf8c64a --- /dev/null +++ b/web/export/src/test/resources/payload.xml @@ -0,0 +1,31 @@ + + + + + Topjava + + + + + + Masterjava + + + + + Санкт-Петербург + Москва + Киев + Минск + + + Full Name + Admin + Deleted + User1 + User2 + User3 + + \ No newline at end of file diff --git a/web/pom.xml b/web/pom.xml new file mode 100644 index 000000000..bf9ef21c3 --- /dev/null +++ b/web/pom.xml @@ -0,0 +1,16 @@ + + 4.0.0 + + ru.javaops + web + pom + 1.0-SNAPSHOT + MasterJava Web + + + common-web + export + webapp + + diff --git a/web/webapp/pom.xml b/web/webapp/pom.xml new file mode 100644 index 000000000..de3a56fc2 --- /dev/null +++ b/web/webapp/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + + ru.javaops + parent-web + ../../parent-web/pom.xml + 1.0-SNAPSHOT + + + webapp + 1.0-SNAPSHOT + war + WebApp + + + webapp + + + + + ${project.groupId} + persist + ${project.version} + + + \ No newline at end of file diff --git a/web/webapp/src/main/java/ru/javaops/masterjava/webapp/UsersServlet.java b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/UsersServlet.java new file mode 100644 index 000000000..5b587128f --- /dev/null +++ b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/UsersServlet.java @@ -0,0 +1,27 @@ +package ru.javaops.masterjava.webapp; + +import com.google.common.collect.ImmutableMap; +import org.thymeleaf.context.WebContext; +import ru.javaops.masterjava.persist.DBIProvider; +import ru.javaops.masterjava.persist.dao.UserDao; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static ru.javaops.masterjava.common.web.ThymeleafListener.engine; + +@WebServlet("") +public class UsersServlet extends HttpServlet { + private UserDao userDao = DBIProvider.getDao(UserDao.class); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + final WebContext webContext = new WebContext(req, resp, req.getServletContext(), req.getLocale(), + ImmutableMap.of("users", userDao.getWithLimit(20))); + engine.process("users", webContext, resp.getWriter()); + } +} diff --git a/web/webapp/src/main/webapp/WEB-INF/templates/users.html b/web/webapp/src/main/webapp/WEB-INF/templates/users.html new file mode 100644 index 000000000..3991c8d07 --- /dev/null +++ b/web/webapp/src/main/webapp/WEB-INF/templates/users.html @@ -0,0 +1,27 @@ + + + + Users + + + + + + + + + + + + + + + + + + + + +
Full NameEmailFlag
+ + \ No newline at end of file