diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ad02a2599 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.idea +out +target +*.iml +log \ No newline at end of file diff --git a/README.md b/README.md index ed139bed8..2b4ce2671 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,162 @@ -#### Написание с нуля полнофункционального многомодульного 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 - -## Асинхронность. -- @OneWay vs Java Execution framework -- Добавление в клиенте асинхронных вызовов. -- Асинхронные сервлеты 3.x в Tomcat +# Многомодульный maven. Многопоточность. XML. Веб сервисы. Удаленное взаимодействие +## Регистрация +## [Программа проекта](#Программа-проекта) + +### _Разработка полнофункционального многомодульного Maven проекта_ +- веб приложение (Tomcat, Thymleaf, jQuery) +- модуль экспорта из XML (JAXB, StAX) +- многопоточный почтовый сервис (JavaMail, java.util.concurrent.*) +- связь модулей через веб-сервисы (SOAP, JAX-WS) и по REST (JAX-RS) +- сохранение данных в RMDBS (postgresql) +- библиотеки Guava, StreamEx, Lombook, Typesafe config, jDBI + +### Требование к участникам +Опыт программирования на Java. Базовые знания Maven. + +### Необходимое ПО +- JDK8 +- Git +- IntelliJ IDEA + +> Выбирать Ultimate, 30 days trial (работа с JavaScript, Tomcat, JSP). Персональный ключ к Ultimate (на 6 месяцев) выдается на первом занятии. + +# Первое занятие: многопоточность. + +## ![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. Атомарные переменные и конкурентные таблицы + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. Реализация многопоточной отправки писем. Execution Framework +> правка к видео: `22: completionService.submit(..)` + +*Все изменения в проекте будут делаться на основе патчей: скачайте [1_1_MailService.patch](https://drive.google.com/open?id=0B9Ye2auQ_NsFTE5ZV3pzWElxTWM), положите его в проект, правой мышкой на нем сделайте Apply Patch ...* -## Динамическое конфигурирование. JMX -- Maven Groovy cкрптинг. groovy-maven-plugin -- Настройка Tomcat на удаленное администрирование по JMX +---------------------------- -## Отправка email в многопоточном приложении -- Initialization on demand holder / Double-checked locking -- java.util.concurrent.*: Executors , Synchronizers, Concurrent Collections, Lock +### Ресурсы (основы) +- Intuit, Потоки выполнения. Синхронизация +- Алексей Владыкин, Основы многопоточность в Java +- Виталий Чибриков, Java. Многопоточность +- Computer Science Center, курс Параллельное программирование +- Юрий Ткач, курс Advanced Java - Concurrency +- Головач, курс Java Multithreading -## Проблема MemoryLeak. Поиск утечки памяти. +--- +## ![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 многопоточная реализация была быстрее однопоточной + +----- + +# Программа проекта +> **возможны изменения, окончательная программа будет перед стартом курса** + +## Занятие 2 +- Разбор ДЗ (многопоточная реализация умножения матриц) +- Java Microbenchmark JMH (от Алексея Шипилева) +- Обзор Guava +- Формат XML. Создание схемы XSD. +- Работа с XML в Java + - JAXB, JAXP + - StAX + - XPath + - XSLT + +## Занятие 3 +- Разбор ДЗ (работа с XML) +- Обзор StreamEx (от Тагира Валеева) +- Монады. flatMap +- SOA и Микросервисы +- Многомодульный Maven проект + +## Занятие 4 +- Разбор ДЗ (реализация структуры проекта, загрузка и разбор xml) +- Thymleaf +- Maven. Поиск и разрешение конфликтов зависимостей +- Логирование +- Выбор lightweight JDBC helper library. JDBI +- Tomcat Class Loader. Memory Leeks + +## Занятие 5 +- Разбор ДЗ (реализуем модули persist, export и web) +- Конфигурирование приложения (Typesafe config) +- Lombook + +## Занятие 6 +- Разбор ДЗ (доработка модели и модуля export) +- Миграция DB +- Веб-сервисы (REST/SOAP) + - Java реализации SOAP + - Имплементируем Mail Service + +## Занятие 7 +- Разбор ДЗ (реализация MailSender, сохранение результатов отправки) +- Стили WSDL. Кастомизация WSDL +- Публикация кастомизированного WSDL. Автогенерация. +- Деплой в Tomcat +- Создание клиента почтового сервиса + +## Занятие 8 +- Разбор ДЗ (отправка почты через Executor из модуля web) +- Доступ к переменным maven в приложении +- SOAP Exception. Выделение общей части схемы +- Передача двоичных данных в веб-сервисах. MTOM + +## Занятие 9 +- Разбор ДЗ (реализация загрузки и отправки вложений по почте) +- JAX-WS Message Context +- JAX-WS Handlers (логирование SOAP) + +## Занятие 10 (предварительно) +- Разбор ДЗ (реализация авторизации и статистики) +- JavaEE + - JAX-RS. Интеграция с Jersey + - EJB + - JMS + +## Занятие 11 (предварительно) +- Асинхронные сервлеты 3.x в Tomcat +- Maven Groovy cкрптинг (groovy-maven-plugin) +- AKKA +- Redis 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/ExceptionType.java b/common/src/main/java/ru/javaops/masterjava/ExceptionType.java new file mode 100644 index 000000000..8489c2f24 --- /dev/null +++ b/common/src/main/java/ru/javaops/masterjava/ExceptionType.java @@ -0,0 +1,37 @@ +package ru.javaops.masterjava; + +import javax.xml.bind.annotation.XmlType; + +@XmlType(namespace = "http://common.javaops.ru/") +public enum ExceptionType { + SYSTEM("Системная ошибка"), + DATA_BASE("Ошибка базы данных"), + STATE("Неверное состояние приложения"), + AUTHORIZATION("Ошибка авторизации"), + CONFIGURATION("Ошибка конфигурирования"), + ILLEGAL_ARGUMENT("Неверный аргумент"), + BPM("Ошибка бизнес-процесса"), + FILE("Ошибка при работе с файловой системой"), + REPORTS("Ошибка в отчете"), + EMAIL("Ошибка при отправке почты"), + TEMPLATE("Ошибка в шаблонах"), + ONE_C("Ошибка в системе 1C"), + ATTACH("Ошибка вложенного файла"), + LDAP("Ошибка соединения с LDAP"), + NETWORK("Сетевая Ошибка"); + + final private String descr; + + ExceptionType(String title) { + this.descr = title; + } + + public String getDescr() { + return descr; + } + + @Override + public String toString() { + return name() + " (" + descr + ')'; + } +} 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..15c5feb8a --- /dev/null +++ b/common/src/main/java/ru/javaops/masterjava/config/Configs.java @@ -0,0 +1,29 @@ +package ru.javaops.masterjava.config; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; + +import java.io.File; + +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); + } + + public static Config getAppConfig(String path) { + return ConfigFactory.parseFile(getConfigFile(path)).resolve(); + } + + public static File getConfigFile(String path) { + return new File(AppConfig.APP_CONFIG.getString("configDir"), path); + } + + private static class AppConfig { + private static final Config APP_CONFIG = getConfig("app.conf", "app"); + } +} diff --git a/common/src/main/java/ru/javaops/masterjava/util/Exceptions.java b/common/src/main/java/ru/javaops/masterjava/util/Exceptions.java new file mode 100644 index 000000000..d9a225341 --- /dev/null +++ b/common/src/main/java/ru/javaops/masterjava/util/Exceptions.java @@ -0,0 +1,27 @@ +package ru.javaops.masterjava.util; + +import lombok.experimental.UtilityClass; + +/** + * @see full Errors at Durian project + */ +@UtilityClass +public class Exceptions { + public static java.lang.Runnable wrap(Functions.Specific.Runnable runnableWitEx) { + return () -> { + try { + runnableWitEx.run(); + } catch (Exception e) { + throw asRuntime(e); + } + }; + } + + public static RuntimeException asRuntime(Throwable e) { + if (e instanceof RuntimeException) { + return (RuntimeException) e; + } else { + return new RuntimeException(e); + } + } +} diff --git a/common/src/main/java/ru/javaops/masterjava/util/Functions.java b/common/src/main/java/ru/javaops/masterjava/util/Functions.java new file mode 100644 index 000000000..26f54643b --- /dev/null +++ b/common/src/main/java/ru/javaops/masterjava/util/Functions.java @@ -0,0 +1,83 @@ +package ru.javaops.masterjava.util; + +/** + * @see Throwing at Durian project + */ +public interface Functions { + /** + * Variations on the standard functional interfaces which throw a specific subclass of Exception. + */ + interface Specific { + @FunctionalInterface + interface Runnable { + void run() throws E; + } + + @FunctionalInterface + interface Supplier { + T get() throws E; + } + + @FunctionalInterface + interface Consumer { + void accept(T t) throws E; + } + + @FunctionalInterface + interface Function { + R apply(T t) throws E; + } + + @FunctionalInterface + interface Predicate { + boolean test(T t) throws E; + } + + @FunctionalInterface + interface BiConsumer { + void accept(T t, U u) throws E; + } + + @FunctionalInterface + interface BiFunction { + R apply(T t, U u) throws E; + } + + @FunctionalInterface + interface BiPredicate { + boolean accept(T t, U u) throws E; + } + } + + @FunctionalInterface + interface RunnableEx extends Specific.Runnable { + } + + @FunctionalInterface + interface SupplierEx extends Specific.Supplier { + } + + @FunctionalInterface + interface ConsumerEx extends Specific.Consumer { + } + + @FunctionalInterface + interface FunctionEx extends Specific.Function { + } + + @FunctionalInterface + interface PredicateEx extends Specific.Predicate { + } + + @FunctionalInterface + interface BiConsumerEx extends Specific.BiConsumer { + } + + @FunctionalInterface + interface BiFunctionEx extends Specific.BiFunction { + } + + @FunctionalInterface + interface BiPredicateEx extends Specific.BiPredicate { + } +} \ No newline at end of file diff --git a/common/src/main/resources/defaults.conf b/common/src/main/resources/defaults.conf new file mode 100644 index 000000000..0bc35f041 --- /dev/null +++ b/common/src/main/resources/defaults.conf @@ -0,0 +1,4 @@ +client.debugLevel = INFO +server.debugLevel = INFO +user = null +password = null \ No newline at end of file diff --git a/common/src/main/resources/hosts.conf b/common/src/main/resources/hosts.conf new file mode 100644 index 000000000..eb4c065fd --- /dev/null +++ b/common/src/main/resources/hosts.conf @@ -0,0 +1,6 @@ +hosts { + mail { + endpoint = "http://localhost:8080" + } +} +include file("/home/konst/work/masterjava/config/hosts.conf") diff --git a/config_templates/akka.conf b/config_templates/akka.conf new file mode 100644 index 000000000..9b027f85e --- /dev/null +++ b/config_templates/akka.conf @@ -0,0 +1,15 @@ +webapp { + include required(classpath("akka-common")) + + akka { + remote.netty.tcp.port = 2554 + } +} + +mail-service { + include required(classpath("akka-common")) + + akka { + remote.netty.tcp.port = 2553 + } +} \ No newline at end of file diff --git a/config_templates/app.conf b/config_templates/app.conf new file mode 100644 index 000000000..d2aee4c5b --- /dev/null +++ b/config_templates/app.conf @@ -0,0 +1,6 @@ +app { + groupId = ${project.groupId} + projectName = ${project.name} + version = ${project.version} + configDir = "${masterjava.config}" +} \ No newline at end of file diff --git a/config_templates/context.xml b/config_templates/context.xml new file mode 100644 index 000000000..c8d390dd0 --- /dev/null +++ b/config_templates/context.xml @@ -0,0 +1,63 @@ + + + + + + + + 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..2992c5ef4 --- /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/mail.conf b/config_templates/mail.conf new file mode 100644 index 000000000..50256be06 --- /dev/null +++ b/config_templates/mail.conf @@ -0,0 +1,10 @@ +mail { + host: smtp.yandex.ru + port: 465 + username: "user@yandex.ru" + password: password + useSSL: true + useTLS: false + debug: true + fromName: MasterJava +} diff --git a/config_templates/persist.conf b/config_templates/persist.conf new file mode 100644 index 000000000..308197095 --- /dev/null +++ b/config_templates/persist.conf @@ -0,0 +1,4 @@ +db { + user = user + password = password +} diff --git a/config_templates/postgres.yml b/config_templates/postgres.yml new file mode 100644 index 000000000..36e28b2e6 --- /dev/null +++ b/config_templates/postgres.yml @@ -0,0 +1,13 @@ +version: '2' +services: + masterjava-postgresql: + image: postgres:9.6 + ports: + - "5432:5432" + environment: + - POSTGRES_USER=user + - POSTGRES_PASSWORD=password + - POSTGRES_DB=masterjava + +# docker-compose -f postgres.yml up -d +# docker-compose -f postgres.yml down \ No newline at end of file diff --git a/config_templates/version.html b/config_templates/version.html new file mode 100644 index 000000000..3cffc3842 --- /dev/null +++ b/config_templates/version.html @@ -0,0 +1,11 @@ + + + + ${project.name} + + +${project.groupId}:${project.name}:${project.version}
+configDir=${masterjava.config}
+Многопоточность. Maven. XML. Веб сервисы. + + diff --git a/config_templates/wsdl/common.xsd b/config_templates/wsdl/common.xsd new file mode 100644 index 000000000..5c47776cd --- /dev/null +++ b/config_templates/wsdl/common.xsd @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config_templates/wsdl/mailService.wsdl b/config_templates/wsdl/mailService.wsdl new file mode 100644 index 000000000..391bffc24 --- /dev/null +++ b/config_templates/wsdl/mailService.wsdl @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 000000000..d3f7da175 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,959 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + masterjava/README.md at master · JavaOPs/masterjava + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content +
+ + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+
+
+ + + + + +
+
+ +
    +
  • +
    + +
    + + + +
    +
    +
    + + Notifications +
    + + + +
    +
    +
    +
    +
  • + +
  • + +
    +
    + + +
    +
    + + +
    + +
  • + +
  • +
    + +
    + +
  • +
+ +

+ + /masterjava + +

+ +
+ +
+ +
+
+ + + Permalink + + + +
+ +
+ + +
+ +
+
+ + Switch branches/tags +
+ +
+
+ +
+
+ +
+
+ + + +
+
+ + +
+ +
Nothing to show
+
+ +
+
+
+ +
+ + Find file + + +
+ +
+ + + +
+ Fetching contributors… +
+ +
+ + Cannot retrieve contributors at this time +
+
+
+
+
+ +
+ Raw + Blame + History +
+ + +
+ +
+ +
+ +
+ 205 lines (174 sloc) + + 13.8 KB +
+
+ + +
+

Многомодульный maven. Многопоточность. XML. Веб сервисы. Удаленное взаимодействие

+

Регистрация

+

Программа проекта

+

Изменения проекта (Release Notes)

+

Разработка полнофункционального многомодульного Maven проекта

+

состоящего из 3-х веб приложений:

+

image

+
    +
  • приложение импорта из XML (JAXB, StAX, XPath, XSLT)
  • +
  • многопоточного почтового веб-сервиса (JavaMail, java.util.concurrent, JAX-WS, MTOM, хендлеры авторизации, логирования и статистики)
  • +
  • веб приложения отправки почты с вложениями +
      +
    • по SOAP (JAX-WS, MTOM)
    • +
    • по JAX-RS (Jersey)
    • +
    • по JMS (ActiveMQ)
    • +
    • через AKKA
    • +
    • через Redis
    • +
    +
  • +
  • сохранение данных в PostgreSQL используя jDBI
  • +
  • миграция базы LiquiBase
  • +
  • использование в проекте Guava, Thymleaf, Lombook, StreamEx, +Typesafe Config, Java Microbenchmark JMH
  • +
+

Требование к участникам

+

Опыт программирования на Java. Базовые знания Maven.

+

Необходимое ПО

+ +

Первое занятие: многопоточность.

+

video 1. Вступление. Многопоточность и параллельность.

+

Concurrent vs Parallel

+

video 2. Структура памяти Java. Ленивая инициализация.

+
+

В видео в LazySingleton ошибка: должно быть как в коде проекта instance == null

+
+

Структура памяти: куча, стек, permanent/metaspace

+ +

Ленивая инициализация

+ +

video 3. Реализация многопоточности в Java

+ +

Tproger: Многопоточное программирование в Java 8

+ +

video 4. Реализация многопоточной отправки писем. Execution Framework

+
+

правка к видео: 22: completionService.submit(..)

+
+

Все изменения в проекте будут делаться на основе патчей

+

Скачайте 1_1_MailService.patch, положите его в проект, правой мышкой на нем сделайте Apply Patch ...

+
+

Ресурсы (основы)

+ +
+

hw Задание первого занятия

+

Вычекать этот проект: +git clone https://github.com/JavaOPs/masterjava.git

+
    +
  • Применить оптимизацию к MatrixUtil.singleThreadMultiply
  • +
  • Реализовать метод MatrixUtil.concurrentMultiply, позволяющий многопоточно перемножать квадратные матрицы N*N.
  • +
  • Количество дочерних потоков ограничено MainMatrix.THREAD_NUMBER.
  • +
  • Добиться того, чтобы на матрице 1000*1000 многопоточная реализация была быстрее однопоточной
  • +
+
+

error Подсказки по 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
  • +
  • Монады. 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
  • +
+

Занятие 11

+
    +
  • Авторизация в контейнере Tomcat
  • +
  • Отправка почты с вложениями +
      +
    • по JAX-RS
    • +
    • по JMS +-Рефакторинг. Эксепшены в Java 8 лямбда
    • +
    +
  • +
  • Concurrent and distributed applications toolkit AKKA
  • +
  • Отсылка почты через AKKA Actors (Typed и Untyped Actors)
  • +
  • Асинхронные сервлеты 3.0
  • +
  • Домашнее задание +
      +
    • Разбор решения с асинхронными сервлетами
    • +
    +
  • +
  • Выбор языка программирования
  • +
+
+
+ +
+ + + + +
+ +
+ +
+
+ +
+ + + + + + +
+ + + You can't perform that action at this time. +
+ + + + + + + + + + +
+ + You signed in with another tab or window. Reload to refresh your session. + You signed out in another tab or window. Reload to refresh your session. +
+ + + + + + diff --git a/doc/lesson02.md b/doc/lesson02.md new file mode 100644 index 000000000..ea83796d8 --- /dev/null +++ b/doc/lesson02.md @@ -0,0 +1,850 @@ + + + + + + + + + + + + + + + + + + + + + + + masterjava/lesson02.md at doc · JavaWebinar/masterjava + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content +
+ + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+
+
+ + + + +
+
+ +
    +
  • +
    + +
    + + + + Watch + + + + +
    +
    +
    + + Notifications +
    + + + +
    +
    +
    +
    +
  • + +
  • +
    +
    + + +
    +
    + + +
    + +
  • + +
  • +
    + +
    + +
  • +
+ +

+ + /masterjava + +

+ +
+ +
+ +
+
+ + + + +Permalink + + + +
+ +
+ + +
+ +
+
+ + Switch branches/tags +
+ +
+
+ +
+
+ +
+
+ + + +
+
+ + +
+ +
Nothing to show
+
+ +
+
+
+ +
+ + Find file + + +
+ +
+ + + +
+ + + 691a100 + + Mar 13, 2017 + + + +
+ + +
+ + +
+ +
+
+
+ +
+ Raw + Blame + History +
+ + +
+ +
+ +
+ +
+ 87 lines (69 sloc) + + 9.27 KB +
+
+ + +
+

Онлайн проекта Masterjava.

+
    +
  • Не стоит стремится прочитать все ссылки урока, их можно впоследствии использовать как справочник. Гораздо важнее сделать Домашнее Задание
  • +
  • Обязательно посмотри правила работы с патчами на проекте (совпадают с проектом Topjava)
  • +
  • Делать Apply Patch лучше по одному, непосредственно перед видео на эту тему, а при просмотре видео сразу отслеживать все изменения кода проекта по изменению в патче
  • +
  • Код проекта обновляется и не всегда совпадает с видео (можно увидеть как развивался проект). Изменения в проекте указываю после соответствующего патча.
  • +
  • 42 IntelliJ IDEA Tips and Tricks
  • +
+

Материалы занятия (скачать все патчи можно через Download папки patch)

+

image

+

hw video 1.1 Вступление. Разбор домашнего задания HW1

+

2_1_HW1_singleThreadMultiplyOpt.patch

+

2_2_HW1_concurrentMultiply.patch

+ +

video 1.2 Дополнение: последняя реализация HW1

+

2_3_HW1_concurrentMultiply3.patch

+
+

Callable / executor.invokeAll в concurrentMultiply3 поменял на Runnable / CountDownLatch

+
+

video 2. Java Microbenchmark JMH (от Алексея Шипилева)

+

2_4_JMH_Benchmark.patch

+

2_5_JMH_main_jar.patch

+
+

Сделал forks=10 для большой точности измерений и убрал лишние измерения

+
+ +

video 3. Формат XML. Создание схемы XSD.

+

2_6_xml_scheme.patch

+ +

Работа с XML в Java

+

video 4. JAXB, JAXP

+

2_7_JAXB.patch

+
+ +
+ +

video 5. StAX

+

2_8_StAX.patch

+ +

video 6. XPath

+

2_9_XPath.patch

+ +

video 7. XSLT

+

2_10_Xslt.patch

+ +

Домашнее задание

+
    +
  • +
      +
    1. Изменить XML схему:
    2. +
    +
      +
    • 1.1 добавить проекты. Имеют название (нарпимер topjava, masterjava) и описание
    • +
    • 1.2 добавить группы. Имеют название и тип (REGISTERING/CURRENT/FINISHED). Группа принадлежат проекту, например проект topjava, группы topjava01,topjava02, ..
    • +
    • 1.3 сделать User.email аттрибутом.
    • +
    • 1.4 реализовать принадлежность участников разным группам (Admin состоит в группах topjava07, topjava08, masterjava01)
    • +
    +
  • +
  • +
      +
    1. Дополнить xml тестовыми данными.
    2. +
    +
  • +
  • +
      +
    1. Реализовать класс MainXml, которые принимает параметром имя проекта в тестовом xml и выводит отсортированный список его участников (использовать JAXB).
    2. +
    +
  • +
+

Optional

+
    +
  • +
      +
    1. Сделать реализацию MainXml через StAX (выводить имя/email)
    2. +
    +
  • +
  • +
      +
    1. Из списка участников сделать html таблицу (имя/email)
    2. +
    +
  • +
  • +
      +
    1. Вывести через XSLT преобразование html таблицу с группами заданного проекта
    2. +
    + +
  • +
+
+
+ +
+ + + + + +
+ +
+ +
+
+ +
+ + + + + + + +
+ + + You can't perform that action at this time. +
+ + + + + + + + + +
+ + You signed in with another tab or window. Reload to refresh your session. + You signed out in another tab or window. Reload to refresh your session. +
+ + + + + + diff --git a/doc/lesson03.md b/doc/lesson03.md new file mode 100644 index 000000000..8df265fbd --- /dev/null +++ b/doc/lesson03.md @@ -0,0 +1,858 @@ + + + + + + + + + + + + + + + + + + + + + + + masterjava/lesson03.md at doc · JavaWebinar/masterjava + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content +
+ + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+
+
+ + + + +
+
+ +
    +
  • +
    + +
    + + + + Watch + + + + +
    +
    +
    + + Notifications +
    + + + +
    +
    +
    +
    +
  • + +
  • +
    +
    + + +
    +
    + + +
    + +
  • + +
  • +
    + +
    + +
  • +
+ +

+ + /masterjava + +

+ +
+ +
+ +
+
+ + + + +Permalink + + + +
+ +
+ + +
+ +
+
+ + Switch branches/tags +
+ +
+
+ +
+
+ +
+
+ + + +
+
+ + +
+ +
Nothing to show
+
+ +
+
+
+ +
+ + Find file + + +
+ +
+ + + +
+ Fetching contributors… +
+ +
+ + Cannot retrieve contributors at this time +
+
+
+
+
+ +
+ Raw + Blame + History +
+ + +
+ +
+ +
+ +
+ 115 lines (100 sloc) + + 11.4 KB +
+
+ + +
+

Онлайн проекта Masterjava.

+

Материалы занятия (скачать все патчи можно через Download папки patch)

+

hw Разбор домашнего задания HW2

+

video 1. Scheme, j2html, JAXB

+

3_1_HW2_schema.patch

+ +

3_2_HW2_JAXB.patch

+
+
    +
  • убрал второй параметр xmlName (всегда payload.xml)
  • +
  • в parseByJaxb сделал закрытие InputStream сразу после обработки
  • +
  • сделал методы статическими
  • +
  • вместо вложенного стрима для групп юзера сделал пересечение коллекций Collections.disjoint
  • +
  • результат JAXB также вывожу в HTML (в ДЗ только в Optional)
  • +
  • в j2html вместо setAttribute сделал attr
  • +
+
+ +

video 2. Optional: StAX

+

3_3_HW2_StAX.patch

+
+
    +
  • зарефакторил в StaxStreamProcessor doUntil() и getAttribute()
  • +
  • константы вставил в код
  • +
  • вместо вложенного цикла для групп юзера сделал пересечение коллекций Collections.disjoint и для маскирования пустых групп Strings.nullToEmpty
  • +
+
+

video 3. Optional: XSLT

+

3_4_HW2_xslt.patch

+ +

Затяние 3

+

video 4. Обзор Guava

+ +

video 5. Монады. flatMap

+ +

video 6. SOA и Микросервисы

+ +

video 7. Многомодульный Maven проект

+

3_5_multimodule.patch

+ +

hw Домашнее задание HW3

+
    +
  • Сделать структуру проекта согласно схеме. В модулях c packaging=pom кода нет, корневое src перенести в другие модули. +
      +
    • Проверьте, что проект собирается! Учитывая, что модулей в проекте предполагается много, измените структуру, чтобы не дублировать maven-war-plugin.
    • +
    +
  • +
+

image

+

Optional

+ +
+
+ +
+ + + + + +
+ +
+ +
+
+ +
+ + + + + + + +
+ + + You can't perform that action at this time. +
+ + + + + + + + + +
+ + You signed in with another tab or window. Reload to refresh your session. + You signed out in another tab or window. Reload to refresh your session. +
+ + + + + + diff --git a/doc/lesson04.md b/doc/lesson04.md new file mode 100644 index 000000000..0b9c98f5a --- /dev/null +++ b/doc/lesson04.md @@ -0,0 +1,851 @@ + + + + + + + + + + + + + + + + + + + + + + + masterjava/lesson04.md at doc · JavaWebinar/masterjava + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content +
+ + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+
+
+ + + + +
+
+ +
    +
  • +
    + +
    + + + + Watch + + + + +
    +
    +
    + + Notifications +
    + + + +
    +
    +
    +
    +
  • + +
  • +
    +
    + + +
    +
    + + +
    + +
  • + +
  • +
    + +
    + +
  • +
+ +

+ + /masterjava + +

+ +
+ +
+ +
+
+ + + + +Permalink + + + +
+ +
+ + +
+ +
+
+ + Switch branches/tags +
+ +
+
+ +
+
+ +
+
+ + + +
+
+ + +
+ +
Nothing to show
+
+ +
+
+
+ +
+ + Find file + + +
+ +
+ + + +
+ + + da75a09 + + Apr 7, 2017 + + + +
+ + +
+ + +
+ +
+
+
+ +
+ Raw + Blame + History +
+ + +
+ +
+ +
+ +
+ 110 lines (93 sloc) + + 10.1 KB +
+
+ + +
+

Онлайн проекта Masterjava.

+

Материалы занятия (скачать все патчи можно через Download папки patch)

+

hw Разбор домашнего задания HW3

+

video 1. Структура многомодульного проекта

+

4_1_HW3_pom_structure.patch

+
+
    +
  • Вместо включения всех модулей в главный аггрегатор, сделал еще 2 аггрегатных модуля 2го уровня: web и services
  • +
  • testResources перенес в главный parent, тк. JUnit тесты могут быть у любого модуля
  • +
+
+

video Вопрос: как разбивать приложение на модули

+

video 2. Реализация модуля export: Thymeleaf и Upload

+

4_2_HW3_thymeleaf_upload.patch

+

4_3_HW3_upload_servlet3.patch

+ +
+

video 3. Maven. Поиск и разрешение конфликтов зависимостей

+ +
mvn dependency:tree
+mvn project-info-reports:dependencies
+
+

4_4_dependencies.patch

+ +
mvn project-info-reports:dependency-convergence
+
+

4_5_fix_convergence.patch

+

4_6_enforcer.patch

+ +
mvn clean install
+mvn -DincludeScope=runtime dependency:copy-dependencies
+java -jar jar-hell.jar . 
+
+ +
mvn dependency:analyze
+
+ +

video 4. Подключаем логирование с общими настройкам

+

4_7_logging.patch

+
+
    +
  • Перенес подключение logback-test.xml из parent-web в parent (он используется в JUnit тестах, которые могут быть в любом модуле)
  • +
  • Добавил в корень проекта config_templates с копией конфигурации. +Общие файлы конфигурации заданы в maven parent как в <masterjava.config>/apps/masterjava/config/</masterjava.config>. +Нужно у себя в корне диска создать этот каталог и положить в него содержимое config_templates
  • +
+
+ +

video 5.Библиотеки и фреймворки для работы с JDBC.

+

Выбор lightweight JDBC helper library

+ +

Tomcat Class Loader. Memory Leeks

+

4_8_fix_and_context.patch

+
+

Сделал небольшой fix и сохранил конфигурацию Tomcat context.xml, в котором конфигурируется jdbc/masterjava. Ее надо будет положить в ${TOMCAT_HOME}/conf

+
+ +

video 6. Модуль persist

+

ВНИМАНИЕ! перед накаткой патча создейте в корне проекта каталоги persist\src\main и persist\src\test, иначе патч промахивается.

+

4_9_persist.patch

+ +

Домашнее задание

+
    +
  • в модуле export сохранить всех импортированных пользователей в базе (записи просто добавляются в таблицу users). Делать вставку группами (chunk) в batch моде. Количестово пользователей в chunk принимать с UI как параметр
  • +
  • сделать отображение первых 20 пользователей в модуле webapp
  • +
  • добавить в таблицу users уникальный индекс на email и вставлять в базу только новых пользователей. Результат импорта: пользователи, уже присутствующие в базе (POSTGRES: INSERT ON CONFLICT)
  • +
+

Optional

+
    +
  • cделать сохранение пользователей чанками в несколько потоков (по мере чтения xml). Результат импорта: пользователи, уже присутствующие в базе и диапазоны (начальный-конечный email) в чанке, +если его обработка закончилась с ошибкой + причина отказа в импорте для каждого пользователя/диапазона.
  • +
  • дополнительно + +
  • +
+
+
+ +
+ + + + + +
+ +
+ +
+
+ +
+ + + + + + + +
+ + + You can't perform that action at this time. +
+ + + + + + + + + +
+ + You signed in with another tab or window. Reload to refresh your session. + You signed out in another tab or window. Reload to refresh your session. +
+ + + + + + diff --git a/doc/lesson05.md b/doc/lesson05.md new file mode 100644 index 000000000..9de52bc4e --- /dev/null +++ b/doc/lesson05.md @@ -0,0 +1,750 @@ + + + + + + + + + + + + + + + + + + + + + + + masterjava/lesson05.md at doc · JavaWebinar/masterjava + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content +
+ + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+
+
+ + + + +
+
+ +
    +
  • +
    + +
    + + + + Watch + + + + +
    +
    +
    + + Notifications +
    + + + +
    +
    +
    +
    +
  • + +
  • +
    +
    + + +
    +
    + + +
    + +
  • + +
  • +
    + +
    + +
  • +
+ +

+ + /masterjava + +

+ +
+ +
+ +
+
+ + + + +Permalink + + + +
+ +
+ + +
+ +
+
+ + Switch branches/tags +
+ +
+
+ +
+
+ +
+
+ + + +
+
+ + +
+ +
Nothing to show
+
+ +
+
+
+ +
+ + Find file + + +
+ +
+ + + +
+ Fetching contributors… +
+ +
+ + Cannot retrieve contributors at this time +
+
+
+
+
+ +
+ Raw + Blame + History +
+ + +
+ +
+ +
+ +
+ 52 lines (41 sloc) + + 4.84 KB +
+
+ + +
+

Онлайн проекта Masterjava.

+

Материалы занятия (скачать все патчи можно через Download папки patch)

+

hw Разбор домашнего задания HW4

+

video 1. Сохранение в базу в batch-моде с обработкой конфликтов

+

5_1_HW4_export_chunk.patch

+

5_2_HW4_webapp_users.patch

+

!! если патч применяется криво- создате каталог web\webapp\src

+

5_3_HW4_already_present.patch

+

video 2. Вставка в несколько потоков

+

5_4_HW4_parallel.patch

+
+

video 3. Конфигурирование приложения

+

ВНИМАНИЕ! перед накаткой патча создейте в common каталог \src , иначе патч промахивается (от корня common\src).

+

5_5_typesafe_config.patch

+
+

Не забудте положить persist.conf в /apps/masterjava/config (или убрать required из persist\src\main\resources\persist.conf)

+
+ +

video 4. Lombook

+

5_6_lombook.patch

+ +

correction Правки

+

5_7_fix.patch

+

Небольшие коррекции и фикс: в UserExport.process нельзя делать chunk.clean(), тк этот список используется для вставки.

+

5_8_fix_share_ThymeleafListener.patch

+

Расшарил ThymeleafListener в common-web и использую в webapp UsersServlet

+

Домашнее задание

+
    +
  • добавить в DB и сделать DAO для городов, групп и проектов (города и группы как - forign keys)
  • +
  • добавить тесты на DAO
  • +
  • добавить в модуле export импорт и сохранение в базу городов (импорт групп и проектов будут в следующем ДЗ) +
      +
    • предполагается, что пользователей будет много, а городов, групп, проектов- на порядки меньше.
    • +
    • если город уже есть в базе, просто пропускаем (не считаем ошибкой)
    • +
    +
  • +
+

Optional

+
    +
  • добавить при импорте пользователей связи на города +
      +
    • если город юзера отсутствует в базе - ошибка импорта
    • +
    +
  • +
+
+
+ +
+ + + + + +
+ +
+ +
+
+ +
+ + + + + + + +
+ + + You can't perform that action at this time. +
+ + + + + + + + + +
+ + You signed in with another tab or window. Reload to refresh your session. + You signed out in another tab or window. Reload to refresh your session. +
+ + + + + + diff --git a/doc/lesson06.md b/doc/lesson06.md new file mode 100644 index 000000000..7a6c80d06 --- /dev/null +++ b/doc/lesson06.md @@ -0,0 +1,822 @@ + + + + + + + + + + + + + + + + + + + + + + + masterjava/lesson06.md at doc · JavaWebinar/masterjava + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content +
+ + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+
+
+ + + + +
+
+ +
    +
  • +
    + +
    + + + + Watch + + + + +
    +
    +
    + + Notifications +
    + + + +
    +
    +
    +
    +
  • + +
  • +
    +
    + + +
    +
    + + +
    + +
  • + +
  • +
    + +
    + +
  • +
+ +

+ + /masterjava + +

+ +
+ +
+ +
+
+ + + + +Permalink + + + +
+ +
+ + +
+ +
+
+ + Switch branches/tags +
+ +
+
+ +
+
+ +
+
+ + + +
+
+ + +
+ +
Nothing to show
+
+ +
+
+
+ +
+ + Find file + + +
+ +
+ + + +
+ Fetching contributors… +
+ +
+ + Cannot retrieve contributors at this time +
+
+
+
+
+ +
+ Raw + Blame + History +
+ + +
+ +
+ +
+ +
+ 105 lines (82 sloc) + + 7.81 KB +
+
+ + +
+

Онлайн проекта Masterjava.

+

Внимание!! Обновите проект или накатите 2 fix-а (5_7_fix и 5_8_fix_share_ThymeleafListener) прошлого урока

+

Материалы занятия (скачать все патчи можно через Download папки patch)

+

hw Разбор домашнего задания HW5

+

video 1. Реализация модели/DAO/JUnit

+

Apply 6_1_HW5_model_sql.patch

+ +

Можно запускать скрип миграции много раз, по истории смотрится, чтобы каждый ChangeSet накатился только 1 раз.

+

Apply 6_2_HW5_dao_test.patch

+

video 2. Реализация export

+
+
    +
  • Классы Result/ChunkResult/GroupResult упростил до List<FailedEmail>
  • +
  • Переименовал +
      +
    • ProcessPayload -> PayloadImporter
    • +
    • CityExport -> CityImporter
    • +
    • UserExport -> UserImporter
    • +
    +
  • +
+
+

Apply 6_3_HW5_add_PayloadImporter.patch

+

Apply 6_4_HW5_add_CityImporter.patch

+
+

video 3. Миграция DB

+ +

http://www.liquibase.org/download +http://www.liquibase.org/quickstart.html

+

video 4. Веб-сервисы

+

Apply 6_5_web_services.patch

+ +

REST.

+ +

SOAP

+ +

Java реализации.

+ +

Имплементируем Mail Service

+ +
+

Домашнее задание

+
    +
  • Реализовать MailSender с конфигурированием параметров в mail.conf + +
  • +
+
   mail.host: smtp.yandex.ru
+   mail.port: 465
+   mail.username: user@yandex.ru
+   mail.password: password
+   mail.useSSL: true
+   mail.useTLS: false
+   mail.debug: true
+   mail.fromName: MasterJava
+
+
    +
  • Сохранят результат отправки писем в DB (в MailSender).
  • +
  • DAO и модель для сохранения сделать в модуле mail-service
  • +
  • Протестировать отправку почты через SoapUI и/или MailServiceClient
  • +
+

Optional

+
    +
  • добавить в модуле export импорт и сохранение в базу групп и проектов (только добавление без удаления/модификации)
  • +
  • добавить при импорте пользователей связи на группы +
      +
    • группы только добавляем
    • +
    • если юзер уже есть в базе, поведение остается прежним: ошибка импорта (группы на него не добавляются)
    • +
    • если какая-либо группа пользователя отсутствует в базе - также ошибка импорта
    • +
    +
  • +
+
+
+ +
+ + + + + +
+ +
+ +
+
+ +
+ + + + + + + +
+ + + You can't perform that action at this time. +
+ + + + + + + + + +
+ + You signed in with another tab or window. Reload to refresh your session. + You signed out in another tab or window. Reload to refresh your session. +
+ + + + + + diff --git a/doc/lesson07.md b/doc/lesson07.md new file mode 100644 index 000000000..517eb4e23 --- /dev/null +++ b/doc/lesson07.md @@ -0,0 +1,766 @@ + + + + + + + + + + + + + + + + + + + + + + + masterjava/lesson07.md at doc · JavaWebinar/masterjava + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content +
+ + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+
+
+ + + + +
+
+ +
    +
  • +
    + +
    + + + + Watch + + + + +
    +
    +
    + + Notifications +
    + + + +
    +
    +
    +
    +
  • + +
  • +
    +
    + + +
    +
    + + +
    + +
  • + +
  • +
    + +
    + +
  • +
+ +

+ + /masterjava + +

+ +
+ +
+ +
+
+ + + + +Permalink + + + +
+ +
+ + +
+ +
+
+ + Switch branches/tags +
+ +
+
+ +
+
+ +
+
+ + + +
+
+ + +
+ +
Nothing to show
+
+ +
+
+
+ +
+ + Find file + + +
+ +
+ + + +
+ Fetching contributors… +
+ +
+ + Cannot retrieve contributors at this time +
+
+
+
+
+ +
+ Raw + Blame + History +
+ + +
+ +
+ +
+ +
+ 58 lines (47 sloc) + + 5.29 KB +
+
+ + +
+

Онлайн проекта Masterjava.

+

Материалы занятия (скачать все патчи можно через Download папки patch)

+

hw Разбор домашнего задания HW6

+

video 1. Реализация MailSender

+

Apply 7_1_HW5_MailSender.patch

+ +

video 2. Сохранение результатов отправки в DB

+

Apply 7_2_HW6_mail_history.patch

+
+

Добавляем таблицу mail_hist через миграцию: после патча просто запускаем config_templates\sql\lb_apply.bat

+
+ +

video 3. Импорт Проектов и Групп

+

Apply 7_3_HW6_ProjectGroupImporter.patch

+

Apply 7_4_HW6_refactor_UserImporter.patch

+ +
+

video 4. Стили WSDL. Кастомизация WSDL

+

Apply 7_5_customize_WSDL.patch

+ +

video 5. Публикация кастомизированного WSDL. Автогенерация.

+

Apply 7_6_publish_CustomizedWSDL.patch

+ +

video 6. Деплой в Tomcat

+

Apply 7_7_deploy_Tomcat.patch

+ +

video 7. Создание клиента почтового сервиса.

+

Apply 7_8_create_client.patch

+ +
+

video 8. Реализация массовой и групповой отправки почты. HW7

+

Apply 7_9_refactoring.patch

+

Apply 7_10_send_group_bulk.patch

+

Домашнее задание

+
    +
  • Расшарить wsdl для всех модей в \apps\masterjava\config\wsdl
  • +
  • Обновить mailService.wsdl в соответствии с реализацией (+пофиксить проблемы) и протестировать работу сервиса
  • +
+

Optional

+
    +
  • Сделать в модуле web простой интерфейс для выбора пользователей из таблицы и отправки им почты
  • +
+
+
+ +
+ + + + + +
+ +
+ +
+
+ +
+ + + + + + + +
+ + + You can't perform that action at this time. +
+ + + + + + + + + +
+ + You signed in with another tab or window. Reload to refresh your session. + You signed out in another tab or window. Reload to refresh your session. +
+ + + + + + diff --git a/doc/lesson08.md b/doc/lesson08.md new file mode 100644 index 000000000..59596794a --- /dev/null +++ b/doc/lesson08.md @@ -0,0 +1,753 @@ + + + + + + + + + + + + + + + + + + + + + + + masterjava/lesson08.md at doc · JavaWebinar/masterjava + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content +
+ + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+
+
+ + + + +
+
+ +
    +
  • +
    + +
    + + + + Watch + + + + +
    +
    +
    + + Notifications +
    + + + +
    +
    +
    +
    +
  • + +
  • +
    +
    + + +
    +
    + + +
    + +
  • + +
  • +
    + +
    + +
  • +
+ +

+ + /masterjava + +

+ +
+ +
+ +
+
+ + + + +Permalink + + + +
+ +
+ + +
+ +
+
+ + Switch branches/tags +
+ +
+
+ +
+
+ +
+
+ + + +
+
+ + +
+ +
Nothing to show
+
+ +
+
+
+ +
+ + Find file + + +
+ +
+ + + +
+ + + 902224d + + Apr 22, 2017 + + + +
+ + +
+ + +
+ +
+
+
+ +
+ Raw + Blame + History +
+ + +
+ +
+ +
+ +
+ 38 lines (29 sloc) + + 3.41 KB +
+
+ + +
+

Онлайн проекта Masterjava.

+

Материалы занятия

+

error Правки в проекте

+

Apply 0_fix.patch

+
+

Удалил дубликаты GroupResult/ MailResult из сервиса и немного их подправил для сериализации/десериализации в xml

+
+

hw Разбор домашнего задания HW7

+

video 1. Делаем общий mailService.wsdl

+

Apply 1_HW7_wsdl_share.patch

+

video 2. Доступ к переменным maven в приложении

+

Apply 2_app_conf.patch

+

video 3. Обновление WSDL

+

Apply 3_HW7_update_wsdl.patch

+

video 4. Отправка почты из модуля webapp

+

Apply 4_HW7_webapp.patch

+
+

video 5. SOAP Exception. Выделение общей части схемы

+

ВНИМАНИЕ! перед накаткой патча создейте в services каталог \common-ws (от корня services\common-ws)

+

Apply 5_soap_exceptions.patch

+ +

video 6. Коррекция схемы

+

Apply 6_fix_wsdl_and_schema.patch

+
+

video 7. Домашнее задание

+

Сделать отправку почты из модуля web c вложениями:

+ +

Optional

+ +
+
+ +
+ + + + + +
+ +
+ +
+
+ +
+ + + + + + + +
+ + + You can't perform that action at this time. +
+ + + + + + + + + +
+ + You signed in with another tab or window. Reload to refresh your session. + You signed out in another tab or window. Reload to refresh your session. +
+ + + + + + diff --git a/doc/lesson09.md b/doc/lesson09.md new file mode 100644 index 000000000..aa15ddba4 --- /dev/null +++ b/doc/lesson09.md @@ -0,0 +1,615 @@ + + + + + + + + + + + + + + + + + + + + + + + masterjava/lesson09.md at doc · JavaWebinar/masterjava · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content +
+ + + + + + + + + + +
+ +
+ +
+
+ + + +
+
+
+ + + + + + +
+
+ + + +

+ + /masterjava + +

+ +
+ +
+ +
+
+ + + + + + + + + Permalink + + + +
+ +
+ + +
+ +
+
+ + Switch branches/tags +
+ +
+
+ +
+
+ +
+
+ + + +
+
+ + +
+ +
Nothing to show
+
+ +
+
+
+ +
+ + Find file + + +
+ +
+ + + +
+ + + c7938bb + + May 9, 2017 + + + +
+ + +
+ + +
+ +
+
+
+ +
+ Raw + Blame + History +
+ + + + +
+ +
+ 57 lines (39 sloc) + + 4.67 KB +
+
+ + +
+

Онлайн проекта Masterjava.

+

Материалы занятия

+

video 1. Добавление мавен плагинов

+

Apply 9_0_mvn_plugins.patch

+ +

hw Разбор домашнего задания HW8

+

video 2. Реализация вложений в веб-сервисе

+

Apply 9_1_HW8_service_attach.patch

+ +

video 3. Подключение MTOM

+

Apply 9_2_HW8_MTOM.patch

+

video 4. Реализация загрузки вложений в модуле webapp

+
+

Реализовал загрузку вложения через Servlet 3 @MultipartConfig

+
+

Apply 9_3_HW8_webapp_attach.patch

+

video 5. Реализация вложений в почте

+

Apply 9_4_HW8_mail_attach.patch

+
+

video 6. JAX-WS Message Context. Авторизация

+

Apply 9_5_msg_ctx_auth.patch

+ +

Асинхронный вызов через @OneWay

+

video 7. JAX-WS Handlers

+

Apply 9_6_logging_handlers.patch

+ +

video 8. Домашнее задание. Статистика

+

Apply 9_7_prepare_HW9.patch

+
    +
  • Сделать отдельный Handler статистики трафика веб-сервиса (в статистике только логирование)
  • +
  • Сделать авторизацию в mailService через SoapServerSecurityHandler
  • +
+

Optional

+
    +
  • Вынести уровень логирования веб-сервисов и креденшелы авторизации в конфигурацию (host.conf)
  • +
  • Сделать (отнаследовать) для mail-service свои хендлеры логирования и авторизации с настройками из конфигурации
  • +
+
+
+ +
+ + + + + + + + + +
+ +
+ + + +
+
+ +
+ + + + + + +
+ + + You can't perform that action at this time. +
+ + + + + + + + + +
+ + You signed in with another tab or window. Reload to refresh your session. + You signed out in another tab or window. Reload to refresh your session. +
+ + + + + + diff --git a/doc/lesson10.md b/doc/lesson10.md new file mode 100644 index 000000000..f225324c8 --- /dev/null +++ b/doc/lesson10.md @@ -0,0 +1,651 @@ + + + + + + + + + + + + + + + + + + + + + + + masterjava/lesson10.md at doc · JavaWebinar/masterjava · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content +
+ + + + + + + + + + +
+ +
+ +
+
+ + + +
+
+
+ + + + + + +
+
+ + + +

+ + /masterjava + +

+ +
+ +
+ +
+
+ + + + + + + + + Permalink + + + +
+ +
+ + +
+ +
+
+ + Switch branches/tags +
+ +
+
+ +
+
+ +
+
+ + + +
+
+ + +
+ +
Nothing to show
+
+ +
+
+
+ +
+ + Find file + + +
+ +
+ + + +
+ + + 09b621b + + Jun 2, 2017 + + + +
+ + +
+ + +
+ +
+
+
+ +
+ Raw + Blame + History +
+ + + + +
+ +
+ 72 lines (57 sloc) + + 6.15 KB +
+
+ + +
+

Онлайн проекта Masterjava.

+

Материалы занятия

+

hw Разбор домашнего задания HW9

+

video 1. Реализация SOAP handlers

+
+
    +
  • Хендреры логирования положил в SoapLoggingHandlers
  • +
  • Для SecurityHandler в mail-service сделал родительский MailHandlers
  • +
+
+

Apply 10_1_HW9_handlers.patch

+

video 2. Конфигурирование сервисов

+
+

Поправил в конфигурации обработку null значений

+
+

Apply 10_2_HW9_host_config.patch

+ +
+

video 4. JavaEE

+ +

video 5. JAX-RS

+

Apply 10_3_JAX_RS.patch

+

Apply 10_4_jersey_logging.patch

+
+ +
+ +

video 6. JMS

+

Apply 10_5_JMS.patch

+
+

Внимание Для того, чтобы по JNDI приложение работало с JMS скопируйте:

+
    +
  • config_templates/context.xml в $TOMCAT_HOME/conf
  • +
  • $MAVEN_REPO(~/.m2)/org/apache/activemq/activemq-all/5.14.5/activemq-all-5.14.5.jar в $TOMCAT_HOME/lib
  • +
+
+ +

Домашнее задание

+
    +
  • Добавить аттачи в JAX-RS + +
  • +
  • Реализовать отсылку почты через JMS ObjectMessage
  • +
+

Optional

+
    +
  • Починить вложения в JAX-RS (javax.mail читает поток из вложения 2 раза)
  • +
  • Сделать вложения в JMS
  • +
+
+
+ +
+ + + + + + + + + +
+ +
+ + + +
+
+ +
+ + + + + + +
+ + + You can't perform that action at this time. +
+ + + + + + + + + +
+ + You signed in with another tab or window. Reload to refresh your session. + You signed out in another tab or window. Reload to refresh your session. +
+ + + + + + diff --git a/doc/lesson11.md b/doc/lesson11.md new file mode 100644 index 000000000..93c600b7e --- /dev/null +++ b/doc/lesson11.md @@ -0,0 +1,659 @@ + + + + + + + + + + + + + + + + + + + + + + + masterjava/lesson11.md at doc · JavaWebinar/masterjava · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content +
+ + + + + + + + + + +
+ +
+ +
+
+ + + +
+
+
+ + + + + + +
+
+ + + +

+ + /masterjava + +

+ +
+ +
+ +
+
+ + + + + + + + + Permalink + + + +
+ +
+ + +
+ +
+
+ + Switch branches/tags +
+ +
+
+ +
+
+ +
+
+ + + +
+
+ + +
+ +
Nothing to show
+
+ +
+
+
+ +
+ + Find file + + +
+ +
+ + + +
+ + + 2ac662d + + Jun 1, 2017 + + + +
+ + +
+ + +
+ +
+
+
+ +
+ Raw + Blame + History +
+ + + + +
+ +
+ 84 lines (65 sloc) + + 7.4 KB +
+
+ + +
+

Онлайн проекта Masterjava.

+

Материалы занятия

+

video 0. Авторизация в контейнере Tomcat

+

Apply 11_0_tomcat_auth.patch

+ +

hw Разбор домашнего задания HW10

+

video 1. Отправка вложений по JAX-RS

+

Apply 11_1_HW10_jersey_attach.patch

+

video 2. Отправка почты с вложениями по JMS

+ +

video 3. Рефакторинг. Эксепшены в Java 8 лямбда

+

Apply 11_2_HW10_JMS_attach.patch

+

Apply 11_3_HW10_JMS_attach_fix.patch

+
+

Отправку по JMS листа аттачей

+
+

Apply 11_4_refactoring.patch

+ +
+

Дополнительно:

+

video Николай Алименков - Нужен ли нам JMS в мире современных Java-технологий?

+
+

video 4. Concurrent and distributed applications toolkit AKKA

+
+

Внимание! Перед накаткой патчеа создайте в services каталог akka-remote (services/akka-remote) иначе файлы придется руками сюда перетаскивать.

+

После патча скопируйте akka.conf в ${masterjava.config} (/apps/masterjava/config)

+
+

Apply 11_5_akka.patch

+ +

video 5. Отсылка почты через AKKA Actors

+

Apply 11_6_akka_typed.patch

+ +

Apply 11_6_2_fix_add_timeout.patch

+
+

При задержке выполнения актора более чем на 5 сек будет вываливаться AskTimeoutException: Timed out

+
+

Apply 11_7_akka_actor.patch

+ +

video 6. Асинхронные сервлеты 3.0

+ +

Домашнее задание

+
    +
  • Сделать асинхронное ожидание и вывод результатов отправки почты пользователю в сервлетах: +
      +
    • AkkaTypedSendServlet с выполнением в Tomcat ThreadPoolExecutor
    • +
    • AkkaActorSendServlet с выполнением в собственном ExecutorService
    • +
    +
  • +
+

video 7. Разбор решения с асинхронными сервлетами

+

Apply 11_8_async_servlet.patch

+

video 8. Выбор языка программирования

+ +
+
+ +
+ + + + + + + + + +
+ +
+ + + +
+
+ +
+ + + + + + +
+ + + You can't perform that action at this time. +
+ + + + + + + + + +
+ + You signed in with another tab or window. Reload to refresh your session. + You signed out in another tab or window. Reload to refresh your session. +
+ + + + + + diff --git a/parent-web/pom.xml b/parent-web/pom.xml new file mode 100644 index 000000000..d3506f059 --- /dev/null +++ b/parent-web/pom.xml @@ -0,0 +1,122 @@ + + + 4.0.0 + + + ru.javaops + parent + ../parent/pom.xml + 1.0-SNAPSHOT + + + parent-web + pom + 1.0-SNAPSHOT + Parent Web + + + true + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.0.0 + + false + + + src/main/webapp + + + ${masterjava.config} + + version.html + + true + + + + + + + + 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 + app.conf + + + + 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..78ce9a675 --- /dev/null +++ b/parent/pom.xml @@ -0,0 +1,148 @@ + + + 4.0.0 + + ru.javaops + parent + pom + 1.0-SNAPSHOT + Parent + + + 1.8 + UTF-8 + UTF-8 + + 1.2.2 + 1.7.25 + + + /home/konst/work/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.19.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} + + + + org.slf4j + jul-to-slf4j + ${slf4j.version} + + + + + + + junit + junit + 4.12 + test + + + + + + + + + + \ No newline at end of file diff --git a/patches/10/patch-20170506T181640Z-001.zip b/patches/10/patch-20170506T181640Z-001.zip new file mode 100644 index 000000000..241be0c2c Binary files /dev/null and b/patches/10/patch-20170506T181640Z-001.zip differ diff --git a/patches/10/patch/10_1_HW9_handlers.patch b/patches/10/patch/10_1_HW9_handlers.patch new file mode 100644 index 000000000..a6b0d11d6 --- /dev/null +++ b/patches/10/patch/10_1_HW9_handlers.patch @@ -0,0 +1,345 @@ +Index: services/common-ws/src/main/java/ru/javaops/web/handler/SoapLoggingHandler.java +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/handler/SoapLoggingHandler.java (revision 52524e425e74665e0bda77f03f6ee2c565b7353c) ++++ services/common-ws/src/main/java/ru/javaops/web/handler/SoapLoggingHandlers.java (revision ) +@@ -25,11 +25,11 @@ + * for better performance over SOAPHandler. + */ + @Slf4j +-public abstract class SoapLoggingHandler extends SoapBaseHandler { ++public abstract class SoapLoggingHandlers extends SoapBaseHandler { + + private final Level loggingLevel; + +- protected SoapLoggingHandler(Level loggingLevel) { ++ protected SoapLoggingHandlers(Level loggingLevel) { + this.loggingLevel = loggingLevel; + } + +@@ -117,4 +117,27 @@ + HANDLER_MAP.get(loggingLevel).handleFault(mhc); + return true; + } ++ ++ public static class ClientHandler extends SoapLoggingHandlers { ++ public ClientHandler(Level loggingLevel) { ++ super(loggingLevel); ++ } ++ ++ @Override ++ protected boolean isRequest(boolean isOutbound) { ++ return isOutbound; ++ } ++ } ++ ++ public static class ServerHandler extends SoapLoggingHandlers { ++ ++ public ServerHandler() { ++ super(Level.INFO); ++ } ++ ++ @Override ++ protected boolean isRequest(boolean isOutbound) { ++ return !isOutbound; ++ } ++ } + } +Index: services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailHandlers.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailHandlers.java (revision ) ++++ services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailHandlers.java (revision ) +@@ -0,0 +1,11 @@ ++package ru.javaops.masterjava.service.mail; ++ ++import ru.javaops.web.handler.SoapServerSecurityHandler; ++ ++public class MailHandlers { ++ public static class SecurityHandler extends SoapServerSecurityHandler { ++ public SecurityHandler() { ++ super(MailWSClient.USER, MailWSClient.PASSWORD); ++ } ++ } ++} +Index: services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java (revision 52524e425e74665e0bda77f03f6ee2c565b7353c) ++++ services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java (revision ) +@@ -1,41 +1,39 @@ + package ru.javaops.masterjava.service.mail; + +-import ru.javaops.web.AuthUtil; + import ru.javaops.web.WebStateException; + +-import javax.annotation.Resource; + import javax.jws.HandlerChain; + import javax.jws.WebService; +-import javax.xml.ws.WebServiceContext; +-import javax.xml.ws.handler.MessageContext; ++import javax.xml.ws.soap.MTOM; + import java.util.List; +-import java.util.Map; + import java.util.Set; + + @WebService(endpointInterface = "ru.javaops.masterjava.service.mail.MailService", targetNamespace = "http://mail.javaops.ru/" + // , wsdlLocation = "WEB-INF/wsdl/mailService.wsdl" + ) + //@StreamingAttachment(parseEagerly=true, memoryThreshold=40000L) +-//@MTOM ++@MTOM + @HandlerChain(file = "mailWsHandlers.xml") + public class MailServiceImpl implements MailService { + +- @Resource +- private WebServiceContext wsContext; ++// @Resource ++// private WebServiceContext wsContext; + + @Override + public String sendToGroup(Set to, Set cc, String subject, String body, List attaches) throws WebStateException { ++/* + MessageContext mCtx = wsContext.getMessageContext(); + Map> headers = (Map>) mCtx.get(MessageContext.HTTP_REQUEST_HEADERS); + +-// HttpServletRequest request = (HttpServletRequest) mCtx.get(MessageContext.SERVLET_REQUEST); +-// HttpServletResponse response = (HttpServletResponse) mCtx.get(MessageContext.SERVLET_RESPONSE); ++ HttpServletRequest request = (HttpServletRequest) mCtx.get(MessageContext.SERVLET_REQUEST); ++ HttpServletResponse response = (HttpServletResponse) mCtx.get(MessageContext.SERVLET_RESPONSE); + + int code = AuthUtil.checkBasicAuth(headers, MailWSClient.AUTH_HEADER); + if (code != 0) { + mCtx.put(MessageContext.HTTP_RESPONSE_CODE, code); + throw new SecurityException(); + } ++*/ + return MailSender.sendToGroup(to, cc, subject, body, attaches); + } + +Index: services/common-ws/src/main/java/ru/javaops/web/handler/SoapClientLoggingHandler.java +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/handler/SoapClientLoggingHandler.java (revision 52524e425e74665e0bda77f03f6ee2c565b7353c) ++++ services/common-ws/src/main/java/ru/javaops/web/handler/SoapClientLoggingHandler.java (revision 52524e425e74665e0bda77f03f6ee2c565b7353c) +@@ -1,15 +0,0 @@ +-package ru.javaops.web.handler; +- +- +-import org.slf4j.event.Level; +- +-public class SoapClientLoggingHandler extends SoapLoggingHandler { +- public SoapClientLoggingHandler(Level loggingLevel) { +- super(loggingLevel); +- } +- +- @Override +- protected boolean isRequest(boolean isOutbound) { +- return isOutbound; +- } +-} +\ No newline at end of file +Index: services/common-ws/src/main/java/ru/javaops/web/handler/SoapServerLoggingHandler.java +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/handler/SoapServerLoggingHandler.java (revision 52524e425e74665e0bda77f03f6ee2c565b7353c) ++++ services/common-ws/src/main/java/ru/javaops/web/handler/SoapServerLoggingHandler.java (revision 52524e425e74665e0bda77f03f6ee2c565b7353c) +@@ -1,16 +0,0 @@ +-package ru.javaops.web.handler; +- +- +-import org.slf4j.event.Level; +- +-public class SoapServerLoggingHandler extends SoapLoggingHandler { +- +- public SoapServerLoggingHandler() { +- super(Level.INFO); +- } +- +- @Override +- protected boolean isRequest(boolean isOutbound) { +- return !isOutbound; +- } +-} +\ No newline at end of file +Index: services/common-ws/src/main/java/ru/javaops/web/WsClient.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/WsClient.java (revision 52524e425e74665e0bda77f03f6ee2c565b7353c) ++++ services/common-ws/src/main/java/ru/javaops/web/WsClient.java (revision ) +@@ -31,7 +31,7 @@ + } + + public void init(String host, String endpointAddress) { +- this.endpointAddress = HOSTS.getString(host) + endpointAddress; ++ this.endpointAddress = HOSTS.getConfig(host).getString("endpoint") + endpointAddress; + } + + // Post is not thread-safe (http://stackoverflow.com/a/10601916/548473) +Index: services/common-ws/src/main/java/ru/javaops/web/handler/SoapStatisticHandler.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/handler/SoapStatisticHandler.java (revision ) ++++ services/common-ws/src/main/java/ru/javaops/web/handler/SoapStatisticHandler.java (revision ) +@@ -0,0 +1,30 @@ ++package ru.javaops.web.handler; ++ ++import com.sun.xml.ws.api.handler.MessageHandlerContext; ++import ru.javaops.web.Statistics; ++ ++public class SoapStatisticHandler extends SoapBaseHandler { ++ ++ private static final String PAYLOAD = "PAYLOAD"; ++ private static final String START_TIME = "START_TIME"; ++ ++ public boolean handleMessage(MessageHandlerContext context) { ++ if (isOutbound(context)) { ++ count(context, Statistics.RESULT.SUCCESS); ++ } else { ++ String payload = context.getMessage().getPayloadLocalPart(); ++ context.put(PAYLOAD, payload); ++ context.put(START_TIME, System.currentTimeMillis()); ++ } ++ return true; ++ } ++ ++ public boolean handleFault(MessageHandlerContext context) { ++ count(context, Statistics.RESULT.FAIL); ++ return true; ++ } ++ ++ private void count(MessageHandlerContext context, Statistics.RESULT result) { ++ Statistics.count((String) context.get(PAYLOAD), (Long) context.get(START_TIME), result); ++ } ++} +\ No newline at end of file +Index: services/common-ws/src/main/java/ru/javaops/web/handler/SoapServerSecurityHandler.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/handler/SoapServerSecurityHandler.java (revision ) ++++ services/common-ws/src/main/java/ru/javaops/web/handler/SoapServerSecurityHandler.java (revision ) +@@ -0,0 +1,43 @@ ++package ru.javaops.web.handler; ++ ++import com.sun.xml.ws.api.handler.MessageHandlerContext; ++import lombok.extern.slf4j.Slf4j; ++import ru.javaops.web.AuthUtil; ++ ++import javax.xml.ws.handler.MessageContext; ++import java.util.List; ++import java.util.Map; ++ ++import static ru.javaops.web.AuthUtil.encodeBasicAuthHeader; ++ ++@Slf4j ++abstract public class SoapServerSecurityHandler extends SoapBaseHandler { ++ ++ private String authHeader; ++ ++ public SoapServerSecurityHandler(String user, String password) { ++ this(encodeBasicAuthHeader(user, password)); ++ } ++ ++ public SoapServerSecurityHandler(String authHeader) { ++ this.authHeader = authHeader; ++ } ++ ++ @Override ++ public boolean handleMessage(MessageHandlerContext ctx) { ++ if (!isOutbound(ctx)) { ++ Map> headers = (Map>) ctx.get(MessageContext.HTTP_REQUEST_HEADERS); ++ int code = AuthUtil.checkBasicAuth(headers, authHeader); ++ if (code != 0) { ++ ctx.put(MessageContext.HTTP_RESPONSE_CODE, code); ++ throw new SecurityException(); ++ } ++ } ++ return true; ++ } ++ ++ @Override ++ public boolean handleFault(MessageHandlerContext context) { ++ return true; ++ } ++} +Index: services/mail-service/src/main/resources/mailWsHandlers.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/resources/mailWsHandlers.xml (revision 52524e425e74665e0bda77f03f6ee2c565b7353c) ++++ services/mail-service/src/main/resources/mailWsHandlers.xml (revision ) +@@ -2,7 +2,15 @@ + + + SoapLoggingHandler +- ru.javaops.web.handler.SoapServerLoggingHandler ++ ru.javaops.web.handler.SoapLoggingHandlers$ServerHandler ++ ++ ++ SoapStatisticHandler ++ ru.javaops.web.handler.SoapStatisticHandler ++ ++ ++ SoapStatisticHandler ++ ru.javaops.masterjava.service.mail.MailHandlers$SecurityHandler + + + +\ No newline at end of file +Index: services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (revision 52524e425e74665e0bda77f03f6ee2c565b7353c) ++++ services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (revision ) +@@ -6,10 +6,9 @@ + import com.google.common.io.Resources; + import lombok.extern.slf4j.Slf4j; + import org.slf4j.event.Level; +-import ru.javaops.web.AuthUtil; + import ru.javaops.web.WebStateException; + import ru.javaops.web.WsClient; +-import ru.javaops.web.handler.SoapClientLoggingHandler; ++import ru.javaops.web.handler.SoapLoggingHandlers; + + import javax.xml.namespace.QName; + import javax.xml.ws.soap.MTOMFeature; +@@ -21,9 +20,7 @@ + private static final WsClient WS_CLIENT; + public static final String USER = "user"; + public static final String PASSWORD = "password"; +- private static final SoapClientLoggingHandler LOGGING_HANDLER = new SoapClientLoggingHandler(Level.DEBUG); +- +- public static String AUTH_HEADER = AuthUtil.encodeBasicAuthHeader(USER, PASSWORD); ++ private static final SoapLoggingHandlers.ClientHandler LOGGING_HANDLER = new SoapLoggingHandlers.ClientHandler(Level.DEBUG); + + static { + WS_CLIENT = new WsClient(Resources.getResource("wsdl/mailService.wsdl"), +Index: services/common-ws/src/main/java/ru/javaops/web/AuthUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/AuthUtil.java (revision 52524e425e74665e0bda77f03f6ee2c565b7353c) ++++ services/common-ws/src/main/java/ru/javaops/web/AuthUtil.java (revision ) +@@ -7,9 +7,10 @@ + import java.util.List; + import java.util.Map; + ++import static com.google.common.net.HttpHeaders.AUTHORIZATION; ++ + @Slf4j + public class AuthUtil { +- private static final String AUTHORIZATION = "Authorization"; + + public static String encodeBasicAuthHeader(String name, String passw) { + String authString = name + ":" + passw; diff --git a/patches/10/patch/10_2_HW9_host_config.patch b/patches/10/patch/10_2_HW9_host_config.patch new file mode 100644 index 000000000..999489882 --- /dev/null +++ b/patches/10/patch/10_2_HW9_host_config.patch @@ -0,0 +1,255 @@ +Index: common/src/main/resources/defaults.conf +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- common/src/main/resources/defaults.conf (revision ) ++++ common/src/main/resources/defaults.conf (revision ) +@@ -0,0 +1,4 @@ ++client.debugLevel = INFO ++server.debugLevel = INFO ++user = null ++password = null +\ No newline at end of file +Index: services/common-ws/src/main/java/ru/javaops/web/handler/SoapServerSecurityHandler.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/handler/SoapServerSecurityHandler.java (date 1493987413000) ++++ services/common-ws/src/main/java/ru/javaops/web/handler/SoapServerSecurityHandler.java (revision ) +@@ -25,7 +25,7 @@ + + @Override + public boolean handleMessage(MessageHandlerContext ctx) { +- if (!isOutbound(ctx)) { ++ if (!isOutbound(ctx) && authHeader != null) { + Map> headers = (Map>) ctx.get(MessageContext.HTTP_REQUEST_HEADERS); + int code = AuthUtil.checkBasicAuth(headers, authHeader); + if (code != 0) { +Index: common/src/main/resources/hosts.conf +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- common/src/main/resources/hosts.conf (date 1493987413000) ++++ common/src/main/resources/hosts.conf (revision ) +@@ -1,10 +1,6 @@ + hosts { + mail { + endpoint = "http://localhost:8080" +- debug.client = DEBUG +- debug.server = INFO +- user = "user" +- password = "password" + } + } + include file("/apps/masterjava/config/hosts.conf") +Index: services/common-ws/src/main/java/ru/javaops/web/handler/SoapLoggingHandlers.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/handler/SoapLoggingHandlers.java (date 1493987413000) ++++ services/common-ws/src/main/java/ru/javaops/web/handler/SoapLoggingHandlers.java (revision ) +@@ -131,8 +131,8 @@ + + public static class ServerHandler extends SoapLoggingHandlers { + +- public ServerHandler() { +- super(Level.INFO); ++ public ServerHandler(Level loggingLevel) { ++ super(loggingLevel); + } + + @Override +Index: services/common-ws/src/main/java/ru/javaops/web/WsClient.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/WsClient.java (date 1493987413000) ++++ services/common-ws/src/main/java/ru/javaops/web/WsClient.java (revision ) +@@ -1,8 +1,10 @@ + package ru.javaops.web; + + import com.typesafe.config.Config; ++import org.slf4j.event.Level; + import ru.javaops.masterjava.ExceptionType; + import ru.javaops.masterjava.config.Configs; ++import ru.javaops.web.handler.SoapLoggingHandlers; + + import javax.xml.namespace.QName; + import javax.xml.ws.Binding; +@@ -19,7 +21,40 @@ + + private final Class serviceClass; + private final Service service; +- private String endpointAddress; ++ private HostConfig hostConfig; ++ ++ public static class HostConfig { ++ public final String endpoint; ++ public final Level serverDebugLevel; ++ public final String user; ++ public final String password; ++ public final String authHeader; ++ public final SoapLoggingHandlers.ClientHandler clientLoggingHandler; ++ ++ public HostConfig(Config config, String endpointAddress) { ++ endpoint = config.getString("endpoint") + endpointAddress; ++ serverDebugLevel = config.getEnum(Level.class, "server.debugLevel"); ++ ++// https://github.com/typesafehub/config/issues/282 ++ if (!config.getIsNull("user") && !config.getIsNull("password")) { ++ user = config.getString("user"); ++ password = config.getString("password"); ++ authHeader = AuthUtil.encodeBasicAuthHeader(user, password); ++ } else { ++ user = password = authHeader = null; ++ } ++ clientLoggingHandler = config.getIsNull("client.debugLevel") ? null : ++ new SoapLoggingHandlers.ClientHandler(config.getEnum(Level.class, "client.debugLevel")); ++ } ++ ++ public boolean hasAuthorization() { ++ return authHeader != null; ++ } ++ ++ public boolean hasHandler() { ++ return clientLoggingHandler != null; ++ } ++ } + + static { + HOSTS = Configs.getConfig("hosts.conf", "hosts"); +@@ -31,7 +66,12 @@ + } + + public void init(String host, String endpointAddress) { +- this.endpointAddress = HOSTS.getConfig(host).getString("endpoint") + endpointAddress; ++ this.hostConfig = new HostConfig( ++ HOSTS.getConfig(host).withFallback(Configs.getConfig("defaults.conf")), endpointAddress); ++ } ++ ++ public HostConfig getHostConfig() { ++ return hostConfig; + } + + // Post is not thread-safe (http://stackoverflow.com/a/10601916/548473) +@@ -39,7 +79,13 @@ + T port = service.getPort(serviceClass, features); + BindingProvider bp = (BindingProvider) port; + Map requestContext = bp.getRequestContext(); +- requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointAddress); ++ requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, hostConfig.endpoint); ++ if (hostConfig.hasAuthorization()) { ++ setAuth(port, hostConfig.user, hostConfig.password); ++ } ++ if (hostConfig.hasHandler()) { ++ setHandler(port, hostConfig.clientLoggingHandler); ++ } + return port; + } + +Index: services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailHandlers.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailHandlers.java (date 1493987413000) ++++ services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailHandlers.java (revision ) +@@ -1,11 +1,18 @@ + package ru.javaops.masterjava.service.mail; + ++import ru.javaops.web.handler.SoapLoggingHandlers; + import ru.javaops.web.handler.SoapServerSecurityHandler; + + public class MailHandlers { + public static class SecurityHandler extends SoapServerSecurityHandler { + public SecurityHandler() { +- super(MailWSClient.USER, MailWSClient.PASSWORD); ++ super(MailWSClient.getHostConfig().authHeader); ++ } ++ } ++ ++ public static class LoggingHandler extends SoapLoggingHandlers.ServerHandler { ++ public LoggingHandler() { ++ super(MailWSClient.getHostConfig().serverDebugLevel); + } + } + } +Index: services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (date 1493987413000) ++++ services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (revision ) +@@ -5,10 +5,8 @@ + import com.google.common.collect.Iterables; + import com.google.common.io.Resources; + import lombok.extern.slf4j.Slf4j; +-import org.slf4j.event.Level; + import ru.javaops.web.WebStateException; + import ru.javaops.web.WsClient; +-import ru.javaops.web.handler.SoapLoggingHandlers; + + import javax.xml.namespace.QName; + import javax.xml.ws.soap.MTOMFeature; +@@ -18,9 +16,6 @@ + @Slf4j + public class MailWSClient { + private static final WsClient WS_CLIENT; +- public static final String USER = "user"; +- public static final String PASSWORD = "password"; +- private static final SoapLoggingHandlers.ClientHandler LOGGING_HANDLER = new SoapLoggingHandlers.ClientHandler(Level.DEBUG); + + static { + WS_CLIENT = new WsClient(Resources.getResource("wsdl/mailService.wsdl"), +@@ -58,14 +53,15 @@ + } + + private static MailService getPort() { +- MailService port = WS_CLIENT.getPort(new MTOMFeature(1024)); +- WsClient.setAuth(port, USER, PASSWORD); +- WsClient.setHandler(port, LOGGING_HANDLER); +- return port; ++ return WS_CLIENT.getPort(new MTOMFeature(1024)); + } + + public static Set split(String addressees) { + Iterable split = Splitter.on(',').trimResults().omitEmptyStrings().split(addressees); + return ImmutableSet.copyOf(Iterables.transform(split, Addressee::new)); + } ++ ++ public static WsClient.HostConfig getHostConfig() { ++ return WS_CLIENT.getHostConfig(); ++ } + } +Index: services/mail-service/src/main/resources/mailWsHandlers.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/resources/mailWsHandlers.xml (date 1493987413000) ++++ services/mail-service/src/main/resources/mailWsHandlers.xml (revision ) +@@ -1,15 +1,15 @@ + + + +- SoapLoggingHandler +- ru.javaops.web.handler.SoapLoggingHandlers$ServerHandler ++ MailLoggingHandler ++ ru.javaops.masterjava.service.mail.MailHandlers$LoggingHandler + + + SoapStatisticHandler + ru.javaops.web.handler.SoapStatisticHandler + + +- SoapStatisticHandler ++ MailSecurityHandler + ru.javaops.masterjava.service.mail.MailHandlers$SecurityHandler + + diff --git a/patches/10/patch/10_3_JAX_RS.patch b/patches/10/patch/10_3_JAX_RS.patch new file mode 100644 index 000000000..eb9500586 --- /dev/null +++ b/patches/10/patch/10_3_JAX_RS.patch @@ -0,0 +1,128 @@ +Index: services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRestConfig.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRestConfig.java (revision ) ++++ services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRestConfig.java (revision ) +@@ -0,0 +1,12 @@ ++package ru.javaops.masterjava.service.mail.rest; ++ ++import org.glassfish.jersey.server.ResourceConfig; ++ ++import javax.ws.rs.ApplicationPath; ++ ++@ApplicationPath("rest") ++public class MailRestConfig extends ResourceConfig { ++ public MailRestConfig() { ++ packages("ru.javaops.masterjava.service.mail.rest"); ++ } ++} +\ No newline at end of file +Index: services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRS.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRS.java (revision ) ++++ services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRS.java (revision ) +@@ -0,0 +1,32 @@ ++package ru.javaops.masterjava.service.mail.rest; ++ ++ ++import org.hibernate.validator.constraints.NotBlank; ++import ru.javaops.masterjava.service.mail.GroupResult; ++import ru.javaops.masterjava.service.mail.MailServiceExecutor; ++import ru.javaops.masterjava.service.mail.MailWSClient; ++import ru.javaops.web.WebStateException; ++ ++import javax.ws.rs.*; ++import javax.ws.rs.core.MediaType; ++import java.util.Collections; ++ ++@Path("/") ++public class MailRS { ++ @GET ++ @Path("test") ++ @Produces(MediaType.TEXT_PLAIN) ++ public String test() { ++ return "Test"; ++ } ++ ++ @POST ++ @Path("send") ++ @Produces(MediaType.APPLICATION_JSON) ++ public GroupResult send(@NotBlank @FormParam("users") String users, ++ @FormParam("subject") String subject, ++ @NotBlank @FormParam("body") String body) throws WebStateException { ++ ++ return MailServiceExecutor.sendBulk(MailWSClient.split(users), subject, body, Collections.emptyList()); ++ } ++} +\ No newline at end of file +Index: web/webapp/src/main/webapp/WEB-INF/templates/users.html +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- web/webapp/src/main/webapp/WEB-INF/templates/users.html (date 1493993350000) ++++ web/webapp/src/main/webapp/WEB-INF/templates/users.html (revision ) +@@ -36,6 +36,9 @@ + + +
++ SOAP
++ REST
++ +
+ +@@ -56,6 +59,17 @@ +
+ + + +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +-
Full NameEmailFlag
++ ++ ++
++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
#Full NameEmailFlag ++
++
++

++ ++

++

++
++

++

++ ++

++
++
++ + + +\ No newline at end of file +Index: services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (date 1492825936000) ++++ services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (revision ) +@@ -1,5 +1,8 @@ + package ru.javaops.masterjava.service.mail; + ++import com.google.common.base.Splitter; ++import com.google.common.collect.ImmutableSet; ++import com.google.common.collect.Iterables; + import com.google.common.io.Resources; + import lombok.extern.slf4j.Slf4j; + import ru.javaops.masterjava.web.WsClient; +@@ -20,8 +23,34 @@ + } + + +- public static void sendToGroup(final Set to, final Set cc, final String subject, final String body) { ++ public static String sendToGroup(final Set to, final Set cc, final String subject, final String body) { + log.info("Send mail to '" + to + "' cc '" + cc + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); +- WS_CLIENT.getPort().sendToGroup(to, cc, subject, body); ++ String status; ++ try { ++ status = WS_CLIENT.getPort().sendToGroup(to, cc, subject, body); ++ log.info("Sent with status: " + status); ++ } catch (Exception e) { ++ log.error("sendToGroup failed", e); ++ status = e.toString(); ++ } ++ return status; ++ } ++ ++ public static GroupResult sendBulk(final Set to, final String subject, final String body) { ++ log.info("Send mail to '" + to + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); ++ GroupResult result; ++ try { ++ result = WS_CLIENT.getPort().sendBulk(to, subject, body); ++ } catch (Exception e) { ++ log.error("sendIndividualMails failed", e); ++ result = new GroupResult(e); ++ } ++ log.info("Sent with result: " + result); ++ return result; ++ } ++ ++ public static Set split(String addressees) { ++ Iterable split = Splitter.on(',').trimResults().omitEmptyStrings().split(addressees); ++ return ImmutableSet.copyOf(Iterables.transform(split, Addressee::new)); + } + } +Index: services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/GroupResult.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/GroupResult.java (date 1492825936000) ++++ services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/GroupResult.java (revision ) +@@ -1,5 +1,6 @@ + package ru.javaops.masterjava.service.mail; + ++import com.google.common.base.Throwables; + import lombok.AllArgsConstructor; + import lombok.Data; + import lombok.NoArgsConstructor; +@@ -14,6 +15,10 @@ + private List failed; // failed emails with causes + private String failedCause; // global fail cause + ++ public GroupResult(Exception e) { ++ this(-1, null, Throwables.getRootCause(e).toString()); ++ } ++ + @Override + public String toString() { + return "Success: " + success + '\n' + diff --git a/patches/8/patch/8_5_soap_exceptions.patch b/patches/8/patch/8_5_soap_exceptions.patch new file mode 100644 index 000000000..b643941f1 --- /dev/null +++ b/patches/8/patch/8_5_soap_exceptions.patch @@ -0,0 +1,719 @@ +Index: services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java (revision ) ++++ services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java (revision ) +@@ -0,0 +1,19 @@ ++package ru.javaops.web; ++ ++import lombok.Data; ++import lombok.NoArgsConstructor; ++import lombok.NonNull; ++import lombok.RequiredArgsConstructor; ++import ru.javaops.masterjava.ExceptionType; ++ ++@Data ++@RequiredArgsConstructor ++@NoArgsConstructor ++public class FaultInfo { ++ private @NonNull ExceptionType type; ++ ++ @Override ++ public String toString() { ++ return type.toString(); ++ } ++} +Index: common/src/main/java/ru/javaops/masterjava/ExceptionType.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- common/src/main/java/ru/javaops/masterjava/ExceptionType.java (revision ) ++++ common/src/main/java/ru/javaops/masterjava/ExceptionType.java (revision ) +@@ -0,0 +1,34 @@ ++package ru.javaops.masterjava; ++ ++public enum ExceptionType { ++ SYSTEM("Системная ошибка"), ++ DATA_BASE("Ошибка базы данных"), ++ STATE("Неверное состояние приложения"), ++ AUTHORIZATION("Ошибка авторизации"), ++ CONFIGURATION("Ошибка конфигурирования"), ++ ILLEGAL_ARGUMENT("Неверный аргумент"), ++ BPM("Ошибка бизнес-процесса"), ++ FILE("Ошибка при работе с файловой системой"), ++ REPORTS("Ошибка в отчете"), ++ EMAIL("Ошибка при отправке почты"), ++ TEMPLATE("Ошибка в шаблонах"), ++ ONE_C("Ошибка в системе 1C"), ++ ATTACH("Ошибка вложенного файла"), ++ LDAP("Ошибка соединения с LDAP"), ++ NETWORK("Сетевая Ошибка"); ++ ++ final private String descr; ++ ++ ExceptionType(String title) { ++ this.descr = title; ++ } ++ ++ public String getDescr() { ++ return descr; ++ } ++ ++ @Override ++ public String toString() { ++ return name() + " (" + descr + ')'; ++ } ++} +Index: config_templates/wsdl/common.xsd +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- config_templates/wsdl/common.xsd (revision ) ++++ config_templates/wsdl/common.xsd (revision ) +@@ -0,0 +1,32 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +\ No newline at end of file +Index: services/common-ws/src/main/java/ru/javaops/web/WebStateException.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/WebStateException.java (revision ) ++++ services/common-ws/src/main/java/ru/javaops/web/WebStateException.java (revision ) +@@ -0,0 +1,35 @@ ++package ru.javaops.web; ++ ++ ++import com.google.common.base.Throwables; ++import ru.javaops.masterjava.ExceptionType; ++ ++import javax.xml.ws.WebFault; ++ ++@WebFault(name = "webStateException", targetNamespace = "http://common.javaops.ru/") ++public class WebStateException extends Exception { ++ private FaultInfo faultInfo; ++ ++ public WebStateException(String message, FaultInfo faultInfo) { ++ super(message); ++ this.faultInfo = faultInfo; ++ } ++ ++ public WebStateException(Exception e) { ++ this(ExceptionType.SYSTEM, e); ++ } ++ ++ public WebStateException(ExceptionType type, Throwable cause) { ++ super(Throwables.getRootCause(cause).toString(), cause); ++ this.faultInfo = new FaultInfo(type); ++ } ++ ++ public FaultInfo getFaultInfo() { ++ return faultInfo; ++ } ++ ++ @Override ++ public String toString() { ++ return faultInfo.toString() + '\n' + super.toString(); ++ } ++} +Index: services/common-ws/pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/pom.xml (revision ) ++++ services/common-ws/pom.xml (revision ) +@@ -0,0 +1,77 @@ ++ ++ ++ 4.0.0 ++ ++ ++ ru.javaops ++ parent ++ ../../parent/pom.xml ++ 1.0-SNAPSHOT ++ ++ ++ common-ws ++ 1.0-SNAPSHOT ++ Common Web Services ++ ++ ++ ++ ${project.groupId} ++ common ++ ${project.version} ++ ++ ++ ++ com.sun.xml.ws ++ jaxws-rt ++ 2.2.10 ++ ++ ++ org.jvnet.mimepull ++ mimepull ++ ++ ++ javax.xml.bind ++ jaxb-api ++ ++ ++ javax.annotation ++ javax.annotation-api ++ ++ ++ org.jvnet.staxex ++ stax-ex ++ ++ ++ javax.xml.soap ++ javax.xml.soap-api ++ ++ ++ ++ ++ ++ javax.activation ++ activation ++ 1.1.1 ++ ++ ++ org.jvnet.staxex ++ stax-ex ++ 1.7.7 ++ ++ ++ javax.activation ++ activation ++ ++ ++ ++ ++ ++ javax.servlet ++ javax.servlet-api ++ 3.1.0 ++ provided ++ ++ ++ +\ No newline at end of file +Index: services/mail-api/pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/pom.xml (date 1492826050000) ++++ services/mail-api/pom.xml (revision ) +@@ -21,6 +21,7 @@ + ${masterjava.config} + + wsdl/mailService.wsdl ++ wsdl/common.xsd + + + +@@ -29,7 +30,7 @@ + + + ${project.groupId} +- common ++ common-ws + ${project.version} + + +Index: services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailSender.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailSender.java (date 1492826050000) ++++ services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailSender.java (revision ) +@@ -5,9 +5,11 @@ + import lombok.extern.slf4j.Slf4j; + import lombok.val; + import org.apache.commons.mail.EmailException; ++import ru.javaops.masterjava.ExceptionType; + import ru.javaops.masterjava.persist.DBIProvider; + import ru.javaops.masterjava.service.mail.persist.MailCase; + import ru.javaops.masterjava.service.mail.persist.MailCaseDao; ++import ru.javaops.web.WebStateException; + + import java.util.Set; + +@@ -19,12 +21,12 @@ + public class MailSender { + private static final MailCaseDao MAIL_CASE_DAO = DBIProvider.getDao(MailCaseDao.class); + +- static MailResult sendTo(Addressee to, String subject, String body) { ++ static MailResult sendTo(Addressee to, String subject, String body) throws WebStateException { + val state = sendToGroup(ImmutableSet.of(to), ImmutableSet.of(), subject, body); + return new MailResult(to.getEmail(), state); + } + +- static String sendToGroup(Set to, Set cc, String subject, String body) { ++ static String sendToGroup(Set to, Set cc, String subject, String body) throws WebStateException { + log.info("Send mail to \'" + to + "\' cc \'" + cc + "\' subject \'" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); + String state = MailResult.OK; + try { +@@ -45,7 +47,12 @@ + log.error(e.getMessage(), e); + state = e.getMessage(); + } +- MAIL_CASE_DAO.insert(MailCase.of(to, cc, subject, body, state)); ++ try { ++ MAIL_CASE_DAO.insert(MailCase.of(to, cc, subject, body, state)); ++ } catch (Exception e) { ++ log.error("Mail history saving exception", e); ++ throw new WebStateException(ExceptionType.DATA_BASE, e); ++ } + log.info("Sent with state: " + state); + return state; + } +Index: services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java (date 1492826050000) ++++ services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java (revision ) +@@ -1,5 +1,7 @@ + package ru.javaops.masterjava.service.mail; + ++import ru.javaops.web.WebStateException; ++ + import javax.jws.WebService; + import java.util.Set; + +@@ -9,12 +11,12 @@ + public class MailServiceImpl implements MailService { + + @Override +- public String sendToGroup(Set to, Set cc, String subject, String body) { ++ public String sendToGroup(Set to, Set cc, String subject, String body) throws WebStateException { + return MailSender.sendToGroup(to, cc, subject, body); + } + + @Override +- public GroupResult sendBulk(Set to, String subject, String body) { ++ public GroupResult sendBulk(Set to, String subject, String body) throws WebStateException { + return MailServiceExecutor.sendBulk(to, subject, body); + } + } +\ No newline at end of file +Index: services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java (date 1492826050000) ++++ services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java (revision ) +@@ -1,6 +1,7 @@ + package ru.javaops.masterjava.service.mail; + + import com.google.common.collect.ImmutableSet; ++import ru.javaops.web.WebStateException; + + import javax.xml.namespace.QName; + import javax.xml.ws.Service; +@@ -21,10 +22,14 @@ + new Addressee("Мастер Java "), + new Addressee("Bad Email ")); + +- String status = mailService.sendToGroup(addressees, ImmutableSet.of(), "Bulk email subject", "Bulk email body"); +- System.out.println(status); ++ try { ++ String status = mailService.sendToGroup(addressees, ImmutableSet.of(), "Bulk email subject", "Bulk email body"); ++ System.out.println(status); + +- GroupResult groupResult = mailService.sendBulk(addressees, "Individual mail subject", "Individual mail body"); +- System.out.println(groupResult); ++ GroupResult groupResult = mailService.sendBulk(addressees, "Individual mail subject", "Individual mail body"); ++ System.out.println(groupResult); ++ } catch (WebStateException e) { ++ System.out.println(e); ++ } + } + } +Index: config_templates/wsdl/mailService.wsdl +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- config_templates/wsdl/mailService.wsdl (date 1492826050000) ++++ config_templates/wsdl/mailService.wsdl (revision ) +@@ -3,11 +3,14 @@ + xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" + xmlns:tns="http://mail.javaops.ru/" + xmlns:xs="http://www.w3.org/2001/XMLSchema" ++ xmlns:common="http://common.javaops.ru/" + xmlns="http://schemas.xmlsoap.org/wsdl/" + targetNamespace="http://mail.javaops.ru/" + name="MailServiceImplService"> + + ++ ++ + + + +@@ -80,17 +83,25 @@ + + + ++ ++ ++ ++ + + + + ++ + + + + ++ + + + +@@ -103,6 +114,9 @@ + + + ++ ++ ++ + + + +@@ -112,6 +126,9 @@ + + + ++ ++ ++ + + + +Index: services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailService.java (date 1492826050000) ++++ services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailService.java (revision ) +@@ -1,5 +1,7 @@ + package ru.javaops.masterjava.service.mail; + ++import ru.javaops.web.WebStateException; ++ + import javax.jws.WebMethod; + import javax.jws.WebParam; + import javax.jws.WebService; +@@ -17,12 +19,12 @@ + @WebParam(name = "to") Set to, + @WebParam(name = "cc") Set cc, + @WebParam(name = "subject") String subject, +- @WebParam(name = "body") String body); ++ @WebParam(name = "body") String body) throws WebStateException; + + @WebMethod + GroupResult sendBulk( + @WebParam(name = "to") Set to, + @WebParam(name = "subject") String subject, +- @WebParam(name = "body") String body); ++ @WebParam(name = "body") String body) throws WebStateException; + + } +\ No newline at end of file +Index: services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServicePublisher.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServicePublisher.java (date 1492826050000) ++++ services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServicePublisher.java (revision ) +@@ -16,7 +16,8 @@ + + Endpoint endpoint = Endpoint.create(new MailServiceImpl()); + List metadata = ImmutableList.of( +- new StreamSource(Configs.getConfigFile("wsdl/mailService.wsdl"))); ++ new StreamSource(Configs.getConfigFile("wsdl/mailService.wsdl")), ++ new StreamSource(Configs.getConfigFile("wsdl/common.xsd"))); + + endpoint.setMetadata(metadata); + endpoint.publish("http://localhost:8080/mail/mailService"); +Index: services/mail-service/pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/pom.xml (date 1492826050000) ++++ services/mail-service/pom.xml (revision ) +@@ -40,6 +40,7 @@ + ${masterjava.config} + + wsdl/mailService.wsdl ++ wsdl/common.xsd + + WEB-INF + +@@ -66,11 +67,7 @@ + + + +- +- javax.activation +- activation +- 1.1.1 +- ++ + + ${project.groupId} + persist +@@ -82,44 +79,6 @@ + ${project.version} + test-jar + test +- +- +- com.sun.xml.ws +- jaxws-rt +- 2.2.10 +- +- +- org.jvnet.mimepull +- mimepull +- +- +- javax.xml.bind +- jaxb-api +- +- +- javax.annotation +- javax.annotation-api +- +- +- org.jvnet.staxex +- stax-ex +- +- +- javax.xml.soap +- javax.xml.soap-api +- +- +- +- +- org.jvnet.staxex +- stax-ex +- 1.7.8 +- +- +- javax.activation +- activation +- +- + + + +\ No newline at end of file +Index: web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java (date 1492826050000) ++++ web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java (revision ) +@@ -1,8 +1,8 @@ + package ru.javaops.masterjava.webapp; + + import lombok.extern.slf4j.Slf4j; +-import ru.javaops.masterjava.service.mail.GroupResult; + import ru.javaops.masterjava.service.mail.MailWSClient; ++import ru.javaops.web.WebStateException; + + import javax.servlet.ServletException; + import javax.servlet.annotation.WebServlet; +@@ -21,7 +21,12 @@ + String users = req.getParameter("users"); + String subject = req.getParameter("subject"); + String body = req.getParameter("body"); +- GroupResult groupResult = MailWSClient.sendBulk(MailWSClient.split(users), subject, body); +- resp.getWriter().write(groupResult.toString()); ++ String groupResult; ++ try { ++ groupResult = MailWSClient.sendBulk(MailWSClient.split(users), subject, body).toString(); ++ } catch (WebStateException e) { ++ groupResult = e.toString(); ++ } ++ resp.getWriter().write(groupResult); + } + } +Index: common/src/main/java/ru/javaops/masterjava/web/WsClient.java +=================================================================== +--- common/src/main/java/ru/javaops/masterjava/web/WsClient.java (date 1492826050000) ++++ services/common-ws/src/main/java/ru/javaops/web/WsClient.java (revision ) +@@ -1,6 +1,7 @@ +-package ru.javaops.masterjava.web; ++package ru.javaops.web; + + import com.typesafe.config.Config; ++import ru.javaops.masterjava.ExceptionType; + import ru.javaops.masterjava.config.Configs; + + import javax.xml.namespace.QName; +@@ -37,4 +38,9 @@ + requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointAddress); + return port; + } ++ ++ public static WebStateException getWebStateException(Exception e) { ++ return (e instanceof WebStateException) ? ++ (WebStateException) e : new WebStateException(ExceptionType.NETWORK, e); ++ } + } +Index: services/mail-api/src/test/java/ru/javaops/masterjava/service/mail/MailWSClientMain.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/test/java/ru/javaops/masterjava/service/mail/MailWSClientMain.java (date 1492826050000) ++++ services/mail-api/src/test/java/ru/javaops/masterjava/service/mail/MailWSClientMain.java (revision ) +@@ -1,11 +1,20 @@ + package ru.javaops.masterjava.service.mail; + + import com.google.common.collect.ImmutableSet; ++import lombok.extern.slf4j.Slf4j; + ++@Slf4j + public class MailWSClientMain { + public static void main(String[] args) { +- MailWSClient.sendToGroup( +- ImmutableSet.of(new Addressee("Григорий Кислин ")), +- ImmutableSet.of(new Addressee("Мастер Java ")), "Subject", "Body"); ++ ImmutableSet addressees = ImmutableSet.of( ++ new Addressee("gkislin@javaops.ru"), ++ new Addressee("Мастер Java "), ++ new Addressee("Bad Email ")); ++ try { ++ String state = MailWSClient.sendToGroup(addressees, ImmutableSet.of(), "Subject", "Body"); ++ System.out.println(state); ++ } catch (Throwable e) { ++ log.error(e.toString(), e); ++ } + } + } +\ No newline at end of file +Index: services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (date 1492826050000) ++++ services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (revision ) +@@ -5,7 +5,8 @@ + import com.google.common.collect.Iterables; + import com.google.common.io.Resources; + import lombok.extern.slf4j.Slf4j; +-import ru.javaops.masterjava.web.WsClient; ++import ru.javaops.web.WebStateException; ++import ru.javaops.web.WsClient; + + import javax.xml.namespace.QName; + import java.util.Set; +@@ -23,7 +24,7 @@ + } + + +- public static String sendToGroup(final Set to, final Set cc, final String subject, final String body) { ++ public static String sendToGroup(final Set to, final Set cc, final String subject, final String body) throws WebStateException { + log.info("Send mail to '" + to + "' cc '" + cc + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); + String status; + try { +@@ -31,19 +32,19 @@ + log.info("Sent with status: " + status); + } catch (Exception e) { + log.error("sendToGroup failed", e); +- status = e.toString(); ++ throw WsClient.getWebStateException(e); + } + return status; + } + +- public static GroupResult sendBulk(final Set to, final String subject, final String body) { ++ public static GroupResult sendBulk(final Set to, final String subject, final String body) throws WebStateException { + log.info("Send mail to '" + to + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); + GroupResult result; + try { + result = WS_CLIENT.getPort().sendBulk(to, subject, body); +- } catch (Exception e) { +- log.error("sendIndividualMails failed", e); +- result = new GroupResult(e); ++ } catch (WebStateException e) { ++ log.error("sendBulk failed", e); ++ throw WsClient.getWebStateException(e); + } + log.info("Sent with result: " + result); + return result; +Index: services/pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/pom.xml (date 1492826050000) ++++ services/pom.xml (revision ) +@@ -9,6 +9,7 @@ + + MasterJava Services + ++ common-ws + mail-api + mail-service + diff --git a/patches/8/patch/8_6_fix_wsdl_and_schema.patch b/patches/8/patch/8_6_fix_wsdl_and_schema.patch new file mode 100644 index 000000000..dc95b543e --- /dev/null +++ b/patches/8/patch/8_6_fix_wsdl_and_schema.patch @@ -0,0 +1,92 @@ +Index: services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Addressee.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Addressee.java (date 1492820861000) ++++ services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Addressee.java (revision ) +@@ -1,14 +1,22 @@ + package ru.javaops.masterjava.service.mail; + + import lombok.AllArgsConstructor; +-import lombok.Data; ++import lombok.Getter; + import lombok.NoArgsConstructor; + +-@Data ++import javax.xml.bind.annotation.XmlAccessType; ++import javax.xml.bind.annotation.XmlAccessorType; ++import javax.xml.bind.annotation.XmlAttribute; ++import javax.xml.bind.annotation.XmlValue; ++ ++@Getter + @AllArgsConstructor + @NoArgsConstructor ++@XmlAccessorType(XmlAccessType.FIELD) + public class Addressee { ++ @XmlAttribute + private String email; ++ @XmlValue + private String name; + + public Addressee(String email) { +Index: common/src/main/java/ru/javaops/masterjava/ExceptionType.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- common/src/main/java/ru/javaops/masterjava/ExceptionType.java (date 1492820861000) ++++ common/src/main/java/ru/javaops/masterjava/ExceptionType.java (revision ) +@@ -1,5 +1,8 @@ + package ru.javaops.masterjava; + ++import javax.xml.bind.annotation.XmlType; ++ ++@XmlType(namespace = "http://common.javaops.ru/") + public enum ExceptionType { + SYSTEM("Системная ошибка"), + DATA_BASE("Ошибка базы данных"), +Index: config_templates/wsdl/common.xsd +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- config_templates/wsdl/common.xsd (date 1492820861000) ++++ config_templates/wsdl/common.xsd (revision ) +@@ -1,12 +1,14 @@ + ++ + + +- ++ + + + +- ++ + + + +Index: services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java (date 1492820861000) ++++ services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java (revision ) +@@ -6,9 +6,12 @@ + import lombok.RequiredArgsConstructor; + import ru.javaops.masterjava.ExceptionType; + ++import javax.xml.bind.annotation.XmlType; ++ + @Data + @RequiredArgsConstructor + @NoArgsConstructor ++@XmlType(namespace = "http://common.javaops.ru/") + public class FaultInfo { + private @NonNull ExceptionType type; + diff --git a/patches/9/patch-20170506T181626Z-001.zip b/patches/9/patch-20170506T181626Z-001.zip new file mode 100644 index 000000000..4130313f9 Binary files /dev/null and b/patches/9/patch-20170506T181626Z-001.zip differ diff --git a/patches/9/patch/9_0_mvn_plugins.patch b/patches/9/patch/9_0_mvn_plugins.patch new file mode 100644 index 000000000..35538eade --- /dev/null +++ b/patches/9/patch/9_0_mvn_plugins.patch @@ -0,0 +1,110 @@ +Index: persist/pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- persist/pom.xml (revision 9aba595804472679d75fe4f3ce2016e9ecec46ed) ++++ persist/pom.xml (revision c744f3ea11c0a6d871ad0ee3629dcfabdf1613f9) +@@ -31,6 +31,30 @@ + + + ++ ++ ++ ++ org.liquibase ++ liquibase-maven-plugin ++ 3.5.3 ++ ++ ../sql/databaseChangeLog.sql ++ org.postgresql.Driver ++ jdbc:postgresql://localhost:5432/masterjava ++ user ++ password ++ ++ ++ ++ ++ ++ process-resources ++ ++ update ++ ++ ++ ++ + + + +Index: services/mail-service/pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/pom.xml (revision 9aba595804472679d75fe4f3ce2016e9ecec46ed) ++++ services/mail-service/pom.xml (revision c744f3ea11c0a6d871ad0ee3629dcfabdf1613f9) +@@ -21,31 +21,24 @@ + + + org.apache.maven.plugins +- maven-war-plugin +- 3.0.0 +- +- false +- +- +- src/main/webapp +- +- +- ${masterjava.config} +- +- version.html +- +- true +- +- +- ${masterjava.config} +- +- wsdl/mailService.wsdl +- wsdl/common.xsd +- +- WEB-INF +- +- +- ++ maven-antrun-plugin ++ 1.8 ++ ++ ++ prepare-package ++ ++ run ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + + + +Index: config_templates/sql/databaseChangeLog.sql +=================================================================== +--- config_templates/sql/databaseChangeLog.sql (revision 9aba595804472679d75fe4f3ce2016e9ecec46ed) ++++ sql/databaseChangeLog.sql (revision c744f3ea11c0a6d871ad0ee3629dcfabdf1613f9) +@@ -1,0 +1,0 @@ +Index: config_templates/sql/initDB.sql +=================================================================== +--- config_templates/sql/initDB.sql (revision 9aba595804472679d75fe4f3ce2016e9ecec46ed) ++++ sql/initDB.sql (revision c744f3ea11c0a6d871ad0ee3629dcfabdf1613f9) +@@ -1,0 +1,0 @@ +Index: config_templates/sql/lb_apply.bat +=================================================================== +--- config_templates/sql/lb_apply.bat (revision 9aba595804472679d75fe4f3ce2016e9ecec46ed) ++++ sql/lb_apply.bat (revision c744f3ea11c0a6d871ad0ee3629dcfabdf1613f9) +@@ -1,0 +1,0 @@ diff --git a/patches/9/patch/9_1_HW8_service_attach.patch b/patches/9/patch/9_1_HW8_service_attach.patch new file mode 100644 index 000000000..86b245bfe --- /dev/null +++ b/patches/9/patch/9_1_HW8_service_attach.patch @@ -0,0 +1,278 @@ +Index: services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Attach.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Attach.java (revision ) ++++ services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Attach.java (revision ) +@@ -0,0 +1,22 @@ ++package ru.javaops.masterjava.service.mail; ++ ++import lombok.AllArgsConstructor; ++import lombok.Getter; ++import lombok.NoArgsConstructor; ++ ++import javax.activation.DataHandler; ++import javax.xml.bind.annotation.XmlAccessType; ++import javax.xml.bind.annotation.XmlAccessorType; ++import javax.xml.bind.annotation.XmlMimeType; ++ ++@Getter ++@AllArgsConstructor ++@NoArgsConstructor ++@XmlAccessorType(XmlAccessType.FIELD) ++public class Attach { ++ // http://stackoverflow.com/questions/12250423/jax-ws-datahandler-getname-is-blank-when-called-from-client-side ++ protected String name; ++ ++ @XmlMimeType("application/octet-stream") ++ private DataHandler dataHandler; ++} +Index: services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java (date 1493398337000) ++++ services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java (revision ) +@@ -3,6 +3,7 @@ + import ru.javaops.web.WebStateException; + + import javax.jws.WebService; ++import java.util.List; + import java.util.Set; + + @WebService(endpointInterface = "ru.javaops.masterjava.service.mail.MailService", targetNamespace = "http://mail.javaops.ru/" +@@ -11,12 +12,12 @@ + public class MailServiceImpl implements MailService { + + @Override +- public String sendToGroup(Set to, Set cc, String subject, String body) throws WebStateException { ++ public String sendToGroup(Set to, Set cc, String subject, String body, List attaches) throws WebStateException { + return MailSender.sendToGroup(to, cc, subject, body); + } + + @Override +- public GroupResult sendBulk(Set to, String subject, String body) throws WebStateException { ++ public GroupResult sendBulk(Set to, String subject, String body, List attaches) throws WebStateException { + return MailServiceExecutor.sendBulk(to, subject, body); + } + } +\ No newline at end of file +Index: services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java (date 1493398337000) ++++ services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java (revision ) +@@ -1,12 +1,16 @@ + package ru.javaops.masterjava.service.mail; + ++import com.google.common.collect.ImmutableList; + import com.google.common.collect.ImmutableSet; + import ru.javaops.web.WebStateException; + ++import javax.activation.DataHandler; + import javax.xml.namespace.QName; + import javax.xml.ws.Service; ++import java.io.File; + import java.net.MalformedURLException; + import java.net.URL; ++import java.util.List; + + public class MailServiceClient { + +@@ -18,15 +22,16 @@ + MailService mailService = service.getPort(MailService.class); + + ImmutableSet addressees = ImmutableSet.of( +- new Addressee("gkislin@javaops.ru"), +- new Addressee("Мастер Java "), +- new Addressee("Bad Email ")); ++ new Addressee("Мастер Java ")); ++ ++ List attaches = ImmutableList.of( ++ new Attach("version.html", new DataHandler(new File("config_templates/version.html").toURI().toURL()))); + + try { +- String status = mailService.sendToGroup(addressees, ImmutableSet.of(), "Bulk email subject", "Bulk email body"); ++ String status = mailService.sendToGroup(addressees, ImmutableSet.of(), "Bulk email subject", "Bulk email body", attaches); + System.out.println(status); + +- GroupResult groupResult = mailService.sendBulk(addressees, "Individual mail subject", "Individual mail body"); ++ GroupResult groupResult = mailService.sendBulk(addressees, "Individual mail subject", "Individual mail body", attaches); + System.out.println(groupResult); + } catch (WebStateException e) { + System.out.println(e); +Index: config_templates/wsdl/mailService.wsdl +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- config_templates/wsdl/mailService.wsdl (date 1493398337000) ++++ config_templates/wsdl/mailService.wsdl (revision ) +@@ -4,6 +4,7 @@ + xmlns:tns="http://mail.javaops.ru/" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:common="http://common.javaops.ru/" ++ xmlns:xmime="http://www.w3.org/2005/05/xmlmime" + xmlns="http://schemas.xmlsoap.org/wsdl/" + targetNamespace="http://mail.javaops.ru/" + name="MailServiceImplService"> +@@ -22,6 +23,7 @@ + + + ++ + + + +@@ -35,6 +37,7 @@ + + + ++ + + + +@@ -69,6 +72,13 @@ + + + ++ ++ ++ ++ ++ ++ ++ + + + +Index: services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailService.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailService.java (date 1493398337000) ++++ services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailService.java (revision ) +@@ -5,6 +5,7 @@ + import javax.jws.WebMethod; + import javax.jws.WebParam; + import javax.jws.WebService; ++import java.util.List; + import java.util.Set; + + @WebService(targetNamespace = "http://mail.javaops.ru/") +@@ -19,12 +20,14 @@ + @WebParam(name = "to") Set to, + @WebParam(name = "cc") Set cc, + @WebParam(name = "subject") String subject, +- @WebParam(name = "body") String body) throws WebStateException; ++ @WebParam(name = "body") String body, ++ @WebParam(name = "attaches") List attaches) throws WebStateException; + + @WebMethod + GroupResult sendBulk( + @WebParam(name = "to") Set to, + @WebParam(name = "subject") String subject, +- @WebParam(name = "body") String body) throws WebStateException; ++ @WebParam(name = "body") String body, ++ @WebParam(name = "attaches") List attaches) throws WebStateException; + + } +\ No newline at end of file +Index: services/mail-api/src/test/java/ru/javaops/masterjava/service/mail/MailWSClientMain.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/test/java/ru/javaops/masterjava/service/mail/MailWSClientMain.java (date 1493398337000) ++++ services/mail-api/src/test/java/ru/javaops/masterjava/service/mail/MailWSClientMain.java (revision ) +@@ -1,17 +1,22 @@ + package ru.javaops.masterjava.service.mail; + ++import com.google.common.collect.ImmutableList; + import com.google.common.collect.ImmutableSet; + import lombok.extern.slf4j.Slf4j; + ++import javax.activation.DataHandler; ++import java.io.File; ++ + @Slf4j + public class MailWSClientMain { + public static void main(String[] args) { + ImmutableSet addressees = ImmutableSet.of( +- new Addressee("gkislin@javaops.ru"), +- new Addressee("Мастер Java "), +- new Addressee("Bad Email ")); ++ new Addressee("Мастер Java ")); ++ + try { +- String state = MailWSClient.sendToGroup(addressees, ImmutableSet.of(), "Subject", "Body"); ++ String state = MailWSClient.sendToGroup(addressees, ImmutableSet.of(), "Subject", "Body", ImmutableList.of( ++ new Attach("version.html", new DataHandler(new File("config_templates/version.html").toURI().toURL())) ++ )); + System.out.println(state); + } catch (Throwable e) { + log.error(e.toString(), e); +Index: services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (date 1493398337000) ++++ services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (revision ) +@@ -9,6 +9,7 @@ + import ru.javaops.web.WsClient; + + import javax.xml.namespace.QName; ++import java.util.List; + import java.util.Set; + + @Slf4j +@@ -24,11 +25,11 @@ + } + + +- public static String sendToGroup(final Set to, final Set cc, final String subject, final String body) throws WebStateException { ++ public static String sendToGroup(final Set to, final Set cc, final String subject, final String body, List attaches) throws WebStateException { + log.info("Send mail to '" + to + "' cc '" + cc + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); + String status; + try { +- status = WS_CLIENT.getPort().sendToGroup(to, cc, subject, body); ++ status = WS_CLIENT.getPort().sendToGroup(to, cc, subject, body, attaches); + log.info("Sent with status: " + status); + } catch (Exception e) { + log.error("sendToGroup failed", e); +@@ -37,11 +38,11 @@ + return status; + } + +- public static GroupResult sendBulk(final Set to, final String subject, final String body) throws WebStateException { ++ public static GroupResult sendBulk(final Set to, final String subject, final String body, List attaches) throws WebStateException { + log.info("Send mail to '" + to + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); + GroupResult result; + try { +- result = WS_CLIENT.getPort().sendBulk(to, subject, body); ++ result = WS_CLIENT.getPort().sendBulk(to, subject, body, attaches); + } catch (WebStateException e) { + log.error("sendBulk failed", e); + throw WsClient.getWebStateException(e); +Index: web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java (date 1493398337000) ++++ web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java (revision ) +@@ -23,7 +23,7 @@ + String body = req.getParameter("body"); + String groupResult; + try { +- groupResult = MailWSClient.sendBulk(MailWSClient.split(users), subject, body).toString(); ++ groupResult = MailWSClient.sendBulk(MailWSClient.split(users), subject, body, null).toString(); + } catch (WebStateException e) { + groupResult = e.toString(); + } diff --git a/patches/9/patch/9_2_HW8_MTOM.patch b/patches/9/patch/9_2_HW8_MTOM.patch new file mode 100644 index 000000000..df4100583 --- /dev/null +++ b/patches/9/patch/9_2_HW8_MTOM.patch @@ -0,0 +1,106 @@ +Index: services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java (date 1493406326000) ++++ services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java (revision ) +@@ -9,6 +9,8 @@ + @WebService(endpointInterface = "ru.javaops.masterjava.service.mail.MailService", targetNamespace = "http://mail.javaops.ru/" + // , wsdlLocation = "WEB-INF/wsdl/mailService.wsdl" + ) ++//@StreamingAttachment(parseEagerly=true, memoryThreshold=40000L) ++//@MTOM + public class MailServiceImpl implements MailService { + + @Override +Index: services/common-ws/pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/pom.xml (date 1493406326000) ++++ services/common-ws/pom.xml (revision ) +@@ -50,6 +50,12 @@ + + + ++ ++ org.jvnet.mimepull ++ mimepull ++ 1.9.4 ++ ++ + + javax.activation + activation +Index: services/common-ws/src/main/java/ru/javaops/web/WsClient.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/WsClient.java (date 1493406326000) ++++ services/common-ws/src/main/java/ru/javaops/web/WsClient.java (revision ) +@@ -7,6 +7,7 @@ + import javax.xml.namespace.QName; + import javax.xml.ws.BindingProvider; + import javax.xml.ws.Service; ++import javax.xml.ws.WebServiceFeature; + import java.net.URL; + import java.util.Map; + +@@ -31,8 +32,8 @@ + } + + // Post is not thread-safe (http://stackoverflow.com/a/10601916/548473) +- public T getPort() { +- T port = service.getPort(serviceClass); ++ public T getPort(WebServiceFeature... features) { ++ T port = service.getPort(serviceClass, features); + BindingProvider bp = (BindingProvider) port; + Map requestContext = bp.getRequestContext(); + requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointAddress); +Index: services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (date 1493406326000) ++++ services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (revision ) +@@ -9,6 +9,7 @@ + import ru.javaops.web.WsClient; + + import javax.xml.namespace.QName; ++import javax.xml.ws.soap.MTOMFeature; + import java.util.List; + import java.util.Set; + +@@ -29,7 +30,7 @@ + log.info("Send mail to '" + to + "' cc '" + cc + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); + String status; + try { +- status = WS_CLIENT.getPort().sendToGroup(to, cc, subject, body, attaches); ++ status = getPort().sendToGroup(to, cc, subject, body, attaches); + log.info("Sent with status: " + status); + } catch (Exception e) { + log.error("sendToGroup failed", e); +@@ -42,7 +43,7 @@ + log.info("Send mail to '" + to + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); + GroupResult result; + try { +- result = WS_CLIENT.getPort().sendBulk(to, subject, body, attaches); ++ result = getPort().sendBulk(to, subject, body, attaches); + } catch (WebStateException e) { + log.error("sendBulk failed", e); + throw WsClient.getWebStateException(e); +@@ -51,6 +52,10 @@ + return result; + } + ++ private static MailService getPort() { ++ return WS_CLIENT.getPort(new MTOMFeature(1024)); ++ } ++ + public static Set split(String addressees) { + Iterable split = Splitter.on(',').trimResults().omitEmptyStrings().split(addressees); + return ImmutableSet.copyOf(Iterables.transform(split, Addressee::new)); diff --git a/patches/9/patch/9_3_HW8_webapp_attach.patch b/patches/9/patch/9_3_HW8_webapp_attach.patch new file mode 100644 index 000000000..2ec1346ed --- /dev/null +++ b/patches/9/patch/9_3_HW8_webapp_attach.patch @@ -0,0 +1,174 @@ +Index: services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java (revision ) ++++ services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java (revision ) +@@ -0,0 +1,47 @@ ++package ru.javaops.masterjava.service.mail.util; ++ ++import lombok.AllArgsConstructor; ++import ru.javaops.masterjava.service.mail.Attach; ++ ++import javax.activation.DataHandler; ++import javax.activation.DataSource; ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.OutputStream; ++ ++public class Attachments { ++ public static Attach getAttach(String name, InputStream inputStream) { ++ return new Attach(name, new DataHandler(new InputStreamDataSource(inputStream))); ++ } ++ ++ // http://stackoverflow.com/a/10783565/548473 ++ @AllArgsConstructor ++ private static class InputStreamDataSource implements DataSource { ++ private InputStream inputStream; ++ ++ @Override ++ public InputStream getInputStream() throws IOException { ++ if (inputStream == null) { ++ throw new IOException("Second getInputStream() call is not supported"); ++ } ++ InputStream res = inputStream; ++ inputStream = null; ++ return res; ++ } ++ ++ @Override ++ public OutputStream getOutputStream() throws IOException { ++ throw new UnsupportedOperationException("Not implemented"); ++ } ++ ++ @Override ++ public String getContentType() { ++ return "application/octet-stream"; ++ } ++ ++ @Override ++ public String getName() { ++ return ""; ++ } ++ } ++} +Index: web/webapp/src/main/webapp/WEB-INF/templates/users.html +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- web/webapp/src/main/webapp/WEB-INF/templates/users.html (date 1493408188000) ++++ web/webapp/src/main/webapp/WEB-INF/templates/users.html (revision ) +@@ -31,36 +31,37 @@ + + + +- ++ + + + +
+-

+- +-

+-

+-
+-

+-

+- +-

+-
++
++ ++ ++ ++

++ ++

++

++
++

++

++ ++

++

++ ++

++
+ + + +Index: web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java (date 1493408188000) ++++ web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java (revision ) +@@ -1,29 +1,44 @@ + package ru.javaops.masterjava.webapp; + ++import com.google.common.collect.ImmutableList; + import lombok.extern.slf4j.Slf4j; ++import ru.javaops.masterjava.service.mail.Attach; + import ru.javaops.masterjava.service.mail.MailWSClient; ++import ru.javaops.masterjava.service.mail.util.Attachments; + import ru.javaops.web.WebStateException; + + 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.util.List; + + @WebServlet("/send") + @Slf4j ++@MultipartConfig + public class SendServlet extends HttpServlet { + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + req.setCharacterEncoding("UTF-8"); + resp.setCharacterEncoding("UTF-8"); ++ + String users = req.getParameter("users"); + String subject = req.getParameter("subject"); + String body = req.getParameter("body"); ++ List attaches; ++ Part filePart = req.getPart("attach"); ++ if (filePart == null) { ++ attaches = ImmutableList.of(); ++ } else { ++ attaches = ImmutableList.of(Attachments.getAttach(filePart.getName(), filePart.getInputStream())); ++ } + String groupResult; + try { +- groupResult = MailWSClient.sendBulk(MailWSClient.split(users), subject, body, null).toString(); ++ groupResult = MailWSClient.sendBulk(MailWSClient.split(users), subject, body, attaches).toString(); + } catch (WebStateException e) { + groupResult = e.toString(); + } diff --git a/patches/9/patch/9_4_HW8_mail_attach.patch b/patches/9/patch/9_4_HW8_mail_attach.patch new file mode 100644 index 000000000..683558d11 --- /dev/null +++ b/patches/9/patch/9_4_HW8_mail_attach.patch @@ -0,0 +1,115 @@ +Index: services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceExecutor.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceExecutor.java (date 1493414685000) ++++ services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceExecutor.java (revision ) +@@ -17,11 +17,11 @@ + + private static final ExecutorService mailExecutor = Executors.newFixedThreadPool(8); + +- public static GroupResult sendBulk(final Set addressees, final String subject, final String body) { ++ public static GroupResult sendBulk(final Set addressees, final String subject, final String body, List attaches) { + final CompletionService completionService = new ExecutorCompletionService<>(mailExecutor); + + List> futures = StreamEx.of(addressees) +- .map(addressee -> completionService.submit(() -> MailSender.sendTo(addressee, subject, body))) ++ .map(addressee -> completionService.submit(() -> MailSender.sendTo(addressee, subject, body, attaches))) + .toList(); + + return new Callable() { +Index: services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java (date 1493414685000) ++++ services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java (revision ) +@@ -15,11 +15,11 @@ + + @Override + public String sendToGroup(Set to, Set cc, String subject, String body, List attaches) throws WebStateException { +- return MailSender.sendToGroup(to, cc, subject, body); ++ return MailSender.sendToGroup(to, cc, subject, body, attaches); + } + + @Override + public GroupResult sendBulk(Set to, String subject, String body, List attaches) throws WebStateException { +- return MailServiceExecutor.sendBulk(to, subject, body); ++ return MailServiceExecutor.sendBulk(to, subject, body, attaches); + } + } +\ No newline at end of file +Index: services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailSender.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailSender.java (date 1493414685000) ++++ services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailSender.java (revision ) +@@ -4,29 +4,28 @@ + import com.google.common.collect.ImmutableSet; + import lombok.extern.slf4j.Slf4j; + import lombok.val; +-import org.apache.commons.mail.EmailException; + import ru.javaops.masterjava.ExceptionType; + import ru.javaops.masterjava.persist.DBIProvider; + import ru.javaops.masterjava.service.mail.persist.MailCase; + import ru.javaops.masterjava.service.mail.persist.MailCaseDao; + import ru.javaops.web.WebStateException; + ++import javax.mail.internet.MimeUtility; ++import java.io.UnsupportedEncodingException; ++import java.nio.charset.StandardCharsets; ++import java.util.List; + import java.util.Set; + +-/** +- * gkislin +- * 15.11.2016 +- */ + @Slf4j + public class MailSender { + private static final MailCaseDao MAIL_CASE_DAO = DBIProvider.getDao(MailCaseDao.class); + +- static MailResult sendTo(Addressee to, String subject, String body) throws WebStateException { +- val state = sendToGroup(ImmutableSet.of(to), ImmutableSet.of(), subject, body); ++ static MailResult sendTo(Addressee to, String subject, String body, List attaches) throws WebStateException { ++ val state = sendToGroup(ImmutableSet.of(to), ImmutableSet.of(), subject, body, attaches); + return new MailResult(to.getEmail(), state); + } + +- static String sendToGroup(Set to, Set cc, String subject, String body) throws WebStateException { ++ static String sendToGroup(Set to, Set cc, String subject, String body, List attaches) throws WebStateException { + log.info("Send mail to \'" + to + "\' cc \'" + cc + "\' subject \'" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); + String state = MailResult.OK; + try { +@@ -39,11 +38,14 @@ + for (Addressee addressee : cc) { + email.addCc(addressee.getEmail(), addressee.getName()); + } ++ for (Attach attach : attaches) { ++ email.attach(attach.getDataHandler().getDataSource(), encodeWord(attach.getName()), null); ++ } + + // https://yandex.ru/blog/company/66296 + email.setHeaders(ImmutableMap.of("List-Unsubscribe", "")); + email.send(); +- } catch (EmailException e) { ++ } catch (Exception e) { + log.error(e.getMessage(), e); + state = e.getMessage(); + } +@@ -56,4 +58,11 @@ + log.info("Sent with state: " + state); + return state; + } ++ ++ public static String encodeWord(String word) throws UnsupportedEncodingException { ++ if (word == null) { ++ return null; ++ } ++ return MimeUtility.encodeWord(word, StandardCharsets.UTF_8.name(), null); ++ } + } diff --git a/patches/9/patch/9_5_msg_ctx_auth.patch b/patches/9/patch/9_5_msg_ctx_auth.patch new file mode 100644 index 000000000..be4c33b98 --- /dev/null +++ b/patches/9/patch/9_5_msg_ctx_auth.patch @@ -0,0 +1,142 @@ +Index: services/common-ws/src/main/java/ru/javaops/web/AuthUtil.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/AuthUtil.java (revision ) ++++ services/common-ws/src/main/java/ru/javaops/web/AuthUtil.java (revision ) +@@ -0,0 +1,32 @@ ++package ru.javaops.web; ++ ++import lombok.extern.slf4j.Slf4j; ++ ++import javax.servlet.http.HttpServletResponse; ++import javax.xml.bind.DatatypeConverter; ++import java.util.List; ++import java.util.Map; ++ ++@Slf4j ++public class AuthUtil { ++ private static final String AUTHORIZATION = "Authorization"; ++ ++ public static String encodeBasicAuthHeader(String name, String passw) { ++ String authString = name + ":" + passw; ++ return "Basic " + DatatypeConverter.printBase64Binary(authString.getBytes()); ++ } ++ ++ public static int checkBasicAuth(Map> headers, String basicAuthCredentials) { ++ List autHeaders = headers.get(AUTHORIZATION); ++ if ((autHeaders == null || autHeaders.isEmpty())) { ++ log.warn("Unauthorized access"); ++ return HttpServletResponse.SC_UNAUTHORIZED; ++ } else { ++ if (!autHeaders.get(0).equals(basicAuthCredentials)) { ++ log.warn("Wrong password access"); ++ return HttpServletResponse.SC_FORBIDDEN; ++ } ++ return 0; ++ } ++ } ++} +Index: services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java (date 1493415590000) ++++ services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java (revision ) +@@ -1,9 +1,14 @@ + package ru.javaops.masterjava.service.mail; + ++import ru.javaops.web.AuthUtil; + import ru.javaops.web.WebStateException; + ++import javax.annotation.Resource; + import javax.jws.WebService; ++import javax.xml.ws.WebServiceContext; ++import javax.xml.ws.handler.MessageContext; + import java.util.List; ++import java.util.Map; + import java.util.Set; + + @WebService(endpointInterface = "ru.javaops.masterjava.service.mail.MailService", targetNamespace = "http://mail.javaops.ru/" +@@ -13,8 +18,22 @@ + //@MTOM + public class MailServiceImpl implements MailService { + ++ @Resource ++ private WebServiceContext wsContext; ++ + @Override + public String sendToGroup(Set to, Set cc, String subject, String body, List attaches) throws WebStateException { ++ MessageContext mCtx = wsContext.getMessageContext(); ++ Map> headers = (Map>) mCtx.get(MessageContext.HTTP_REQUEST_HEADERS); ++ ++// HttpServletRequest request = (HttpServletRequest) mCtx.get(MessageContext.SERVLET_REQUEST); ++// HttpServletResponse response = (HttpServletResponse) mCtx.get(MessageContext.SERVLET_RESPONSE); ++ ++ int code = AuthUtil.checkBasicAuth(headers, MailWSClient.AUTH_HEADER); ++ if (code != 0) { ++ mCtx.put(MessageContext.HTTP_RESPONSE_CODE, code); ++ throw new SecurityException(); ++ } + return MailSender.sendToGroup(to, cc, subject, body, attaches); + } + +Index: services/common-ws/src/main/java/ru/javaops/web/WsClient.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/WsClient.java (date 1493415590000) ++++ services/common-ws/src/main/java/ru/javaops/web/WsClient.java (revision ) +@@ -40,6 +40,12 @@ + return port; + } + ++ public static void setAuth(T port, String user, String password) { ++ Map requestContext = ((BindingProvider) port).getRequestContext(); ++ requestContext.put(BindingProvider.USERNAME_PROPERTY, user); ++ requestContext.put(BindingProvider.PASSWORD_PROPERTY, password); ++ } ++ + public static WebStateException getWebStateException(Exception e) { + return (e instanceof WebStateException) ? + (WebStateException) e : new WebStateException(ExceptionType.NETWORK, e); +Index: services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (date 1493415590000) ++++ services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (revision ) +@@ -5,6 +5,7 @@ + import com.google.common.collect.Iterables; + import com.google.common.io.Resources; + import lombok.extern.slf4j.Slf4j; ++import ru.javaops.web.AuthUtil; + import ru.javaops.web.WebStateException; + import ru.javaops.web.WsClient; + +@@ -16,6 +17,10 @@ + @Slf4j + public class MailWSClient { + private static final WsClient WS_CLIENT; ++ public static final String USER = "user"; ++ public static final String PASSWORD = "password"; ++ ++ public static String AUTH_HEADER = AuthUtil.encodeBasicAuthHeader(USER, PASSWORD); + + static { + WS_CLIENT = new WsClient(Resources.getResource("wsdl/mailService.wsdl"), +@@ -53,7 +58,9 @@ + } + + private static MailService getPort() { +- return WS_CLIENT.getPort(new MTOMFeature(1024)); ++ MailService port = WS_CLIENT.getPort(new MTOMFeature(1024)); ++ WsClient.setAuth(port, USER, PASSWORD); ++ return port; + } + + public static Set split(String addressees) { diff --git a/patches/9/patch/9_6_logging_handlers.patch b/patches/9/patch/9_6_logging_handlers.patch new file mode 100644 index 000000000..32bef70f3 --- /dev/null +++ b/patches/9/patch/9_6_logging_handlers.patch @@ -0,0 +1,377 @@ +Index: services/common-ws/src/main/java/ru/javaops/web/handler/SoapBaseHandler.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/handler/SoapBaseHandler.java (revision ) ++++ services/common-ws/src/main/java/ru/javaops/web/handler/SoapBaseHandler.java (revision ) +@@ -0,0 +1,23 @@ ++package ru.javaops.web.handler; ++ ++import com.sun.xml.ws.api.handler.MessageHandler; ++import com.sun.xml.ws.api.handler.MessageHandlerContext; ++ ++import javax.xml.namespace.QName; ++import javax.xml.ws.handler.MessageContext; ++import java.util.Set; ++ ++public abstract class SoapBaseHandler implements MessageHandler { ++ ++ public Set getHeaders() { ++ return null; ++ } ++ ++ @Override ++ public void close(MessageContext context) { ++ } ++ ++ protected static boolean isOutbound(MessageHandlerContext context) { ++ return (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); ++ } ++} +Index: services/common-ws/src/main/java/ru/javaops/web/handler/SoapLoggingHandler.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/handler/SoapLoggingHandler.java (revision ) ++++ services/common-ws/src/main/java/ru/javaops/web/handler/SoapLoggingHandler.java (revision ) +@@ -0,0 +1,120 @@ ++package ru.javaops.web.handler; ++ ++ ++import com.sun.xml.txw2.output.IndentingXMLStreamWriter; ++import com.sun.xml.ws.api.handler.MessageHandlerContext; ++import com.sun.xml.ws.api.message.Message; ++import com.sun.xml.ws.api.streaming.XMLStreamWriterFactory; ++import lombok.extern.slf4j.Slf4j; ++import org.slf4j.event.Level; ++ ++import javax.xml.stream.XMLStreamWriter; ++import java.io.ByteArrayOutputStream; ++import java.nio.charset.StandardCharsets; ++import java.util.EnumMap; ++import java.util.Map; ++ ++/** ++ * Refactored from: ++ * ++ * @see {http://weblogs.java.net/blog/ramapulavarthi/archive/2007/12/extend_your_web.html ++ * http://fisheye5.cenqua.com/browse/jax-ws-sources/jaxws-ri/samples/efficient_handler/src/efficient_handler/common/LoggingHandler.java?r=MAIN} ++ *

++ * This simple LoggingHandler will log the contents of incoming ++ * and outgoing messages. This is implemented as a MessageHandler ++ * for better performance over SOAPHandler. ++ */ ++@Slf4j ++public abstract class SoapLoggingHandler extends SoapBaseHandler { ++ ++ private final Level loggingLevel; ++ ++ protected SoapLoggingHandler(Level loggingLevel) { ++ this.loggingLevel = loggingLevel; ++ } ++ ++ private static final Map HANDLER_MAP = new EnumMap(Level.class) { ++ { ++ put(Level.TRACE, HANDLER.DEBUG); ++ put(Level.DEBUG, HANDLER.DEBUG); ++ put(Level.INFO, HANDLER.INFO); ++ put(Level.WARN, HANDLER.ERROR); ++ put(Level.ERROR, HANDLER.ERROR); ++ } ++ }; ++ ++ protected enum HANDLER { ++ NONE { ++ @Override ++ public void handleFault(MessageHandlerContext mhc) { ++ } ++ ++ @Override ++ public void handleMessage(MessageHandlerContext mhc, boolean isRequest) { ++ } ++ }, ++ ERROR { ++ private static final String REQUEST_MSG = "REQUEST_MSG"; ++ ++ public void handleFault(MessageHandlerContext context) { ++ log.error("Fault SOAP request:\n" + getMessageText(((Message) context.get(REQUEST_MSG)))); ++ } ++ ++ public void handleMessage(MessageHandlerContext context, boolean isRequest) { ++ if (isRequest) { ++ context.put(REQUEST_MSG, context.getMessage().copy()); ++ } ++ } ++ }, ++ INFO { ++ public void handleFault(MessageHandlerContext context) { ++ ERROR.handleFault(context); ++ } ++ ++ public void handleMessage(MessageHandlerContext context, boolean isRequest) { ++ ERROR.handleMessage(context, isRequest); ++ log.info((isRequest ? "SOAP request: " : "SOAP response: ") + context.getMessage().getPayloadLocalPart()); ++ } ++ }, ++ DEBUG { ++ public void handleFault(MessageHandlerContext context) { ++ log.error("Fault SOAP message:\n" + getMessageText(context.getMessage().copy())); ++ } ++ ++ public void handleMessage(MessageHandlerContext context, boolean isRequest) { ++ log.info((isRequest ? "SOAP request:\n" : "SOAP response:\n") + getMessageText(context.getMessage().copy())); ++ } ++ }; ++ ++ public abstract void handleMessage(MessageHandlerContext mhc, boolean isRequest); ++ ++ public abstract void handleFault(MessageHandlerContext mhc); ++ ++ protected static String getMessageText(Message msg) { ++ try { ++ ByteArrayOutputStream out = new ByteArrayOutputStream(); ++ XMLStreamWriter writer = XMLStreamWriterFactory.create(out, "UTF-8"); ++ IndentingXMLStreamWriter wrap = new IndentingXMLStreamWriter(writer); ++ msg.writeTo(wrap); ++ return out.toString(StandardCharsets.UTF_8.name()); ++ } catch (Exception e) { ++ log.warn("Coudn't get SOAP message for logging", e); ++ return null; ++ } ++ } ++ } ++ ++ abstract protected boolean isRequest(boolean isOutbound); ++ ++ @Override ++ public boolean handleMessage(MessageHandlerContext mhc) { ++ HANDLER_MAP.get(loggingLevel).handleMessage(mhc, isRequest(isOutbound(mhc))); ++ return true; ++ } ++ ++ @Override ++ public boolean handleFault(MessageHandlerContext mhc) { ++ HANDLER_MAP.get(loggingLevel).handleFault(mhc); ++ return true; ++ } ++} +Index: services/mail-service/src/main/resources/mailWsHandlers.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/resources/mailWsHandlers.xml (revision ) ++++ services/mail-service/src/main/resources/mailWsHandlers.xml (revision ) +@@ -0,0 +1,8 @@ ++ ++ ++ ++ SoapLoggingHandler ++ ru.javaops.web.handler.SoapServerLoggingHandler ++ ++ ++ +\ No newline at end of file +Index: services/common-ws/src/main/java/ru/javaops/web/handler/SoapClientLoggingHandler.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/handler/SoapClientLoggingHandler.java (revision ) ++++ services/common-ws/src/main/java/ru/javaops/web/handler/SoapClientLoggingHandler.java (revision ) +@@ -0,0 +1,15 @@ ++package ru.javaops.web.handler; ++ ++ ++import org.slf4j.event.Level; ++ ++public class SoapClientLoggingHandler extends SoapLoggingHandler { ++ public SoapClientLoggingHandler(Level loggingLevel) { ++ super(loggingLevel); ++ } ++ ++ @Override ++ protected boolean isRequest(boolean isOutbound) { ++ return isOutbound; ++ } ++} +\ No newline at end of file +Index: services/common-ws/src/main/java/ru/javaops/web/handler/SoapServerLoggingHandler.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/handler/SoapServerLoggingHandler.java (revision ) ++++ services/common-ws/src/main/java/ru/javaops/web/handler/SoapServerLoggingHandler.java (revision ) +@@ -0,0 +1,16 @@ ++package ru.javaops.web.handler; ++ ++ ++import org.slf4j.event.Level; ++ ++public class SoapServerLoggingHandler extends SoapLoggingHandler { ++ ++ public SoapServerLoggingHandler() { ++ super(Level.INFO); ++ } ++ ++ @Override ++ protected boolean isRequest(boolean isOutbound) { ++ return !isOutbound; ++ } ++} +\ No newline at end of file +Index: services/mail-api/pom.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/pom.xml (date 1493417251000) ++++ services/mail-api/pom.xml (revision ) +@@ -33,6 +33,11 @@ + common-ws + ${project.version} + ++ ++ commons-io ++ commons-io ++ 2.5 ++ + + + +\ No newline at end of file +Index: services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java (date 1493417251000) ++++ services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java (revision ) +@@ -4,6 +4,7 @@ + import ru.javaops.web.WebStateException; + + import javax.annotation.Resource; ++import javax.jws.HandlerChain; + import javax.jws.WebService; + import javax.xml.ws.WebServiceContext; + import javax.xml.ws.handler.MessageContext; +@@ -16,6 +17,7 @@ + ) + //@StreamingAttachment(parseEagerly=true, memoryThreshold=40000L) + //@MTOM ++@HandlerChain(file = "mailWsHandlers.xml") + public class MailServiceImpl implements MailService { + + @Resource +Index: services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java (date 1493417251000) ++++ services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java (revision ) +@@ -1,6 +1,7 @@ + package ru.javaops.masterjava.service.mail.util; + + import lombok.AllArgsConstructor; ++import org.apache.commons.io.input.CloseShieldInputStream; + import ru.javaops.masterjava.service.mail.Attach; + + import javax.activation.DataHandler; +@@ -14,19 +15,16 @@ + return new Attach(name, new DataHandler(new InputStreamDataSource(inputStream))); + } + +- // http://stackoverflow.com/a/10783565/548473 ++ // http://stackoverflow.com/questions/2830561/how-to-convert-an-inputstream-to-a-datahandler ++ // http://stackoverflow.com/a/5924019/548473 ++ + @AllArgsConstructor + private static class InputStreamDataSource implements DataSource { + private InputStream inputStream; + + @Override + public InputStream getInputStream() throws IOException { +- if (inputStream == null) { +- throw new IOException("Second getInputStream() call is not supported"); +- } +- InputStream res = inputStream; +- inputStream = null; +- return res; ++ return new CloseShieldInputStream(inputStream); + } + + @Override +Index: services/common-ws/src/main/java/ru/javaops/web/WsClient.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/WsClient.java (date 1493417251000) ++++ services/common-ws/src/main/java/ru/javaops/web/WsClient.java (revision ) +@@ -5,10 +5,13 @@ + import ru.javaops.masterjava.config.Configs; + + import javax.xml.namespace.QName; ++import javax.xml.ws.Binding; + import javax.xml.ws.BindingProvider; + import javax.xml.ws.Service; + import javax.xml.ws.WebServiceFeature; ++import javax.xml.ws.handler.Handler; + import java.net.URL; ++import java.util.List; + import java.util.Map; + + public class WsClient { +@@ -46,6 +49,13 @@ + requestContext.put(BindingProvider.PASSWORD_PROPERTY, password); + } + ++ public static void setHandler(T port, Handler handler) { ++ Binding binding = ((BindingProvider) port).getBinding(); ++ List handlerList = binding.getHandlerChain(); ++ handlerList.add(handler); ++ binding.setHandlerChain(handlerList); ++ } ++ + public static WebStateException getWebStateException(Exception e) { + return (e instanceof WebStateException) ? + (WebStateException) e : new WebStateException(ExceptionType.NETWORK, e); +Index: services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (date 1493417251000) ++++ services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java (revision ) +@@ -5,9 +5,11 @@ + import com.google.common.collect.Iterables; + import com.google.common.io.Resources; + import lombok.extern.slf4j.Slf4j; ++import org.slf4j.event.Level; + import ru.javaops.web.AuthUtil; + import ru.javaops.web.WebStateException; + import ru.javaops.web.WsClient; ++import ru.javaops.web.handler.SoapClientLoggingHandler; + + import javax.xml.namespace.QName; + import javax.xml.ws.soap.MTOMFeature; +@@ -19,6 +21,7 @@ + private static final WsClient WS_CLIENT; + public static final String USER = "user"; + public static final String PASSWORD = "password"; ++ private static final SoapClientLoggingHandler LOGGING_HANDLER = new SoapClientLoggingHandler(Level.DEBUG); + + public static String AUTH_HEADER = AuthUtil.encodeBasicAuthHeader(USER, PASSWORD); + +@@ -60,6 +63,7 @@ + private static MailService getPort() { + MailService port = WS_CLIENT.getPort(new MTOMFeature(1024)); + WsClient.setAuth(port, USER, PASSWORD); ++ WsClient.setHandler(port, LOGGING_HANDLER); + return port; + } + diff --git a/patches/9/patch/9_7_prepare_HW9.patch b/patches/9/patch/9_7_prepare_HW9.patch new file mode 100644 index 000000000..1b87c6409 --- /dev/null +++ b/patches/9/patch/9_7_prepare_HW9.patch @@ -0,0 +1,50 @@ +Index: common/src/main/resources/hosts.conf +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- common/src/main/resources/hosts.conf (revision dcac6c076c28383c7afcd0621bba01a6789b858d) ++++ common/src/main/resources/hosts.conf (revision 3c63fd8640d75a6787c8a3d25f9fc941db55ecd5) +@@ -1,4 +1,10 @@ + hosts { +- mail = "http://localhost:8080" ++ mail { ++ endpoint = "http://localhost:8080" ++ debug.client = DEBUG ++ debug.server = INFO ++ user = "user" ++ password = "password" ++ } + } + include file("/apps/masterjava/config/hosts.conf") +Index: services/common-ws/src/main/java/ru/javaops/web/Statistics.java +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- services/common-ws/src/main/java/ru/javaops/web/Statistics.java (revision 3c63fd8640d75a6787c8a3d25f9fc941db55ecd5) ++++ services/common-ws/src/main/java/ru/javaops/web/Statistics.java (revision 3c63fd8640d75a6787c8a3d25f9fc941db55ecd5) +@@ -0,0 +1,23 @@ ++package ru.javaops.web; ++ ++import lombok.extern.slf4j.Slf4j; ++ ++/** ++ * gkislin ++ * 09.01.2017 ++ */ ++@Slf4j ++public class Statistics { ++ public enum RESULT { ++ SUCCESS, FAIL ++ } ++ ++ public static void count(String payload, long startTime, RESULT result) { ++ long now = System.currentTimeMillis(); ++ int ms = (int) (now - startTime); ++ log.info(payload + " " + result.name() + " execution time(ms): " + ms); ++ // place for statistics staff ++ ++ } ++ ++} diff --git a/persist/pom.xml b/persist/pom.xml new file mode 100644 index 000000000..369d3f832 --- /dev/null +++ b/persist/pom.xml @@ -0,0 +1,89 @@ + + + 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 + + + + + + + + org.liquibase + liquibase-maven-plugin + 3.5.3 + + ../sql/databaseChangeLog.sql + org.postgresql.Driver + jdbc:postgresql://localhost:5432/masterjava + user + password + + + + + + process-resources + + update + + + + + + + + + + ${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..9fc78e2c7 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/GroupDao.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.Group; + +import java.util.Collection; +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); + } + + @SqlBatch("INSERT INTO groups (name, type, project_id) VALUES (:name, CAST(:type AS group_type), :projectId)") + public abstract void insertBatch(@BindBean Collection groups); +} 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..2f138983c --- /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(users::get) + .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..64dda08be --- /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("/home/konst/work/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..0e177638d --- /dev/null +++ b/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + + ru.javaops + masterjava + pom + + 1.0-SNAPSHOT + + Master Java + https://github.com/JavaOPs/masterjava + + + parent + parent-web + + common + persist + test + + services/akka-remote + services/common-ws + services/mail-api + services/mail-service + + web/common-web + web/webapp + web/export + + diff --git a/services/akka-remote/pom.xml b/services/akka-remote/pom.xml new file mode 100644 index 000000000..1034bd1ad --- /dev/null +++ b/services/akka-remote/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + + ru.javaops + parent-web + ../../parent-web/pom.xml + 1.0-SNAPSHOT + + + akka-remote + 1.0-SNAPSHOT + Akka Remote + + + + ${project.groupId} + common + ${project.version} + + + + com.typesafe.akka + akka-remote_2.12 + 2.5.1 + + + com.typesafe + config + + + org.scala-lang + scala-library + + + + + org.scala-lang + scala-library + 2.12.2 + + + \ No newline at end of file diff --git a/services/akka-remote/src/main/java/ru/javaops/masterjava/akka/AkkaActivator.java b/services/akka-remote/src/main/java/ru/javaops/masterjava/akka/AkkaActivator.java new file mode 100644 index 000000000..20d5d1f98 --- /dev/null +++ b/services/akka-remote/src/main/java/ru/javaops/masterjava/akka/AkkaActivator.java @@ -0,0 +1,64 @@ +package ru.javaops.masterjava.akka; + +import akka.actor.*; +import akka.japi.Creator; +import akka.util.Timeout; +import lombok.extern.slf4j.Slf4j; +import ru.javaops.masterjava.config.Configs; +import scala.concurrent.ExecutionContext; +import scala.concurrent.duration.Duration; + +import java.util.concurrent.TimeUnit; + +@Slf4j +public class AkkaActivator { + private static final String AKKA_CONF = "akka.conf"; + + private ActorSystem system; + + private AkkaActivator(String actorSystemName, String nodeName) { + log.info("Start AKKA System {} : {}", actorSystemName, nodeName); + system = ActorSystem.create(actorSystemName, Configs.getAppConfig(AKKA_CONF).getConfig(nodeName)); + } + + public static AkkaActivator start(String actorSystemName, String configName) { + return new AkkaActivator(actorSystemName, configName); + } + + public void startTypedActor(Class typedClass, String name, Creator creator) { + log.info("Start AKKA typed actor: {}", name); + TypedActor.get(system).typedActorOf( + new TypedProps(typedClass, creator).withTimeout(new Timeout(Duration.create(20, TimeUnit.SECONDS))), name); + } + + public ActorRef startActor(Class actorClass, String name) { + log.info("Start AKKA actor: {}", name); + return system.actorOf(Props.create(actorClass), name); + } + + public ActorRef startActor(Props props) { + log.info("Start new AKKA actor"); + return system.actorOf(props); + } + + public T getTypedRef(Class typedClass, String path) { + log.info("Get typed reference with path={}", path); + return TypedActor.get(system).typedActorOf(new TypedProps(typedClass), system.actorFor(path)); + } + + public ActorRef getActorRef(String path) { + log.info("Get actor reference with path={}", path); + return system.actorFor(path); + } + + public ExecutionContext getExecutionContext() { + return system.dispatcher(); + } + + public void shutdown() { + if (system != null) { + log.info("Akka system shutdown"); + system.terminate(); + } + } +} diff --git a/services/akka-remote/src/main/resources/akka-common.conf b/services/akka-remote/src/main/resources/akka-common.conf new file mode 100644 index 000000000..f63aac186 --- /dev/null +++ b/services/akka-remote/src/main/resources/akka-common.conf @@ -0,0 +1,12 @@ +akka { + actor { + provider = "akka.remote.RemoteActorRefProvider" + } + + remote { + netty.tcp { + hostname = "127.0.0.1" + maximum-frame-size = 10000000b + } + } +} \ No newline at end of file diff --git a/services/common-ws/pom.xml b/services/common-ws/pom.xml new file mode 100644 index 000000000..9a38ba13a --- /dev/null +++ b/services/common-ws/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + + ru.javaops + parent + ../../parent/pom.xml + 1.0-SNAPSHOT + + + common-ws + 1.0-SNAPSHOT + Common Web Services + + + + ${project.groupId} + common + ${project.version} + + + + com.sun.xml.ws + jaxws-rt + 2.2.10 + + + org.jvnet.mimepull + mimepull + + + javax.xml.bind + jaxb-api + + + javax.annotation + javax.annotation-api + + + org.jvnet.staxex + stax-ex + + + javax.xml.soap + javax.xml.soap-api + + + + + + org.jvnet.mimepull + mimepull + 1.9.4 + + + + javax.activation + activation + 1.1.1 + + + org.jvnet.staxex + stax-ex + 1.7.7 + + + javax.activation + activation + + + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + \ No newline at end of file diff --git a/services/common-ws/src/main/java/ru/javaops/web/AuthUtil.java b/services/common-ws/src/main/java/ru/javaops/web/AuthUtil.java new file mode 100644 index 000000000..4e3422276 --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/web/AuthUtil.java @@ -0,0 +1,33 @@ +package ru.javaops.web; + +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletResponse; +import javax.xml.bind.DatatypeConverter; +import java.util.List; +import java.util.Map; + +import static com.google.common.net.HttpHeaders.AUTHORIZATION; + +@Slf4j +public class AuthUtil { + + public static String encodeBasicAuthHeader(String name, String passw) { + String authString = name + ":" + passw; + return "Basic " + DatatypeConverter.printBase64Binary(authString.getBytes()); + } + + public static int checkBasicAuth(Map> headers, String basicAuthCredentials) { + List autHeaders = headers.get(AUTHORIZATION); + if ((autHeaders == null || autHeaders.isEmpty())) { + log.warn("Unauthorized access"); + return HttpServletResponse.SC_UNAUTHORIZED; + } else { + if (!autHeaders.get(0).equals(basicAuthCredentials)) { + log.warn("Wrong password access"); + return HttpServletResponse.SC_FORBIDDEN; + } + return 0; + } + } +} diff --git a/services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java b/services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java new file mode 100644 index 000000000..d3525c5b9 --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java @@ -0,0 +1,22 @@ +package ru.javaops.web; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import ru.javaops.masterjava.ExceptionType; + +import javax.xml.bind.annotation.XmlType; + +@Data +@RequiredArgsConstructor +@NoArgsConstructor +@XmlType(namespace = "http://common.javaops.ru/") +public class FaultInfo { + private @NonNull ExceptionType type; + + @Override + public String toString() { + return type.toString(); + } +} diff --git a/services/common-ws/src/main/java/ru/javaops/web/Statistics.java b/services/common-ws/src/main/java/ru/javaops/web/Statistics.java new file mode 100644 index 000000000..e42fa1790 --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/web/Statistics.java @@ -0,0 +1,23 @@ +package ru.javaops.web; + +import lombok.extern.slf4j.Slf4j; + +/** + * gkislin + * 09.01.2017 + */ +@Slf4j +public class Statistics { + public enum RESULT { + SUCCESS, FAIL + } + + public static void count(String payload, long startTime, RESULT result) { + long now = System.currentTimeMillis(); + int ms = (int) (now - startTime); + log.info(payload + " " + result.name() + " execution time(ms): " + ms); + // place for statistics staff + + } + +} diff --git a/services/common-ws/src/main/java/ru/javaops/web/WebStateException.java b/services/common-ws/src/main/java/ru/javaops/web/WebStateException.java new file mode 100644 index 000000000..8f1ffc4e0 --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/web/WebStateException.java @@ -0,0 +1,35 @@ +package ru.javaops.web; + + +import com.google.common.base.Throwables; +import ru.javaops.masterjava.ExceptionType; + +import javax.xml.ws.WebFault; + +@WebFault(name = "webStateException", targetNamespace = "http://common.javaops.ru/") +public class WebStateException extends Exception { + private FaultInfo faultInfo; + + public WebStateException(String message, FaultInfo faultInfo) { + super(message); + this.faultInfo = faultInfo; + } + + public WebStateException(Exception e) { + this(ExceptionType.SYSTEM, e); + } + + public WebStateException(ExceptionType type, Throwable cause) { + super(Throwables.getRootCause(cause).toString(), cause); + this.faultInfo = new FaultInfo(type); + } + + public FaultInfo getFaultInfo() { + return faultInfo; + } + + @Override + public String toString() { + return faultInfo.toString() + '\n' + super.toString(); + } +} diff --git a/services/common-ws/src/main/java/ru/javaops/web/WsClient.java b/services/common-ws/src/main/java/ru/javaops/web/WsClient.java new file mode 100644 index 000000000..0242b8a98 --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/web/WsClient.java @@ -0,0 +1,109 @@ +package ru.javaops.web; + +import com.typesafe.config.Config; +import org.slf4j.event.Level; +import ru.javaops.masterjava.ExceptionType; +import ru.javaops.masterjava.config.Configs; +import ru.javaops.web.handler.SoapLoggingHandlers; + +import javax.xml.namespace.QName; +import javax.xml.ws.Binding; +import javax.xml.ws.BindingProvider; +import javax.xml.ws.Service; +import javax.xml.ws.WebServiceFeature; +import javax.xml.ws.handler.Handler; +import java.net.URL; +import java.util.List; +import java.util.Map; + +public class WsClient { + private static Config HOSTS; + + private final Class serviceClass; + private final Service service; + private HostConfig hostConfig; + + public static class HostConfig { + public final String endpoint; + public final Level serverDebugLevel; + public final String user; + public final String password; + public final String authHeader; + public final SoapLoggingHandlers.ClientHandler clientLoggingHandler; + + public HostConfig(Config config, String endpointAddress) { + endpoint = config.getString("endpoint") + endpointAddress; + serverDebugLevel = config.getEnum(Level.class, "server.debugLevel"); + +// https://github.com/typesafehub/config/issues/282 + if (!config.getIsNull("user") && !config.getIsNull("password")) { + user = config.getString("user"); + password = config.getString("password"); + authHeader = AuthUtil.encodeBasicAuthHeader(user, password); + } else { + user = password = authHeader = null; + } + clientLoggingHandler = config.getIsNull("client.debugLevel") ? null : + new SoapLoggingHandlers.ClientHandler(config.getEnum(Level.class, "client.debugLevel")); + } + + public boolean hasAuthorization() { + return authHeader != null; + } + + public boolean hasHandler() { + return clientLoggingHandler != null; + } + } + + static { + HOSTS = Configs.getConfig("hosts.conf", "hosts"); + } + + public WsClient(URL wsdlUrl, QName qname, Class serviceClass) { + this.serviceClass = serviceClass; + this.service = Service.create(wsdlUrl, qname); + } + + public void init(String host, String endpointAddress) { + this.hostConfig = new HostConfig( + HOSTS.getConfig(host).withFallback(Configs.getConfig("defaults.conf")), endpointAddress); + } + + public HostConfig getHostConfig() { + return hostConfig; + } + + // Post is not thread-safe (http://stackoverflow.com/a/10601916/548473) + public T getPort(WebServiceFeature... features) { + T port = service.getPort(serviceClass, features); + BindingProvider bp = (BindingProvider) port; + Map requestContext = bp.getRequestContext(); + requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, hostConfig.endpoint); + if (hostConfig.hasAuthorization()) { + setAuth(port, hostConfig.user, hostConfig.password); + } + if (hostConfig.hasHandler()) { + setHandler(port, hostConfig.clientLoggingHandler); + } + return port; + } + + public static void setAuth(T port, String user, String password) { + Map requestContext = ((BindingProvider) port).getRequestContext(); + requestContext.put(BindingProvider.USERNAME_PROPERTY, user); + requestContext.put(BindingProvider.PASSWORD_PROPERTY, password); + } + + public static void setHandler(T port, Handler handler) { + Binding binding = ((BindingProvider) port).getBinding(); + List handlerList = binding.getHandlerChain(); + handlerList.add(handler); + binding.setHandlerChain(handlerList); + } + + public static WebStateException getWebStateException(Exception e) { + return (e instanceof WebStateException) ? + (WebStateException) e : new WebStateException(ExceptionType.NETWORK, e); + } +} diff --git a/services/common-ws/src/main/java/ru/javaops/web/handler/SoapBaseHandler.java b/services/common-ws/src/main/java/ru/javaops/web/handler/SoapBaseHandler.java new file mode 100644 index 000000000..ad2b18779 --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/web/handler/SoapBaseHandler.java @@ -0,0 +1,23 @@ +package ru.javaops.web.handler; + +import com.sun.xml.ws.api.handler.MessageHandler; +import com.sun.xml.ws.api.handler.MessageHandlerContext; + +import javax.xml.namespace.QName; +import javax.xml.ws.handler.MessageContext; +import java.util.Set; + +public abstract class SoapBaseHandler implements MessageHandler { + + public Set getHeaders() { + return null; + } + + @Override + public void close(MessageContext context) { + } + + protected static boolean isOutbound(MessageHandlerContext context) { + return (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); + } +} diff --git a/services/common-ws/src/main/java/ru/javaops/web/handler/SoapLoggingHandlers.java b/services/common-ws/src/main/java/ru/javaops/web/handler/SoapLoggingHandlers.java new file mode 100644 index 000000000..cb29cb346 --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/web/handler/SoapLoggingHandlers.java @@ -0,0 +1,143 @@ +package ru.javaops.web.handler; + + +import com.sun.xml.txw2.output.IndentingXMLStreamWriter; +import com.sun.xml.ws.api.handler.MessageHandlerContext; +import com.sun.xml.ws.api.message.Message; +import com.sun.xml.ws.api.streaming.XMLStreamWriterFactory; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.event.Level; + +import javax.xml.stream.XMLStreamWriter; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.EnumMap; +import java.util.Map; + +/** + * Refactored from: + * + * @see {http://weblogs.java.net/blog/ramapulavarthi/archive/2007/12/extend_your_web.html + * http://fisheye5.cenqua.com/browse/jax-ws-sources/jaxws-ri/samples/efficient_handler/src/efficient_handler/common/LoggingHandler.java?r=MAIN} + *

+ * This simple LoggingHandler will log the contents of incoming + * and outgoing messages. This is implemented as a MessageHandler + * for better performance over SOAPHandler. + */ +@Slf4j +public abstract class SoapLoggingHandlers extends SoapBaseHandler { + + private final Level loggingLevel; + + protected SoapLoggingHandlers(Level loggingLevel) { + this.loggingLevel = loggingLevel; + } + + private static final Map HANDLER_MAP = new EnumMap(Level.class) { + { + put(Level.TRACE, HANDLER.DEBUG); + put(Level.DEBUG, HANDLER.DEBUG); + put(Level.INFO, HANDLER.INFO); + put(Level.WARN, HANDLER.ERROR); + put(Level.ERROR, HANDLER.ERROR); + } + }; + + protected enum HANDLER { + NONE { + @Override + public void handleFault(MessageHandlerContext mhc) { + } + + @Override + public void handleMessage(MessageHandlerContext mhc, boolean isRequest) { + } + }, + ERROR { + private static final String REQUEST_MSG = "REQUEST_MSG"; + + public void handleFault(MessageHandlerContext context) { + log.error("Fault SOAP request:\n" + getMessageText(((Message) context.get(REQUEST_MSG)))); + } + + public void handleMessage(MessageHandlerContext context, boolean isRequest) { + if (isRequest) { + context.put(REQUEST_MSG, context.getMessage().copy()); + } + } + }, + INFO { + public void handleFault(MessageHandlerContext context) { + ERROR.handleFault(context); + } + + public void handleMessage(MessageHandlerContext context, boolean isRequest) { + ERROR.handleMessage(context, isRequest); + log.info((isRequest ? "SOAP request: " : "SOAP response: ") + context.getMessage().getPayloadLocalPart()); + } + }, + DEBUG { + public void handleFault(MessageHandlerContext context) { + log.error("Fault SOAP message:\n" + getMessageText(context.getMessage().copy())); + } + + public void handleMessage(MessageHandlerContext context, boolean isRequest) { + log.info((isRequest ? "SOAP request:\n" : "SOAP response:\n") + getMessageText(context.getMessage().copy())); + } + }; + + public abstract void handleMessage(MessageHandlerContext mhc, boolean isRequest); + + public abstract void handleFault(MessageHandlerContext mhc); + + protected static String getMessageText(Message msg) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + XMLStreamWriter writer = XMLStreamWriterFactory.create(out, "UTF-8"); + IndentingXMLStreamWriter wrap = new IndentingXMLStreamWriter(writer); + msg.writeTo(wrap); + return out.toString(StandardCharsets.UTF_8.name()); + } catch (Exception e) { + log.warn("Coudn't get SOAP message for logging", e); + return null; + } + } + } + + abstract protected boolean isRequest(boolean isOutbound); + + @Override + public boolean handleMessage(MessageHandlerContext mhc) { + HANDLER_MAP.get(loggingLevel).handleMessage(mhc, isRequest(isOutbound(mhc))); + return true; + } + + @Override + public boolean handleFault(MessageHandlerContext mhc) { + HANDLER_MAP.get(loggingLevel).handleFault(mhc); + return true; + } + + public static class ClientHandler extends SoapLoggingHandlers { + public ClientHandler(Level loggingLevel) { + super(loggingLevel); + } + + @Override + protected boolean isRequest(boolean isOutbound) { + return isOutbound; + } + } + + public static class ServerHandler extends SoapLoggingHandlers { + + public ServerHandler(Level loggingLevel) { + super(loggingLevel); + } + + @Override + protected boolean isRequest(boolean isOutbound) { + return !isOutbound; + } + } +} diff --git a/services/common-ws/src/main/java/ru/javaops/web/handler/SoapServerSecurityHandler.java b/services/common-ws/src/main/java/ru/javaops/web/handler/SoapServerSecurityHandler.java new file mode 100644 index 000000000..5855630dc --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/web/handler/SoapServerSecurityHandler.java @@ -0,0 +1,43 @@ +package ru.javaops.web.handler; + +import com.sun.xml.ws.api.handler.MessageHandlerContext; +import lombok.extern.slf4j.Slf4j; +import ru.javaops.web.AuthUtil; + +import javax.xml.ws.handler.MessageContext; +import java.util.List; +import java.util.Map; + +import static ru.javaops.web.AuthUtil.encodeBasicAuthHeader; + +@Slf4j +abstract public class SoapServerSecurityHandler extends SoapBaseHandler { + + private String authHeader; + + public SoapServerSecurityHandler(String user, String password) { + this(encodeBasicAuthHeader(user, password)); + } + + public SoapServerSecurityHandler(String authHeader) { + this.authHeader = authHeader; + } + + @Override + public boolean handleMessage(MessageHandlerContext ctx) { + if (!isOutbound(ctx) && authHeader != null) { + Map> headers = (Map>) ctx.get(MessageContext.HTTP_REQUEST_HEADERS); + int code = AuthUtil.checkBasicAuth(headers, authHeader); + if (code != 0) { + ctx.put(MessageContext.HTTP_RESPONSE_CODE, code); + throw new SecurityException(); + } + } + return true; + } + + @Override + public boolean handleFault(MessageHandlerContext context) { + return true; + } +} diff --git a/services/common-ws/src/main/java/ru/javaops/web/handler/SoapStatisticHandler.java b/services/common-ws/src/main/java/ru/javaops/web/handler/SoapStatisticHandler.java new file mode 100644 index 000000000..db2c9e68b --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/web/handler/SoapStatisticHandler.java @@ -0,0 +1,30 @@ +package ru.javaops.web.handler; + +import com.sun.xml.ws.api.handler.MessageHandlerContext; +import ru.javaops.web.Statistics; + +public class SoapStatisticHandler extends SoapBaseHandler { + + private static final String PAYLOAD = "PAYLOAD"; + private static final String START_TIME = "START_TIME"; + + public boolean handleMessage(MessageHandlerContext context) { + if (isOutbound(context)) { + count(context, Statistics.RESULT.SUCCESS); + } else { + String payload = context.getMessage().getPayloadLocalPart(); + context.put(PAYLOAD, payload); + context.put(START_TIME, System.currentTimeMillis()); + } + return true; + } + + public boolean handleFault(MessageHandlerContext context) { + count(context, Statistics.RESULT.FAIL); + return true; + } + + private void count(MessageHandlerContext context, Statistics.RESULT result) { + Statistics.count((String) context.get(PAYLOAD), (Long) context.get(START_TIME), result); + } +} \ No newline at end of file diff --git a/services/mail-api/pom.xml b/services/mail-api/pom.xml new file mode 100644 index 000000000..cb76585c5 --- /dev/null +++ b/services/mail-api/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + + ru.javaops + parent + ../../parent/pom.xml + 1.0-SNAPSHOT + + + mail-api + 1.0-SNAPSHOT + Mail API + + + + + ${masterjava.config} + + wsdl/mailService.wsdl + wsdl/common.xsd + + + + + + + + ${project.groupId} + common-ws + ${project.version} + + + commons-io + commons-io + 2.5 + + + ${project.groupId} + akka-remote + ${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..babe171b5 --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Addressee.java @@ -0,0 +1,50 @@ +package ru.javaops.masterjava.service.mail; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlValue; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@XmlAccessorType(XmlAccessType.FIELD) +public class Addressee { + @XmlAttribute + private String email; + @XmlValue + private String name; + + public Addressee(String email) { + email = email.trim(); + int idx = email.indexOf('<'); + if (idx == -1) { + this.email = email; + } else { + this.name = email.substring(0, idx).trim(); + this.email = email.substring(idx + 1, email.length() - 1).trim(); + } + } + + @Override + public String toString() { + return name == null ? email : name + " <" + email + '>'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Addressee addressee = (Addressee) o; + return email.equals(addressee.email); + } + + @Override + public int hashCode() { + return email.hashCode(); + } +} diff --git a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Attach.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Attach.java new file mode 100644 index 000000000..c9e7cd377 --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Attach.java @@ -0,0 +1,22 @@ +package ru.javaops.masterjava.service.mail; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.activation.DataHandler; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlMimeType; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@XmlAccessorType(XmlAccessType.FIELD) +public class Attach { + // http://stackoverflow.com/questions/12250423/jax-ws-datahandler-getname-is-blank-when-called-from-client-side + protected String name; + + @XmlMimeType("application/octet-stream") + private DataHandler dataHandler; +} diff --git a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/GroupResult.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/GroupResult.java new file mode 100644 index 000000000..19f0ae508 --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/GroupResult.java @@ -0,0 +1,31 @@ +package ru.javaops.masterjava.service.mail; + +import com.google.common.base.Throwables; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GroupResult implements Serializable { + private static final long serialVersionUID = 1L; + + private int success; // number of successfully sent email + private List failed; // failed emails with causes + private String failedCause; // global fail cause + + public GroupResult(Exception e) { + this(-1, null, Throwables.getRootCause(e).toString()); + } + + @Override + public String toString() { + return "Success: " + success + '\n' + + (failed == null ? "" : "Failed: " + failed.toString() + '\n') + + (failedCause == null ? "" : "Failed cause: " + failedCause); + } +} \ No newline at end of file diff --git a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailRemoteService.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailRemoteService.java new file mode 100644 index 000000000..c7be717a6 --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailRemoteService.java @@ -0,0 +1,8 @@ +package ru.javaops.masterjava.service.mail; + +import ru.javaops.masterjava.service.mail.util.MailUtils; + +public interface MailRemoteService { + + scala.concurrent.Future sendBulk(MailUtils.MailObject mailObject); +} \ No newline at end of file diff --git a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailResult.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailResult.java new file mode 100644 index 000000000..64927f156 --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailResult.java @@ -0,0 +1,30 @@ +package ru.javaops.masterjava.service.mail; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +import java.io.Serializable; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class MailResult implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String OK = "OK"; + + private @NonNull + String email; + private String result; + + public boolean isOk() { + return OK.equals(result); + } + + @Override + public String toString() { + return '\'' + email + "' result '" + result + '\''; + } +} 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..6e51b1926 --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailService.java @@ -0,0 +1,33 @@ +package ru.javaops.masterjava.service.mail; + +import ru.javaops.web.WebStateException; + +import javax.jws.WebMethod; +import javax.jws.WebParam; +import javax.jws.WebService; +import java.util.List; +import java.util.Set; + +@WebService(targetNamespace = "http://mail.javaops.ru/") +//@SOAPBinding( +// style = SOAPBinding.Style.DOCUMENT, +// use= SOAPBinding.Use.LITERAL, +// parameterStyle = SOAPBinding.ParameterStyle.WRAPPED) +public interface MailService { + + @WebMethod + String sendToGroup( + @WebParam(name = "to") Set to, + @WebParam(name = "cc") Set cc, + @WebParam(name = "subject") String subject, + @WebParam(name = "body") String body, + @WebParam(name = "attaches") List attaches) throws WebStateException; + + @WebMethod + GroupResult sendBulk( + @WebParam(name = "to") Set to, + @WebParam(name = "subject") String subject, + @WebParam(name = "body") String body, + @WebParam(name = "attaches") List attaches) throws WebStateException; + +} \ No newline at end of file diff --git a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java new file mode 100644 index 000000000..568e7b94b --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java @@ -0,0 +1,59 @@ +package ru.javaops.masterjava.service.mail; + +import com.google.common.io.Resources; +import lombok.extern.slf4j.Slf4j; +import ru.javaops.web.WebStateException; +import ru.javaops.web.WsClient; + +import javax.xml.namespace.QName; +import javax.xml.ws.soap.MTOMFeature; +import java.util.List; +import java.util.Set; + +@Slf4j +public class MailWSClient { + private static final WsClient WS_CLIENT; + + static { + WS_CLIENT = new WsClient(Resources.getResource("wsdl/mailService.wsdl"), + new QName("http://mail.javaops.ru/", "MailServiceImplService"), + MailService.class); + + WS_CLIENT.init("mail", "/mail/mailService?wsdl"); + } + + + public static String sendToGroup(final Set to, final Set cc, final String subject, final String body, List attaches) throws WebStateException { + log.info("Send mail to '" + to + "' cc '" + cc + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); + String status; + try { + status = getPort().sendToGroup(to, cc, subject, body, attaches); + log.info("Sent with status: " + status); + } catch (Exception e) { + log.error("sendToGroup failed", e); + throw WsClient.getWebStateException(e); + } + return status; + } + + public static GroupResult sendBulk(final Set to, final String subject, final String body, List attaches) throws WebStateException { + log.info("Send mail to '" + to + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); + GroupResult result; + try { + result = getPort().sendBulk(to, subject, body, attaches); + } catch (WebStateException e) { + log.error("sendBulk failed", e); + throw WsClient.getWebStateException(e); + } + log.info("Sent with result: " + result); + return result; + } + + private static MailService getPort() { + return WS_CLIENT.getPort(new MTOMFeature(1024)); + } + + public static WsClient.HostConfig getHostConfig() { + return WS_CLIENT.getHostConfig(); + } +} diff --git a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/MailUtils.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/MailUtils.java new file mode 100644 index 000000000..25f41d869 --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/MailUtils.java @@ -0,0 +1,70 @@ +package ru.javaops.masterjava.service.mail.util; + +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.sun.istack.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.apache.commons.io.input.CloseShieldInputStream; +import ru.javaops.masterjava.service.mail.Addressee; +import ru.javaops.masterjava.service.mail.Attach; + +import javax.activation.DataHandler; +import javax.activation.DataSource; +import java.io.*; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class MailUtils { + + public static Set split(String addressees) { + Iterable split = Splitter.on(',').trimResults().omitEmptyStrings().split(addressees); + return ImmutableSet.copyOf(Iterables.transform(split, Addressee::new)); + } + + @Data + @AllArgsConstructor + public static class MailObject implements Serializable { + private static final long serialVersionUID = 1L; + + private @NotNull String users; + private String subject; + private @NotNull String body; + // http://stackoverflow.com/questions/521171/a-java-collection-of-value-pairs-tuples + private List> attaches; + } + + public static List getAttaches(List> attaches) { + return attaches.stream().map(a -> getAttach(a.getKey(), a.getValue())).collect(Collectors.toList()); + } + + public static Attach getAttach(String name, byte[] attachData) { + return new Attach(name, new DataHandler((ProxyDataSource) () -> new ByteArrayInputStream(attachData))); + } + + public static Attach getAttach(String name, InputStream inputStream) { + // http://stackoverflow.com/questions/2830561/how-to-convert-an-inputstream-to-a-datahandler + // http://stackoverflow.com/a/5924019/548473 + return new Attach(name, new DataHandler((ProxyDataSource) () -> new CloseShieldInputStream(inputStream))); + } + + public interface ProxyDataSource extends DataSource { + @Override + default OutputStream getOutputStream() throws IOException { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + default String getContentType() { + return "application/octet-stream"; + } + + @Override + default String getName() { + return ""; + } + } +} diff --git a/services/mail-api/src/test/java/ru/javaops/masterjava/service/mail/MailWSClientMain.java b/services/mail-api/src/test/java/ru/javaops/masterjava/service/mail/MailWSClientMain.java new file mode 100644 index 000000000..6ec2b76d7 --- /dev/null +++ b/services/mail-api/src/test/java/ru/javaops/masterjava/service/mail/MailWSClientMain.java @@ -0,0 +1,25 @@ +package ru.javaops.masterjava.service.mail; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import lombok.extern.slf4j.Slf4j; + +import javax.activation.DataHandler; +import java.io.File; + +@Slf4j +public class MailWSClientMain { + public static void main(String[] args) { + ImmutableSet addressees = ImmutableSet.of( + new Addressee("Мастер Java ")); + + try { + String state = MailWSClient.sendToGroup(addressees, ImmutableSet.of(), "Subject", "Body", ImmutableList.of( + new Attach("version.html", new DataHandler(new File("config_templates/version.html").toURI().toURL())) + )); + System.out.println(state); + } catch (Throwable e) { + log.error(e.toString(), e); + } + } +} \ 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..e3516583f --- /dev/null +++ b/services/mail-service/pom.xml @@ -0,0 +1,118 @@ + + + 4.0.0 + + + ru.javaops + parent-web + ../../parent-web/pom.xml + 1.0-SNAPSHOT + + + mail-service + 1.0-SNAPSHOT + war + Mail Service + + + mail + + + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + + + prepare-package + + run + + + + + + + + + + + + + + + + + + ${project.groupId} + mail-api + ${project.version} + + + org.apache.commons + commons-email + 1.4 + + + javax.activation + activation + + + + + + ${project.groupId} + persist + ${project.version} + + + ${project.groupId} + persist + ${project.version} + test-jar + test + + + + + org.glassfish.jersey.containers + jersey-container-servlet + 2.25.1 + + + org.glassfish.jersey.media + jersey-media-moxy + 2.25.1 + + + org.glassfish.jersey.ext + jersey-bean-validation + 2.25.1 + + + org.glassfish.jersey.media + jersey-media-multipart + 2.25.1 + + + org.jvnet.mimepull + mimepull + + + + + + org.apache.activemq + activemq-all + 5.14.5 + provided + + + org.slf4j + slf4j-api + + + + + \ No newline at end of file diff --git a/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailConfig.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailConfig.java new file mode 100644 index 000000000..4b00e0276 --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailConfig.java @@ -0,0 +1,67 @@ +package ru.javaops.masterjava.service.mail; + +import com.typesafe.config.Config; +import org.apache.commons.mail.DefaultAuthenticator; +import org.apache.commons.mail.Email; +import org.apache.commons.mail.EmailException; +import org.apache.commons.mail.HtmlEmail; +import ru.javaops.masterjava.config.Configs; + +import javax.mail.Authenticator; +import java.nio.charset.StandardCharsets; + +public class MailConfig { + private static final MailConfig INSTANCE = + new MailConfig(Configs.getConfig("mail.conf", "mail")); + + final private String host; + final private int port; + final private boolean useSSL; + final private boolean useTLS; + final private boolean debug; + final private String username; + final private Authenticator auth; + final private String fromName; + + private MailConfig(Config conf) { + host = conf.getString("host"); + port = conf.getInt("port"); + username = conf.getString("username"); + auth = new DefaultAuthenticator(username, conf.getString("password")); + useSSL = conf.getBoolean("useSSL"); + useTLS = conf.getBoolean("useTLS"); + debug = conf.getBoolean("debug"); + fromName = conf.getString("fromName"); + } + + public T prepareEmail(T email) throws EmailException { + email.setFrom(username, fromName); + email.setHostName(host); + if (useSSL) { + email.setSslSmtpPort(String.valueOf(port)); + } else { + email.setSmtpPort(port); + } + email.setSSLOnConnect(useSSL); + email.setStartTLSEnabled(useTLS); + email.setDebug(debug); + email.setAuthenticator(auth); + email.setCharset(StandardCharsets.UTF_8.name()); + return email; + } + + public static HtmlEmail createHtmlEmail() throws EmailException { + return INSTANCE.prepareEmail(new HtmlEmail()); + } + + @Override + public String toString() { + return "\nhost='" + host + '\'' + + "\nport=" + port + + "\nuseSSL=" + useSSL + + "\nuseTLS=" + useTLS + + "\ndebug=" + debug + + "\nusername='" + username + '\'' + + "\nfromName='" + fromName + '\''; + } +} diff --git a/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailHandlers.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailHandlers.java new file mode 100644 index 000000000..ec8521f7c --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailHandlers.java @@ -0,0 +1,18 @@ +package ru.javaops.masterjava.service.mail; + +import ru.javaops.web.handler.SoapLoggingHandlers; +import ru.javaops.web.handler.SoapServerSecurityHandler; + +public class MailHandlers { + public static class SecurityHandler extends SoapServerSecurityHandler { + public SecurityHandler() { + super(MailWSClient.getHostConfig().authHeader); + } + } + + public static class LoggingHandler extends SoapLoggingHandlers.ServerHandler { + public LoggingHandler() { + super(MailWSClient.getHostConfig().serverDebugLevel); + } + } +} 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..1da70a781 --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailSender.java @@ -0,0 +1,68 @@ +package ru.javaops.masterjava.service.mail; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import ru.javaops.masterjava.ExceptionType; +import ru.javaops.masterjava.persist.DBIProvider; +import ru.javaops.masterjava.service.mail.persist.MailCase; +import ru.javaops.masterjava.service.mail.persist.MailCaseDao; +import ru.javaops.web.WebStateException; + +import javax.mail.internet.MimeUtility; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Set; + +@Slf4j +public class MailSender { + private static final MailCaseDao MAIL_CASE_DAO = DBIProvider.getDao(MailCaseDao.class); + + static MailResult sendTo(Addressee to, String subject, String body, List attaches) throws WebStateException { + val state = sendToGroup(ImmutableSet.of(to), ImmutableSet.of(), subject, body, attaches); + return new MailResult(to.getEmail(), state); + } + + static String sendToGroup(Set to, Set cc, String subject, String body, List attaches) throws WebStateException { + log.info("Send mail to \'" + to + "\' cc \'" + cc + "\' subject \'" + subject + '\'' + (log.isDebugEnabled() ? "\nbody=" + body : "")); + String state = MailResult.OK; + try { + val email = MailConfig.createHtmlEmail(); + email.setSubject(subject); + email.setHtmlMsg(body); + for (Addressee addressee : to) { + email.addTo(addressee.getEmail(), addressee.getName()); + } + for (Addressee addressee : cc) { + email.addCc(addressee.getEmail(), addressee.getName()); + } + for (Attach attach : attaches) { + email.attach(attach.getDataHandler().getDataSource(), encodeWord(attach.getName()), null); + } + + // https://yandex.ru/blog/company/66296 + email.setHeaders(ImmutableMap.of("List-Unsubscribe", "")); + email.send(); + } catch (Exception e) { + log.error(e.getMessage(), e); + state = e.getMessage(); + } + try { + MAIL_CASE_DAO.insert(MailCase.of(to, cc, subject, body, state)); + } catch (Exception e) { + log.error("Mail history saving exception", e); + throw new WebStateException(ExceptionType.DATA_BASE, e); + } + log.info("Sent with state: " + state); + return state; + } + + public static String encodeWord(String word) throws UnsupportedEncodingException { + if (word == null) { + return null; + } + return MimeUtility.encodeWord(word, StandardCharsets.UTF_8.name(), null); + } +} 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..b3b7ab784 --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceExecutor.java @@ -0,0 +1,94 @@ +package ru.javaops.masterjava.service.mail; + +import akka.dispatch.Futures; +import lombok.extern.slf4j.Slf4j; +import one.util.streamex.StreamEx; +import ru.javaops.masterjava.service.mail.util.MailUtils; +import ru.javaops.masterjava.service.mail.util.MailUtils.MailObject; +import ru.javaops.web.WebStateException; +import scala.concurrent.ExecutionContext; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.*; + +@Slf4j +public class MailServiceExecutor { + + 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 static final ExecutorService mailExecutor = Executors.newFixedThreadPool(8); + + public static GroupResult sendBulk(final MailObject mailObject) { + return sendBulk(MailUtils.split(mailObject.getUsers()), + mailObject.getSubject(), mailObject.getBody(), MailUtils.getAttaches(mailObject.getAttaches())); + } + + public static GroupResult sendBulk(final Set addressees, final String subject, final String body, List attaches) { + final CompletionService completionService = new ExecutorCompletionService<>(mailExecutor); + + List> futures = StreamEx.of(addressees) + .map(addressee -> completionService.submit(() -> MailSender.sendTo(addressee, subject, body, attaches))) + .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); + } + } + GroupResult groupResult = new GroupResult(success, failed, null); + log.info("groupResult: {}", groupResult); + return groupResult; + } + + private GroupResult cancelWithFail(String cause) { + futures.forEach(f -> f.cancel(true)); + return new GroupResult(success, failed, cause); + } + }.call(); + } + + public static scala.concurrent.Future sendAsyncWithReply(MailObject mailObject, ExecutionContext ec) { + // http://doc.akka.io/docs/akka/current/java/futures.html + return Futures.future(() -> sendBulk(mailObject), ec); + } + + public static void sendAsync(MailObject mailObject) { + Set addressees = MailUtils.split(mailObject.getUsers()); + addressees.forEach(addressee -> + mailExecutor.submit(() -> { + try { + MailSender.sendTo(addressee, mailObject.getSubject(), mailObject.getBody(), MailUtils.getAttaches(mailObject.getAttaches())); + } catch (WebStateException e) { + // already logged + } + }) + ); + } +} \ 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..3bad1b536 --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java @@ -0,0 +1,44 @@ +package ru.javaops.masterjava.service.mail; + +import ru.javaops.web.WebStateException; + +import javax.jws.HandlerChain; +import javax.jws.WebService; +import javax.xml.ws.soap.MTOM; +import java.util.List; +import java.util.Set; + +@WebService(endpointInterface = "ru.javaops.masterjava.service.mail.MailService", targetNamespace = "http://mail.javaops.ru/" +// , wsdlLocation = "WEB-INF/wsdl/mailService.wsdl" +) +//@StreamingAttachment(parseEagerly=true, memoryThreshold=40000L) +@MTOM +@HandlerChain(file = "mailWsHandlers.xml") +public class MailServiceImpl implements MailService { + +// @Resource +// private WebServiceContext wsContext; + + @Override + public String sendToGroup(Set to, Set cc, String subject, String body, List attaches) throws WebStateException { +/* + MessageContext mCtx = wsContext.getMessageContext(); + Map> headers = (Map>) mCtx.get(MessageContext.HTTP_REQUEST_HEADERS); + + HttpServletRequest request = (HttpServletRequest) mCtx.get(MessageContext.SERVLET_REQUEST); + HttpServletResponse response = (HttpServletResponse) mCtx.get(MessageContext.SERVLET_RESPONSE); + + int code = AuthUtil.checkBasicAuth(headers, MailWSClient.AUTH_HEADER); + if (code != 0) { + mCtx.put(MessageContext.HTTP_RESPONSE_CODE, code); + throw new SecurityException(); + } +*/ + return MailSender.sendToGroup(to, cc, subject, body, attaches); + } + + @Override + public GroupResult sendBulk(Set to, String subject, String body, List attaches) throws WebStateException { + return MailServiceExecutor.sendBulk(to, subject, body, attaches); + } +} \ No newline at end of file diff --git a/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/listeners/AkkaMailListener.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/listeners/AkkaMailListener.java new file mode 100644 index 000000000..a107db375 --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/listeners/AkkaMailListener.java @@ -0,0 +1,49 @@ +package ru.javaops.masterjava.service.mail.listeners; + +import akka.actor.AbstractActor; +import akka.japi.Creator; +import lombok.extern.slf4j.Slf4j; +import ru.javaops.masterjava.akka.AkkaActivator; +import ru.javaops.masterjava.service.mail.GroupResult; +import ru.javaops.masterjava.service.mail.MailRemoteService; +import ru.javaops.masterjava.service.mail.MailServiceExecutor; +import ru.javaops.masterjava.service.mail.util.MailUtils; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; + +@WebListener +@Slf4j +public class AkkaMailListener implements ServletContextListener { + private AkkaActivator akkaActivator; + + @Override + public void contextInitialized(ServletContextEvent sce) { + akkaActivator = AkkaActivator.start("MailService", "mail-service"); + akkaActivator.startTypedActor(MailRemoteService.class, "mail-remote-service", + (Creator) () -> + mailObject -> MailServiceExecutor.sendAsyncWithReply(mailObject, akkaActivator.getExecutionContext())); + akkaActivator.startActor(MailActor.class, "mail-actor"); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + akkaActivator.shutdown(); + } + + public static class MailActor extends AbstractActor { + @Override + public Receive createReceive() { + return receiveBuilder().match(MailUtils.MailObject.class, + mailObject -> { + log.info("Receive mail form webappActor"); + GroupResult groupResult = MailServiceExecutor.sendBulk(mailObject); + log.info("Send result to webappActor"); + sender().tell(groupResult, self()); + }) + .build(); + } + } + +} \ No newline at end of file diff --git a/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/listeners/JmsMailListener.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/listeners/JmsMailListener.java new file mode 100644 index 000000000..4b29b7079 --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/listeners/JmsMailListener.java @@ -0,0 +1,67 @@ +package ru.javaops.masterjava.service.mail.listeners; + +import lombok.extern.slf4j.Slf4j; +import org.apache.activemq.ActiveMQConnectionFactory; +import ru.javaops.masterjava.service.mail.MailServiceExecutor; +import ru.javaops.masterjava.service.mail.util.MailUtils.MailObject; + +import javax.jms.*; +import javax.naming.InitialContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; + +@WebListener +@Slf4j +public class JmsMailListener implements ServletContextListener { + private Thread listenerThread = null; + private QueueConnection connection; + + @Override + public void contextInitialized(ServletContextEvent sce) { + try { + InitialContext initCtx = new InitialContext(); + ActiveMQConnectionFactory connectionFactory = + (ActiveMQConnectionFactory) initCtx.lookup("java:comp/env/jms/ConnectionFactory"); + connectionFactory.setTrustAllPackages(true); + connection = connectionFactory.createQueueConnection(); + QueueSession queueSession = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); + Queue queue = (Queue) initCtx.lookup("java:comp/env/jms/queue/MailQueue"); + QueueReceiver receiver = queueSession.createReceiver(queue); + connection.start(); + log.info("Listen JMS messages ..."); + listenerThread = new Thread(() -> { + try { + while (!Thread.interrupted()) { + Message m = receiver.receive(); + if (m instanceof ObjectMessage) { + ObjectMessage om = (ObjectMessage) m; + MailObject mailObject = (MailObject) om.getObject(); + log.info("Received MailObject {}", mailObject); + MailServiceExecutor.sendAsync(mailObject); + } + } + } catch (Exception e) { + log.error("Receiving messages failed: " + e.getMessage(), e); + } + }); + listenerThread.start(); + } catch (Exception e) { + log.error("JMS failed: " + e.getMessage(), e); + } + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + if (connection != null) { + try { + connection.close(); + } catch (JMSException ex) { + log.warn("Couldn't close JMSConnection: ", ex); + } + } + if (listenerThread != null) { + listenerThread.interrupt(); + } + } +} \ No newline at end of file diff --git a/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/persist/MailCase.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/persist/MailCase.java new file mode 100644 index 000000000..2286a3966 --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/persist/MailCase.java @@ -0,0 +1,28 @@ +package ru.javaops.masterjava.service.mail.persist; + +import com.bertoncelj.jdbi.entitymapper.Column; +import com.google.common.base.Joiner; +import lombok.*; +import ru.javaops.masterjava.persist.model.BaseEntity; +import ru.javaops.masterjava.service.mail.Addressee; + +import java.util.Date; +import java.util.Set; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode // compare without id +@ToString +public class MailCase extends BaseEntity { + private @Column("list_to") String listTo; + private @Column("list_cc") String listCc; + private String subject; + private String body; + private String state; + private Date date; + + public static MailCase of(Set to, Set cc, String subject, String body, String state) { + return new MailCase(Joiner.on(", ").join(to), Joiner.on(", ").join(cc), subject, body, state, new Date()); + } +} \ No newline at end of file diff --git a/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/persist/MailCaseDao.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/persist/MailCaseDao.java new file mode 100644 index 000000000..d2915c53c --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/persist/MailCaseDao.java @@ -0,0 +1,24 @@ +package ru.javaops.masterjava.service.mail.persist; + +import com.bertoncelj.jdbi.entitymapper.EntityMapperFactory; +import org.skife.jdbi.v2.sqlobject.*; +import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapperFactory; +import ru.javaops.masterjava.persist.dao.AbstractDao; + +import java.util.Date; +import java.util.List; + +@RegisterMapperFactory(EntityMapperFactory.class) +public abstract class MailCaseDao implements AbstractDao { + + @SqlUpdate("TRUNCATE mail_hist") + @Override + public abstract void clean(); + + @SqlQuery("SELECT * FROM mail_hist WHERE date>=:after ORDER BY date DESC") + public abstract List getAfter(@Bind("after") Date date); + + @SqlUpdate("INSERT INTO mail_hist (list_to, list_cc, subject, body, state, date) VALUES (:listTo, :listCc, :subject, :body, :state, :date)") + @GetGeneratedKeys + public abstract int insert(@BindBean MailCase mails); +} diff --git a/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRS.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRS.java new file mode 100644 index 000000000..5337826e6 --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRS.java @@ -0,0 +1,57 @@ +package ru.javaops.masterjava.service.mail.rest; + + +import com.google.common.collect.ImmutableList; +import org.glassfish.jersey.media.multipart.BodyPartEntity; +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.FormDataParam; +import org.hibernate.validator.constraints.NotBlank; +import ru.javaops.masterjava.service.mail.Attach; +import ru.javaops.masterjava.service.mail.GroupResult; +import ru.javaops.masterjava.service.mail.MailServiceExecutor; +import ru.javaops.masterjava.service.mail.util.MailUtils; +import ru.javaops.web.WebStateException; + +import javax.activation.DataHandler; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import java.io.UnsupportedEncodingException; +import java.util.List; + +@Path("/") +public class MailRS { + @GET + @Path("test") + @Produces(MediaType.TEXT_PLAIN) + public String test() { + return "Test"; + } + + @POST + @Path("/send") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.MULTIPART_FORM_DATA) + public GroupResult send(@NotBlank @FormDataParam("users") String users, + @FormDataParam("subject") String subject, + @NotBlank @FormDataParam("body") String body, + @FormDataParam("attach") FormDataBodyPart attachBodyPart) throws WebStateException { + + final List attaches; + String attachName = attachBodyPart.getContentDisposition().getFileName(); + + if (attachName.isEmpty()) { + attaches = ImmutableList.of(); + } else { + try { +// UTF-8 encoding workaround: https://java.net/jira/browse/JERSEY-3032 + String utf8name = new String(attachName.getBytes("ISO8859_1"), "UTF-8"); + BodyPartEntity bodyPartEntity = ((BodyPartEntity) attachBodyPart.getEntity()); + + attaches = ImmutableList.of(new Attach(utf8name, new DataHandler((MailUtils.ProxyDataSource) bodyPartEntity::getInputStream))); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + } + return MailServiceExecutor.sendBulk(MailUtils.split(users), subject, body, attaches); + } +} \ No newline at end of file diff --git a/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRestConfig.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRestConfig.java new file mode 100644 index 000000000..0a1fa5cc5 --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRestConfig.java @@ -0,0 +1,19 @@ +package ru.javaops.masterjava.service.mail.rest; + +import org.glassfish.jersey.media.multipart.MultiPartFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.slf4j.bridge.SLF4JBridgeHandler; + +import javax.ws.rs.ApplicationPath; + +@ApplicationPath("rest") +public class MailRestConfig extends ResourceConfig { + + public MailRestConfig() { + // Set Jersey log to SLF4J instead of JUL + // http://stackoverflow.com/questions/4121722 + SLF4JBridgeHandler.install(); + packages("ru.javaops.masterjava.service.mail.rest"); + register(MultiPartFeature.class); + } +} \ No newline at end of file diff --git a/services/mail-service/src/main/resources/logback.xml b/services/mail-service/src/main/resources/logback.xml new file mode 100644 index 000000000..be7988a95 --- /dev/null +++ b/services/mail-service/src/main/resources/logback.xml @@ -0,0 +1,46 @@ + + + + + + true + + + + + + + + + ${LOG_DIR}/mail.log + + UTF-8 + %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] %-5level %logger{0} [%file:%line] - %msg%n + + + + ${LOG_DIR}/archived/mail.%d{yyyy-MM-dd}.%i.log + + + 5MB + + + + + + + UTF-8 + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} [%file:%line] - %msg%n + + + + + + + + + + + + \ No newline at end of file diff --git a/services/mail-service/src/main/resources/mail.conf b/services/mail-service/src/main/resources/mail.conf new file mode 100644 index 000000000..0537e61e5 --- /dev/null +++ b/services/mail-service/src/main/resources/mail.conf @@ -0,0 +1,12 @@ +mail { + host: smtp.yandex.ru + port: 465 + username: "user@yandex.ru" + password: password + useSSL: true + useTLS: false + debug: false + fromName: MasterJava +} + +include required(file("/home/konst/work/masterjava/config/mail.conf")) \ No newline at end of file diff --git a/services/mail-service/src/main/resources/mailWsHandlers.xml b/services/mail-service/src/main/resources/mailWsHandlers.xml new file mode 100644 index 000000000..14ac001d0 --- /dev/null +++ b/services/mail-service/src/main/resources/mailWsHandlers.xml @@ -0,0 +1,12 @@ + + + + MailLoggingHandler + ru.javaops.masterjava.service.mail.MailHandlers$LoggingHandler + + + SoapStatisticHandler + ru.javaops.web.handler.SoapStatisticHandler + + + \ No newline at end of file diff --git a/services/mail-service/src/main/webapp/WEB-INF/sun-jaxws.xml b/services/mail-service/src/main/webapp/WEB-INF/sun-jaxws.xml new file mode 100644 index 000000000..763d86504 --- /dev/null +++ b/services/mail-service/src/main/webapp/WEB-INF/sun-jaxws.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/services/mail-service/src/main/webapp/WEB-INF/web.xml b/services/mail-service/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..5a4bd7190 --- /dev/null +++ b/services/mail-service/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,25 @@ + + + + + mailService + /mailService + + + tomcat + + + + + BASIC + Tomcat basic auth + + + + tomcat + + 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..722112b14 --- /dev/null +++ b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java @@ -0,0 +1,40 @@ +package ru.javaops.masterjava.service.mail; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import ru.javaops.web.WebStateException; + +import javax.activation.DataHandler; +import javax.xml.namespace.QName; +import javax.xml.ws.Service; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; + +public class MailServiceClient { + + public static void main(String[] args) throws MalformedURLException { + Service service = Service.create( + new URL("http://localhost:8080/mail/mailService?wsdl"), + new QName("http://mail.javaops.ru/", "MailServiceImplService")); + + MailService mailService = service.getPort(MailService.class); + + ImmutableSet addressees = ImmutableSet.of( + new Addressee("Мастер Java ")); + + List attaches = ImmutableList.of( + new Attach("version.html", new DataHandler(new File("config_templates/version.html").toURI().toURL()))); + + try { + String status = mailService.sendToGroup(addressees, ImmutableSet.of(), "Bulk email subject", "Bulk email body", attaches); + System.out.println(status); + + GroupResult groupResult = mailService.sendBulk(addressees, "Individual mail subject", "Individual mail body", attaches); + System.out.println(groupResult); + } catch (WebStateException e) { + System.out.println(e); + } + } +} 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..1d77f0f27 --- /dev/null +++ b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServicePublisher.java @@ -0,0 +1,25 @@ +package ru.javaops.masterjava.service.mail; + +import com.google.common.collect.ImmutableList; +import ru.javaops.masterjava.config.Configs; +import ru.javaops.masterjava.persist.DBITestProvider; + +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import javax.xml.ws.Endpoint; +import java.util.List; + +public class MailServicePublisher { + + public static void main(String[] args) { + DBITestProvider.initDBI(); + + Endpoint endpoint = Endpoint.create(new MailServiceImpl()); + List metadata = ImmutableList.of( + new StreamSource(Configs.getConfigFile("wsdl/mailService.wsdl")), + new StreamSource(Configs.getConfigFile("wsdl/common.xsd"))); + + endpoint.setMetadata(metadata); + endpoint.publish("http://localhost:8080/mail/mailService"); + } +} diff --git a/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/persist/MailCaseDaoTest.java b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/persist/MailCaseDaoTest.java new file mode 100644 index 000000000..2f6253501 --- /dev/null +++ b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/persist/MailCaseDaoTest.java @@ -0,0 +1,22 @@ +package ru.javaops.masterjava.service.mail.persist; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import ru.javaops.masterjava.persist.dao.AbstractDaoTest; + +public class MailCaseDaoTest extends AbstractDaoTest { + public MailCaseDaoTest() { + super(MailCaseDao.class); + } + + @Before + public void setUp() throws Exception { + MailCaseTestData.setUp(); + } + + @Test + public void getAll() throws Exception { + Assert.assertEquals(MailCaseTestData.MAIL_CASES, dao.getAfter(MailCaseTestData.DATE_FROM)); + } +} \ No newline at end of file diff --git a/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/persist/MailCaseTestData.java b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/persist/MailCaseTestData.java new file mode 100644 index 000000000..3ba350895 --- /dev/null +++ b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/persist/MailCaseTestData.java @@ -0,0 +1,48 @@ +package ru.javaops.masterjava.service.mail.persist; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import ru.javaops.masterjava.persist.DBIProvider; +import ru.javaops.masterjava.service.mail.Addressee; + +import java.time.Duration; +import java.time.Instant; +import java.util.Date; +import java.util.List; + +/** + * gkislin + * 26.11.2016 + */ +public class MailCaseTestData { + private static final Instant now = Instant.now(); + static final Date DATE_FROM = Date.from(now.minus(Duration.ofDays(1))); + + static final List MAIL_CASES = ImmutableList.of( + MailCase.of( + ImmutableSet.of( + new Addressee("ИмяTo1 Фамилия1 "), + new Addressee("Имя2 Фамилия2 ")), + ImmutableSet.of( + new Addressee("ИмяCc1 Фамилия1 "), + new Addressee("ИмяCc2 Фамилия2 ")), + "subject1", "body1", "state1" + ), + new MailCase("toMail2@ya.ru", null, "subject2", "body2", "state2", + Date.from(now.minus(Duration.ofMinutes(1)))), + new MailCase(null, "ccMail3@ya.ru", "subject3", "body3", "state3", DATE_FROM) + ); + + static final MailCase MAIL_CASE_EXCLUDED = + new MailCase("toMail4@ya.ru", "ccMail4@ya.ru", "subject4", "body4", "state4", + Date.from(now.minus(Duration.ofDays(2)))); + + public static void setUp() { + MailCaseDao dao = DBIProvider.getDao(MailCaseDao.class); + dao.clean(); + DBIProvider.getDBI().useTransaction((conn, status) -> { + MAIL_CASES.forEach(dao::insert); + dao.insert(MAIL_CASE_EXCLUDED); + }); + } +} diff --git a/services/pom.xml b/services/pom.xml new file mode 100644 index 000000000..9bc70b43a --- /dev/null +++ b/services/pom.xml @@ -0,0 +1,17 @@ + + 4.0.0 + + ru.javaops + services + pom + 1.0-SNAPSHOT + + MasterJava Services + + akka-remote + common-ws + mail-api + mail-service + + diff --git a/sql/databaseChangeLog.sql b/sql/databaseChangeLog.sql new file mode 100644 index 000000000..af0b27954 --- /dev/null +++ b/sql/databaseChangeLog.sql @@ -0,0 +1,49 @@ +--liquibase formatted sql + +--changeset gkislin:1 +CREATE SEQUENCE common_seq START 100000; + +CREATE TABLE city ( + id INTEGER PRIMARY KEY DEFAULT nextval('common_seq'), + ref TEXT UNIQUE, + name TEXT NOT NULL +); + +ALTER TABLE users + ADD COLUMN city_id INTEGER REFERENCES city (id); + +--changeset gkislin:2 +CREATE TABLE project ( + id INTEGER PRIMARY KEY DEFAULT nextval('common_seq'), + name TEXT NOT NULL UNIQUE, + description TEXT +); + +CREATE TYPE GROUP_TYPE AS ENUM ('REGISTERING', 'CURRENT', 'FINISHED'); + +CREATE TABLE groups ( + id INTEGER PRIMARY KEY DEFAULT nextval('common_seq'), + name TEXT NOT NULL UNIQUE, + type GROUP_TYPE NOT NULL, + project_id INTEGER NOT NULL REFERENCES project (id) +); + +CREATE TABLE user_group ( + user_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE, + group_id INTEGER NOT NULL REFERENCES groups (id), + CONSTRAINT users_group_idx UNIQUE (user_id, group_id) +); + +--changeset gkislin:3 +CREATE TABLE mail_hist ( + id SERIAL PRIMARY KEY, + list_to TEXT NULL, + list_cc TEXT NULL, + subject TEXT NULL, + body TEXT NULL, + state TEXT NOT NULL, + date TIMESTAMP NOT NULL +); + +COMMENT ON TABLE mail_hist IS 'История отправки email'; +COMMENT ON COLUMN mail_hist.date IS 'Время отправки'; \ No newline at end of file diff --git a/sql/initDB.sql b/sql/initDB.sql new file mode 100644 index 000000000..09b463d69 --- /dev/null +++ b/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/sql/lb_apply.bat b/sql/lb_apply.bat new file mode 100644 index 000000000..80f23598b --- /dev/null +++ b/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/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..3196e8a05 --- /dev/null +++ b/web/export/pom.xml @@ -0,0 +1,42 @@ + + + 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 + + + + + + \ 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..f6b4e8be3 --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/export/CityImporter.java @@ -0,0 +1,38 @@ +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 javax.xml.stream.events.XMLEvent; +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(); + String element; + + while ((element = processor.doUntilAny(XMLEvent.START_ELEMENT, "City", "Users")) != null) { + if (element.equals("Users")) break; + 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..1879a6018 --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/export/PayloadImporter.java @@ -0,0 +1,33 @@ +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 ProjectGroupImporter projectGroupImporter = new ProjectGroupImporter(); + 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 groups = projectGroupImporter.process(processor); + val cities = cityImporter.process(processor); + return userImporter.process(processor, groups, cities, chunkSize); + } +} diff --git a/web/export/src/main/java/ru/javaops/masterjava/export/ProjectGroupImporter.java b/web/export/src/main/java/ru/javaops/masterjava/export/ProjectGroupImporter.java new file mode 100644 index 000000000..d9b2c9159 --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/export/ProjectGroupImporter.java @@ -0,0 +1,53 @@ +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.GroupDao; +import ru.javaops.masterjava.persist.dao.ProjectDao; +import ru.javaops.masterjava.persist.model.Group; +import ru.javaops.masterjava.persist.model.GroupType; +import ru.javaops.masterjava.persist.model.Project; +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.Map; + +@Slf4j +public class ProjectGroupImporter { + private final ProjectDao projectDao = DBIProvider.getDao(ProjectDao.class); + private final GroupDao groupDao = DBIProvider.getDao(GroupDao.class); + + public Map process(StaxStreamProcessor processor) throws XMLStreamException { + val projectMap = projectDao.getAsMap(); + val groupMap = groupDao.getAsMap(); + String element; + + val newGroups = new ArrayList(); + Project project = null; + while ((element = processor.doUntilAny(XMLEvent.START_ELEMENT, "Project", "Group", "Cities")) != null) { + if (element.equals("Cities")) break; + if (element.equals("Project")) { + val name = processor.getAttribute("name"); + val description = processor.getElementValue("description"); + project = projectMap.get(name); + if (project == null) { + project = new Project(name, description); + log.info("Insert project " + project); + projectDao.insert(project); + } + } else { + val name = processor.getAttribute("name"); + if (!groupMap.containsKey(name)) { + // project here already assigned, as it located in xml before Group + newGroups.add(new Group(name, GroupType.valueOf(processor.getAttribute("type")), project.getId())); + } + } + } + log.info("Insert groups " + newGroups); + groupDao.insertBatch(newGroups); + return groupDao.getAsMap(); + } +} 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..5ce55a638 --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/export/UserImporter.java @@ -0,0 +1,126 @@ +package ru.javaops.masterjava.export; + +import com.google.common.base.Splitter; +import lombok.Value; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +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.dao.UserGroupDao; +import ru.javaops.masterjava.persist.model.*; +import ru.javaops.masterjava.xml.util.StaxStreamProcessor; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +@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); + private UserGroupDao userGroupDao = DBIProvider.getDao(UserGroupDao.class); + + public List process(StaxStreamProcessor processor, Map groups, Map cities, int chunkSize) throws XMLStreamException { + log.info("Start proseccing with chunkSize=" + chunkSize); + + @Value + class ChunkItem { + private User user; + private StreamEx userGroups; + } + + 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 { + val futures = new ArrayList(); + int id = userDao.getSeqAndSkip(chunkSize); + List chunk = new ArrayList<>(chunkSize); + val 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 { + val groupRefs = processor.getAttribute("groupRefs"); + List groupNames = (groupRefs == null) ? + Collections.emptyList() : + Splitter.on(' ').splitToList(groupRefs); + + if (!groups.keySet().containsAll(groupNames)) { + failed.add(new FailedEmail(email, "One of group from '" + groupRefs + "' is not present in DB")); + } else { + final UserFlag flag = UserFlag.valueOf(processor.getAttribute("flag")); + final String fullName = processor.getText(); + final User user = new User(id++, fullName, email, flag, city.getId()); + StreamEx userGroups = StreamEx.of(groupNames).map(name -> new UserGroup(user.getId(), groups.get(name).getId())); + chunk.add(new ChunkItem(user, userGroups)); + 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) { + val users = StreamEx.of(chunk).map(ChunkItem::getUser).toList(); + ChunkFuture chunkFuture = new ChunkFuture( + users, + executorService.submit(() -> { + List alreadyPresents = userDao.insertAndGetConflictEmails(users); + Set alreadyPresentsIds = StreamEx.of(alreadyPresents).map(User::getId).toSet(); + userGroupDao.insertBatch( + StreamEx.of(chunk).flatMap(ChunkItem::getUserGroups) + .filter(ug -> !alreadyPresentsIds.contains(ug.getUserId())) + .toList() + ); + return StreamEx.of(alreadyPresents).map(User::getEmail).toList(); + }) + ); + 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..56fc888f4 --- /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 synchronized 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..ce507b764 --- /dev/null +++ b/web/export/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java @@ -0,0 +1,71 @@ +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 XMLStreamReader getReader() { + return reader; + } + + public boolean doUntil(int stopEvent, String value) throws XMLStreamException { + return doUntilAny(stopEvent, value) != null; + } + + public String getAttribute(String name) throws XMLStreamException { + return reader.getAttributeValue(null, name); + } + + public String doUntilAny(int stopEvent, String... values) throws XMLStreamException { + while (reader.hasNext()) { + int event = reader.next(); + if (event == stopEvent) { + String xmlValue = getValue(event); + for (String value : values) { + if (value.equals(xmlValue)) { + return xmlValue; + } + } + } + } + return null; + } + + 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..dcb6901a2 --- /dev/null +++ b/web/export/src/main/resources/groups.xsl @@ -0,0 +1,36 @@ + + + + + + + + + <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..a4cd759b1 --- /dev/null +++ b/web/export/src/test/java/ru/javaops/masterjava/MainXml.java @@ -0,0 +1,144 @@ +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("Format: 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<>(); + + // Projects loop + projects: + while (processor.doUntil(XMLEvent.START_ELEMENT, "Project")) { + if (projectName.equals(processor.getAttribute("name"))) { + // Groups loop + String element; + while ((element = processor.doUntilAny(XMLEvent.START_ELEMENT, "Project", "Group", "Users")) != null) { + if (!element.equals("Group")) { + break projects; + } + groupNames.add(processor.getAttribute("name")); + } + } + } + if (groupNames.isEmpty()) { + throw new IllegalArgumentException("Invalid " + projectName + " or no groups"); + } + + // Users loop + Set users = new TreeSet<>(USER_COMPARATOR); + + while (processor.doUntil(XMLEvent.START_ELEMENT, "User")) { + 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/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..8763bbab6 --- /dev/null +++ b/web/webapp/pom.xml @@ -0,0 +1,48 @@ + + + 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} + + + ${project.groupId} + mail-api + ${project.version} + + + + org.apache.activemq + activemq-client + provided + 5.14.3 + + + org.slf4j + slf4j-api + + + + + \ No newline at end of file diff --git a/web/webapp/src/main/java/ru/javaops/masterjava/webapp/JmsSendServlet.java b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/JmsSendServlet.java new file mode 100644 index 000000000..7303f9661 --- /dev/null +++ b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/JmsSendServlet.java @@ -0,0 +1,65 @@ +package ru.javaops.masterjava.webapp; + +import lombok.extern.slf4j.Slf4j; +import ru.javaops.masterjava.service.mail.util.MailUtils.MailObject; + +import javax.jms.*; +import javax.naming.InitialContext; +import javax.servlet.ServletConfig; +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 java.io.IOException; +import java.lang.IllegalStateException; + +import static ru.javaops.masterjava.webapp.WebUtil.doAndWriteResponse; + +@WebServlet("/sendJms") +@Slf4j +@MultipartConfig +public class JmsSendServlet extends HttpServlet { + private Connection connection; + private Session session; + private MessageProducer producer; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + try { + InitialContext initCtx = new InitialContext(); + ConnectionFactory connectionFactory = (ConnectionFactory) initCtx.lookup("java:comp/env/jms/ConnectionFactory"); + connection = connectionFactory.createConnection(); + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + producer = session.createProducer((Destination) initCtx.lookup("java:comp/env/jms/queue/MailQueue")); + } catch (Exception e) { + throw new IllegalStateException("JMS init failed", e); + } + } + + @Override + public void destroy() { + if (connection != null) { + try { + connection.close(); + } catch (JMSException ex) { + log.warn("Couldn't close JMSConnection: ", ex); + } + } + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + req.setCharacterEncoding("UTF-8"); + doAndWriteResponse(resp, () -> sendJms(WebUtil.createMailObject(req))); + } + + private synchronized String sendJms(MailObject mailObject) throws JMSException { + ObjectMessage om = session.createObjectMessage(); + om.setObject(mailObject); + producer.send(om); + return "Successfully sent JMS message."; + } +} \ No newline at end of file diff --git a/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SoapSendServlet.java b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SoapSendServlet.java new file mode 100644 index 000000000..a4b8e0ff4 --- /dev/null +++ b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SoapSendServlet.java @@ -0,0 +1,42 @@ +package ru.javaops.masterjava.webapp; + +import com.google.common.collect.ImmutableList; +import lombok.extern.slf4j.Slf4j; +import ru.javaops.masterjava.service.mail.Attach; +import ru.javaops.masterjava.service.mail.MailWSClient; +import ru.javaops.masterjava.service.mail.util.MailUtils; + +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.util.List; + +import static ru.javaops.masterjava.webapp.WebUtil.*; + +@WebServlet("/sendSoap") +@Slf4j +@MultipartConfig +public class SoapSendServlet extends HttpServlet { + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + req.setCharacterEncoding("UTF-8"); + doAndWriteResponse(resp, () -> { + String users = getNotEmptyUsers(req); + String subject = req.getParameter("subject"); + String body = getNotEmptyParam(req, "body"); + List attaches; + Part filePart = req.getPart("attach"); + if (filePart == null) { + attaches = ImmutableList.of(); + } else { + attaches = ImmutableList.of(MailUtils.getAttach(filePart.getSubmittedFileName(), filePart.getInputStream())); + } + return MailWSClient.sendBulk(MailUtils.split(users), subject, body, attaches).toString(); + }); + } +} 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/java/ru/javaops/masterjava/webapp/WebUtil.java b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/WebUtil.java new file mode 100644 index 000000000..cdafd397a --- /dev/null +++ b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/WebUtil.java @@ -0,0 +1,77 @@ +package ru.javaops.masterjava.webapp; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import ru.javaops.masterjava.service.mail.util.MailUtils; +import ru.javaops.masterjava.util.Functions; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.Part; +import java.io.IOException; +import java.util.AbstractMap; +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; + +@Slf4j +public class WebUtil { + + public static void doAsync(HttpServletResponse resp, Functions.RunnableEx doer) throws IOException { + resp.setCharacterEncoding("UTF-8"); + try { + log.info("Start asynchronous processing"); + doer.run(); + log.info("Asynchronous processing running ..."); + } catch (Exception e) { + log.error("Asynchronous processing failed", e); + String message = e.getMessage(); + String result = (message != null) ? message : e.getClass().getName(); + resp.getWriter().write(result); + } + } + + public static void doAndWriteResponse(HttpServletResponse resp, Functions.SupplierEx doer) throws IOException { + resp.setCharacterEncoding("UTF-8"); + String result; + try { + log.info("Start processing"); + result = doer.get(); + log.info("Processing finished with result: {}", result); + } catch (Exception e) { + log.error("Processing failed", e); + String message = e.getMessage(); + result = (message != null) ? message : e.getClass().getName(); + } + resp.getWriter().write(result); + } + + public static String getNotEmptyUsers(HttpServletRequest req) { + String users = req.getParameter("users"); + checkArgument(!Strings.isNullOrEmpty(users), "Addresses are not selected"); + return users; + } + + public static String getNotEmptyParam(HttpServletRequest req, String param) { + String value = req.getParameter(param); + checkArgument(!Strings.isNullOrEmpty(value), param + " must not be empty"); + return value; + } + + public static MailUtils.MailObject createMailObject(HttpServletRequest req) throws IOException, ServletException { + Part filePart = req.getPart("attach"); + + List> attaches; + if (filePart.getSize() == 0) { + attaches = ImmutableList.of(); + } else { + attaches = ImmutableList.of( + new AbstractMap.SimpleImmutableEntry<>(filePart.getSubmittedFileName(), IOUtils.toByteArray(filePart.getInputStream())) + ); + } + return new MailUtils.MailObject(getNotEmptyUsers(req), req.getParameter("subject"), getNotEmptyParam(req, "body"), attaches); + } +} diff --git a/web/webapp/src/main/java/ru/javaops/masterjava/webapp/akka/AkkaActorSendServlet.java b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/akka/AkkaActorSendServlet.java new file mode 100644 index 000000000..b9594dc53 --- /dev/null +++ b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/akka/AkkaActorSendServlet.java @@ -0,0 +1,88 @@ +package ru.javaops.masterjava.webapp.akka; + +import akka.actor.AbstractActor; +import akka.actor.ActorRef; +import akka.actor.Props; +import lombok.extern.slf4j.Slf4j; +import ru.javaops.masterjava.service.mail.GroupResult; +import ru.javaops.masterjava.service.mail.util.MailUtils.MailObject; + +import javax.servlet.AsyncContext; +import javax.servlet.ServletConfig; +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 java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static ru.javaops.masterjava.webapp.WebUtil.createMailObject; +import static ru.javaops.masterjava.webapp.WebUtil.doAsync; +import static ru.javaops.masterjava.webapp.akka.AkkaWebappListener.akkaActivator; + +@WebServlet(value = "/sendAkkaActor", loadOnStartup = 1, asyncSupported = true) +@Slf4j +@MultipartConfig +public class AkkaActorSendServlet extends HttpServlet { + private ActorRef mailActor; + private ExecutorService executorService; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + mailActor = akkaActivator.getActorRef("akka.tcp://MailService@127.0.0.1:2553/user/mail-actor"); + executorService = Executors.newFixedThreadPool(8); + } + + @Override + public void destroy() { + super.destroy(); + if (executorService != null) { + log.info("shutdown"); + executorService.shutdown(); + try { + if (!executorService.awaitTermination(3, TimeUnit.SECONDS)) { + log.info("shutdownNow"); + executorService.shutdownNow(); + } + } catch (InterruptedException e) { //nothing + } + } + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + req.setCharacterEncoding("UTF-8"); + doAsync(resp, () -> { + MailObject mailObject = createMailObject(req); + final AsyncContext ac = req.startAsync(); + executorService.submit(() -> { + ActorRef webappActor = akkaActivator.startActor(Props.create(WebappActor.class, ac)); + mailActor.tell(mailObject, webappActor); + }); + }); + } + + public static class WebappActor extends AbstractActor { + private final AsyncContext asyncCtx; + + public WebappActor(AsyncContext ac) { + this.asyncCtx = ac; + } + + @Override + public Receive createReceive() { + return receiveBuilder().match(GroupResult.class, + groupResult -> { + log.info("Receive result form mailActor"); + asyncCtx.getResponse().getWriter().write(groupResult.toString()); + asyncCtx.complete(); + }) + .build(); + } + } +} \ No newline at end of file diff --git a/web/webapp/src/main/java/ru/javaops/masterjava/webapp/akka/AkkaTypedSendServlet.java b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/akka/AkkaTypedSendServlet.java new file mode 100644 index 000000000..db614a790 --- /dev/null +++ b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/akka/AkkaTypedSendServlet.java @@ -0,0 +1,56 @@ +package ru.javaops.masterjava.webapp.akka; + +import lombok.extern.slf4j.Slf4j; +import ru.javaops.masterjava.service.mail.GroupResult; +import ru.javaops.masterjava.service.mail.MailRemoteService; +import ru.javaops.masterjava.service.mail.util.MailUtils.MailObject; +import ru.javaops.masterjava.util.Exceptions; +import scala.concurrent.Await; +import scala.concurrent.duration.Duration; + +import javax.servlet.AsyncContext; +import javax.servlet.ServletConfig; +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 java.io.IOException; + +import static ru.javaops.masterjava.webapp.WebUtil.*; +import static ru.javaops.masterjava.webapp.akka.AkkaWebappListener.akkaActivator; + +@WebServlet(value = "/sendAkkaTyped", loadOnStartup = 1, asyncSupported = true) +@Slf4j +@MultipartConfig +public class AkkaTypedSendServlet extends HttpServlet { + + private MailRemoteService mailService; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + mailService = akkaActivator.getTypedRef(MailRemoteService.class, "akka.tcp://MailService@127.0.0.1:2553/user/mail-remote-service"); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + req.setCharacterEncoding("UTF-8"); + // https://dzone.com/articles/limited-usefulness + doAsync(resp, () -> { + MailObject mailObject = createMailObject(req); + + final AsyncContext ac = req.startAsync(); + ac.start(Exceptions.wrap(() -> { + doAndWriteResponse((HttpServletResponse) ac.getResponse(), () -> { + scala.concurrent.Future future = mailService.sendBulk(mailObject); + log.info("Receive future, await result ..."); + GroupResult groupResult = Await.result(future, Duration.create(10, "seconds")); + return groupResult.toString(); + }); + ac.complete(); + })); + }); + } +} \ No newline at end of file diff --git a/web/webapp/src/main/java/ru/javaops/masterjava/webapp/akka/AkkaWebappListener.java b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/akka/AkkaWebappListener.java new file mode 100644 index 000000000..d988b7858 --- /dev/null +++ b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/akka/AkkaWebappListener.java @@ -0,0 +1,24 @@ +package ru.javaops.masterjava.webapp.akka; + +import lombok.extern.slf4j.Slf4j; +import ru.javaops.masterjava.akka.AkkaActivator; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; + +@WebListener +@Slf4j +public class AkkaWebappListener implements ServletContextListener { + public static AkkaActivator akkaActivator; + + @Override + public void contextInitialized(ServletContextEvent sce) { + akkaActivator = AkkaActivator.start("WebApp", "webapp"); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + akkaActivator.shutdown(); + } +} \ No newline at end of file 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..b20fcfdc7 --- /dev/null +++ b/web/webapp/src/main/webapp/WEB-INF/templates/users.html @@ -0,0 +1,78 @@ + + + + Users + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
#Full NameEmailFlag +
+
+ SOAP
+ REST
+ JMS
+ AKKA Typed
+ AKKA Untyped
+ +
+ + + +

+ +

+

+
+

+

+ +

+

+ +

+
+
+ + + \ No newline at end of file