From 22fc85d3f3eeee18024c3ed4b46047f52e92d811 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 17 Oct 2017 11:50:59 +0300 Subject: [PATCH 01/83] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 127b1304a..a6de25e17 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ - используя асинхронные сервлеты 3.0 - сохранение данных в PostgreSQL используя [jDBI](http://jdbi.org/) - миграция базы [LiquiBase](http://www.liquibase.org/) -- использование в проекте [Guava](https://github.com/google/guava/wiki), [Thymleaf](http://www.thymeleaf.org/), [Lombook](https://projectlombok.org/), [StreamEx](https://github.com/amaembo/streamex), +- использование в проекте [Guava](https://github.com/google/guava/wiki), [Thymleaf](http://www.thymeleaf.org/), [Lombok](https://projectlombok.org/), [StreamEx](https://github.com/amaembo/streamex), [Typesafe Config](https://github.com/typesafehub/config), [Java Microbenchmark JMH](http://openjdk.java.net/projects/code-tools/jmh) ### Требование к участникам @@ -134,14 +134,14 @@ MatrixBenchmark.concurrentMultiply3 1000 ss 100 186,827 ± 11,882 - Maven. Поиск и разрешение конфликтов зависимостей - Подключаем логирование с общими настройкам - Библиотеки и фреймворки для работы с JDBC. -- Модуль persist +- Модуль persistence ## Занятие 5 - Разбор ДЗ - Сохранение в базу в batch-моде с обработкой конфликтов - Вставка в несколько потоков - Конфигурирование приложения (Typesafe config) -- Lombook +- Lombok ## Занятие 6 - Разбор ДЗ (доработка модели и модуля export) From 66dbb57f307e8ac7f01f61af948a3a315a40b4f5 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 23 Oct 2017 05:31:40 +0300 Subject: [PATCH 02/83] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a6de25e17..2d26e390c 100644 --- a/README.md +++ b/README.md @@ -99,9 +99,9 @@ ----- ## ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Подсказки по HW1 -- не делайте 1000 000 тасок, лучше их сделать крупнее -- у меня разница между 4 и 1000 тасками по времени незаметна, поэтому делайте просто и не делайте сложно -- наконец: можно не считать значение элемента результирующей матрицы C за раз, а накапливать (`concurrentMultiply3`). Мои результаты: +- 1: не делайте 1000 000 тасок, лучше их сделать крупнее +- 2: у меня разница между 4 и 1000 тасками по времени незаметна, поэтому делайте просто и не делайте сложно +- 3: наконец: можно не считать значение элемента результирующей матрицы C за раз, а накапливать (`concurrentMultiply3`). Тогда трансформация B не нужна. Мои результаты: ``` Benchmark (matrixSize) Mode Cnt Score Error Units MatrixBenchmark.singleThreadMultiplyOpt 1000 ss 100 837,867 ± 25,530 ms/op From 4b3a183635f38c7190971038b7a720395f808f41 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 22 Feb 2018 18:07:56 +0300 Subject: [PATCH 03/83] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2d26e390c..6157a56d3 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ #### Скачайте [1_1_MailService.patch](https://drive.google.com/open?id=0B9Ye2auQ_NsFTE5ZV3pzWElxTWM), положите его в проект, правой мышкой на нем сделайте Apply Patch ... ---------------------------- +- [Как сделать Java код проще и нагляднее](https://habrahabr.ru/company/wrike/blog/349652/) ### Ресурсы (основы) - Intuit, Потоки выполнения. Синхронизация From 389d911b60d0e1ddca77dbde8c9d34311e5f115a Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 8 Nov 2018 16:37:46 +0300 Subject: [PATCH 04/83] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6157a56d3..e5fe3209b 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ > В видео в `LazySingleton` ошибка: должно быть как в коде проекта `instance == null` ### Структура памяти: куча, стек, permanent/metaspace - - JVM изнутри - оптимизация и профилирование. + - JVM изнутри - оптимизация и профилирование. - Stack and Heap - Дополнительно: - Из каких частей состоит память java процесса. From 45f97151852c296e41061236c0c9b5ab3e5dd331 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 27 Dec 2018 19:32:21 +0300 Subject: [PATCH 05/83] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index e5fe3209b..2ece5d267 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,9 @@ - Permanent область памяти - Java thread stack - Размер Java объектов + - Оптимизация памяти + - [Escape analysis и скаляризация: Пусть GC отдохнет](https://habr.com/company/jugru/blog/322348) + - [Условия для размещения объекта в стеке](https://stackoverflow.com/a/43002529/548473) ### Ленивая инициализация - Реализация Singleton в JAVA From 3cda2da54b317782957b186a2df71dffba3155d9 Mon Sep 17 00:00:00 2001 From: booktest Date: Wed, 2 Jan 2019 17:20:47 +0300 Subject: [PATCH 06/83] #Lesson01 --- .../masterjava/service/MailService.java | 72 ++++++++++++++++++- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/javaops/masterjava/service/MailService.java b/src/main/java/ru/javaops/masterjava/service/MailService.java index 2b6aeb294..cef46e55e 100644 --- a/src/main/java/ru/javaops/masterjava/service/MailService.java +++ b/src/main/java/ru/javaops/masterjava/service/MailService.java @@ -1,8 +1,10 @@ package ru.javaops.masterjava.service; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.concurrent.*; +import java.util.stream.Collectors; public class MailService { private static final String OK = "OK"; @@ -11,10 +13,74 @@ public class MailService { private static final String INTERRUPTED_BY_TIMEOUT = "+++ Interrupted by timeout"; private static final String INTERRUPTED_EXCEPTION = "+++ InterruptedException"; + private final ExecutorService mailExecutor = Executors.newFixedThreadPool(8); + public GroupResult sendToList(final String template, final Set emails) throws Exception { - return new GroupResult(0, Collections.emptyList(), null); - } + final CompletionService completionService = new ExecutorCompletionService<>(mailExecutor); + + List> futures = emails.stream() + .map(email -> completionService.submit(() -> sendToUser(template, email))) + .collect(Collectors.toList()); + return new Callable() { + private int success = 0; + private List failed = new ArrayList<>(); + + @Override + public GroupResult call() { + while (!futures.isEmpty()) { + try { + Future future = completionService.poll(10, TimeUnit.SECONDS); + if (future == null) { + return cancelWithFail(INTERRUPTED_BY_TIMEOUT); + } + futures.remove(future); + MailResult mailResult = future.get(); + if (mailResult.isOk()) { + success++; + } else { + failed.add(mailResult); + if (failed.size() >= 5) { + return cancelWithFail(INTERRUPTED_BY_FAULTS_NUMBER); + } + } + } catch (ExecutionException e) { + return cancelWithFail(e.getCause().toString()); + } catch (InterruptedException e) { + return cancelWithFail(INTERRUPTED_EXCEPTION); + } + } +/* + for (Future future : futures) { + MailResult mailResult; + try { + mailResult = future.get(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + return cancelWithFail(INTERRUPTED_EXCEPTION); + } catch (ExecutionException e) { + return cancelWithFail(e.getCause().toString()); + } catch (TimeoutException e) { + return cancelWithFail(INTERRUPTED_BY_TIMEOUT); + } + if (mailResult.isOk()) { + success++; + } else { + failed.add(mailResult); + if (failed.size() >= 5) { + return cancelWithFail(INTERRUPTED_BY_FAULTS_NUMBER); + } + } + } +*/ + return new GroupResult(success, failed, null); + } + + private GroupResult cancelWithFail(String cause) { + futures.forEach(f -> f.cancel(true)); + return new GroupResult(success, failed, cause); + } + }.call(); + } // dummy realization public MailResult sendToUser(String template, String email) throws Exception { From 8ae727f4c66e04d130a58661404017115e8f5c14 Mon Sep 17 00:00:00 2001 From: booktest Date: Wed, 2 Jan 2019 23:07:18 +0300 Subject: [PATCH 07/83] #Lesson02 - variants HW01 --- .../javaops/masterjava/matrix/MainMatrix.java | 8 +- .../javaops/masterjava/matrix/MatrixUtil.java | 124 ++++++++++++++++-- 2 files changed, 112 insertions(+), 20 deletions(-) diff --git a/src/main/java/ru/javaops/masterjava/matrix/MainMatrix.java b/src/main/java/ru/javaops/masterjava/matrix/MainMatrix.java index ec1c8a691..1f1380674 100644 --- a/src/main/java/ru/javaops/masterjava/matrix/MainMatrix.java +++ b/src/main/java/ru/javaops/masterjava/matrix/MainMatrix.java @@ -4,10 +4,6 @@ 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; @@ -24,13 +20,13 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc while (count < 6) { System.out.println("Pass " + count); long start = System.currentTimeMillis(); - final int[][] matrixC = MatrixUtil.singleThreadMultiply(matrixA, matrixB); + 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.concurrentMultiply(matrixA, matrixB, executor); + final int[][] concurrentMatrixC = MatrixUtil.concurrentMultiplyStreams(matrixA, matrixB, Runtime.getRuntime().availableProcessors() - 1); duration = (System.currentTimeMillis() - start) / 1000.; out("Concurrent thread time, sec: %.3f", duration); concurrentThreadSum += duration; diff --git a/src/main/java/ru/javaops/masterjava/matrix/MatrixUtil.java b/src/main/java/ru/javaops/masterjava/matrix/MatrixUtil.java index 80a344ac2..d00a67a05 100644 --- a/src/main/java/ru/javaops/masterjava/matrix/MatrixUtil.java +++ b/src/main/java/ru/javaops/masterjava/matrix/MatrixUtil.java @@ -1,35 +1,131 @@ package ru.javaops.masterjava.matrix; +import java.util.ArrayList; +import java.util.List; import java.util.Random; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; +import java.util.concurrent.*; +import java.util.stream.IntStream; -/** - * gkislin - * 03.07.2016 - */ public class MatrixUtil { - // TODO implement parallel multiplication matrixA*matrixB - public static int[][] concurrentMultiply(int[][] matrixA, int[][] matrixB, ExecutorService executor) throws InterruptedException, ExecutionException { + public static int[][] concurrentMultiplyStreams(int[][] matrixA, int[][] matrixB, int threadNumber) + throws InterruptedException, ExecutionException { + final int matrixSize = matrixA.length; final int[][] matrixC = new int[matrixSize][matrixSize]; + new ForkJoinPool(threadNumber).submit( + () -> IntStream.range(0, matrixSize) + .parallel() + .forEach(row -> { + final int[] rowA = matrixA[row]; + final int[] rowC = matrixC[row]; + + 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]; + } + } + })).get(); + return matrixC; } - // TODO optimize by https://habrahabr.ru/post/114797/ - public static int[][] singleThreadMultiply(int[][] matrixA, int[][] matrixB) { + public static int[][] concurrentMultiply(int[][] matrixA, int[][] matrixB, ExecutorService executor) throws InterruptedException, ExecutionException { final int matrixSize = matrixA.length; - final int[][] matrixC = new int[matrixSize][matrixSize]; + 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[][] concurrentMultiply2(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 += matrixA[i][k] * matrixB[k][j]; + sum += rowA[k] * columnB[k]; + } + matrixC[row][col] = sum; + } + } + return matrixC; + } + + public static int[][] singleThreadMultiplyOpt2(int[][] matrixA, int[][] matrixB) { + final int matrixSize = matrixA.length; + final int[][] matrixC = new int[matrixSize][matrixSize]; + + for (int row = 0; row < matrixSize; row++) { + final int[] rowA = matrixA[row]; + final int[] rowC = matrixC[row]; + + 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]; } - matrixC[i][j] = sum; } } return matrixC; @@ -58,4 +154,4 @@ public static boolean compare(int[][] matrixA, int[][] matrixB) { } return true; } -} +} \ No newline at end of file From 8dc83cbb61fcbb14a2aa565b3b46b468ee93a0d0 Mon Sep 17 00:00:00 2001 From: booktest Date: Thu, 3 Jan 2019 12:22:02 +0300 Subject: [PATCH 08/83] #Lesson02 - add benchmark --- pom.xml | 48 ++++++++++- .../masterjava/matrix/MatrixBenchmark.java | 85 +++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java diff --git a/pom.xml b/pom.xml index 39e811e08..4cadaf85e 100644 --- a/pom.xml +++ b/pom.xml @@ -24,16 +24,62 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.7.0 ${java.version} ${java.version} + + org.apache.maven.plugins + maven-shade-plugin + 3.1.0 + + + package + + shade + + + benchmarks + + + org.openjdk.jmh.Main + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + org.openjdk.jmh + jmh-core + RELEASE + + + org.openjdk.jmh + jmh-generator-annprocess + RELEASE + provided + diff --git a/src/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java b/src/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java new file mode 100644 index 000000000..1ef24cdbf --- /dev/null +++ b/src/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java @@ -0,0 +1,85 @@ +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; + +@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 + private static final int MATRIX_SIZE = 1000; + + @Param({"3", "4", "10"}) + private int threadNumber; + + private static int[][] matrixA; + private static int[][] matrixB; + + @Setup + public void setUp() { + matrixA = MatrixUtil.create(MATRIX_SIZE); + matrixB = MatrixUtil.create(MATRIX_SIZE); + } + + private ExecutorService executor; + + 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[][] singleThreadMultiplyOpt2() throws Exception { + return MatrixUtil.singleThreadMultiplyOpt(matrixA, matrixB); + } + + @Benchmark + public int[][] concurrentMultiplyStreams() throws Exception { + return MatrixUtil.concurrentMultiplyStreams(matrixA, matrixB, threadNumber); + } + + // @Benchmark + public int[][] concurrentMultiply() throws Exception { + return MatrixUtil.concurrentMultiply(matrixA, matrixB, executor); + } + + @Benchmark + public int[][] concurrentMultiply2() throws Exception { + return MatrixUtil.concurrentMultiply2(matrixA, matrixB, executor); + } + + @Setup + public void setup() { + executor = Executors.newFixedThreadPool(threadNumber); + } + + @TearDown + public void tearDown() { + executor.shutdown(); + } +} \ No newline at end of file From d26ebc8b6b9b9df5239f2070a09a91b7616fc609 Mon Sep 17 00:00:00 2001 From: booktest Date: Thu, 3 Jan 2019 12:39:46 +0300 Subject: [PATCH 09/83] #lesson02 - xml scheme and jaxb classes --- .../masterjava/xml/schema/CityType.java | 94 +++++++ .../masterjava/xml/schema/FlagType.java | 54 ++++ .../masterjava/xml/schema/ObjectFactory.java | 85 +++++++ .../masterjava/xml/schema/Payload.java | 233 ++++++++++++++++++ .../javaops/masterjava/xml/schema/User.java | 151 ++++++++++++ src/main/resources/payload.xsd | 56 +++++ src/test/resources/payload.xml | 23 ++ 7 files changed, 696 insertions(+) create mode 100644 src/main/java/ru/javaops/masterjava/xml/schema/CityType.java create mode 100644 src/main/java/ru/javaops/masterjava/xml/schema/FlagType.java create mode 100644 src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java create mode 100644 src/main/java/ru/javaops/masterjava/xml/schema/Payload.java create mode 100644 src/main/java/ru/javaops/masterjava/xml/schema/User.java create mode 100644 src/main/resources/payload.xsd create mode 100644 src/test/resources/payload.xml diff --git a/src/main/java/ru/javaops/masterjava/xml/schema/CityType.java b/src/main/java/ru/javaops/masterjava/xml/schema/CityType.java new file mode 100644 index 000000000..029e352cb --- /dev/null +++ b/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/src/main/java/ru/javaops/masterjava/xml/schema/FlagType.java b/src/main/java/ru/javaops/masterjava/xml/schema/FlagType.java new file mode 100644 index 000000000..eda39fa9a --- /dev/null +++ b/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/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java b/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java new file mode 100644 index 000000000..e8f105e2a --- /dev/null +++ b/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java @@ -0,0 +1,85 @@ + +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 Payload } + * + */ + public Payload createPayload() { + return new Payload(); + } + + /** + * Create an instance of {@link User } + * + */ + public User createUser() { + return new User(); + } + + /** + * 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/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java b/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java new file mode 100644 index 000000000..2a6276490 --- /dev/null +++ b/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java @@ -0,0 +1,233 @@ + +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">
+ *       <all>
+ *         <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>
+ *       </all>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + +}) +@XmlRootElement(name = "Payload", namespace = "http://javaops.ru") +public class Payload { + + @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 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" 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/src/main/java/ru/javaops/masterjava/xml/schema/User.java b/src/main/java/ru/javaops/masterjava/xml/schema/User.java new file mode 100644 index 000000000..b3430ce71 --- /dev/null +++ b/src/main/java/ru/javaops/masterjava/xml/schema/User.java @@ -0,0 +1,151 @@ + +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.XmlElement; +import javax.xml.bind.annotation.XmlIDREF; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlSchemaType; +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="email" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ *         <element name="fullName" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ *       </sequence>
+ *       <attribute name="flag" use="required" type="{http://javaops.ru}flagType" />
+ *       <attribute name="city" use="required" type="{http://www.w3.org/2001/XMLSchema}IDREF" />
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "email", + "fullName" +}) +@XmlRootElement(name = "User", namespace = "http://javaops.ru") +public class User { + + @XmlElement(namespace = "http://javaops.ru", required = true) + protected String email; + @XmlElement(namespace = "http://javaops.ru", required = true) + protected String fullName; + @XmlAttribute(name = "flag", required = true) + protected FlagType flag; + @XmlAttribute(name = "city", required = true) + @XmlIDREF + @XmlSchemaType(name = "IDREF") + protected Object city; + + /** + * 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 fullName property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getFullName() { + return fullName; + } + + /** + * Sets the value of the fullName property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setFullName(String value) { + this.fullName = 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; + } + +} diff --git a/src/main/resources/payload.xsd b/src/main/resources/payload.xsd new file mode 100644 index 000000000..9ef1e46eb --- /dev/null +++ b/src/main/resources/payload.xsd @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/payload.xml b/src/test/resources/payload.xml new file mode 100644 index 000000000..796e99cb3 --- /dev/null +++ b/src/test/resources/payload.xml @@ -0,0 +1,23 @@ + + + + gmail@gmail.com + Full Name + + + admin@javaops.ru + Admin + + + mail@yandex.ru + Deleted + + + + Санкт-Петербург + Киев + Минск + + \ No newline at end of file From 779cdf5f737a061478dcbbd8be00885191363dff Mon Sep 17 00:00:00 2001 From: booktest Date: Thu, 3 Jan 2019 13:22:11 +0300 Subject: [PATCH 10/83] #Lesson02 - jaxb parser and tests for him --- pom.xml | 19 ++++ .../masterjava/xml/util/JaxbMarshaller.java | 39 ++++++++ .../masterjava/xml/util/JaxbParser.java | 88 +++++++++++++++++++ .../masterjava/xml/util/JaxbUnmarshaller.java | 33 +++++++ .../javaops/masterjava/xml/util/Schemas.java | 48 ++++++++++ .../masterjava/xml/util/JaxbParserTest.java | 40 +++++++++ src/test/resources/city.xml | 4 + 7 files changed, 271 insertions(+) create mode 100644 src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java create mode 100644 src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java create mode 100644 src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java create mode 100644 src/main/java/ru/javaops/masterjava/xml/util/Schemas.java create mode 100644 src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java create mode 100644 src/test/resources/city.xml diff --git a/pom.xml b/pom.xml index 4cadaf85e..884be4839 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,14 @@ ${java.version} + + org.apache.maven.plugins + maven-surefire-plugin + 2.20.1 + + -Dfile.encoding=UTF-8 + + org.apache.maven.plugins maven-shade-plugin @@ -80,6 +88,17 @@ RELEASE provided + + com.google.guava + guava + 21.0 + + + junit + junit + 4.12 + test + diff --git a/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java b/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java new file mode 100644 index 000000000..d6006800f --- /dev/null +++ b/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java @@ -0,0 +1,39 @@ +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; + +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/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java b/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java new file mode 100644 index 000000000..b3a45f66c --- /dev/null +++ b/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java @@ -0,0 +1,88 @@ +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 + */ +public class JaxbParser { + + protected JaxbMarshaller jaxbMarshaller; + protected JaxbUnmarshaller jaxbUnmarshaller; + protected Schema schema; + + public JaxbParser(Class... classesToBeBound) { + try { + init(JAXBContext.newInstance(classesToBeBound)); + } catch (JAXBException e) { + throw new IllegalArgumentException(e); + } + } + + // http://stackoverflow.com/questions/30643802/what-is-jaxbcontext-newinstancestring-contextpath + public JaxbParser(String context) { + try { + init(JAXBContext.newInstance(context)); + } catch (JAXBException e) { + throw new IllegalArgumentException(e); + } + } + + private void init(JAXBContext ctx) throws JAXBException { + jaxbMarshaller = new JaxbMarshaller(ctx); + jaxbUnmarshaller = new JaxbUnmarshaller(ctx); + } + + // Unmarshaller + public T unmarshal(InputStream is) throws JAXBException { + return (T) jaxbUnmarshaller.unmarshal(is); + } + + public T unmarshal(Reader reader) throws JAXBException { + return (T) jaxbUnmarshaller.unmarshal(reader); + } + + public T unmarshal(String str) throws JAXBException { + return (T) jaxbUnmarshaller.unmarshal(str); + } + + // Marshaller + public void setMarshallerProperty(String prop, Object value) { + try { + jaxbMarshaller.setProperty(prop, value); + } catch (PropertyException e) { + throw new IllegalArgumentException(e); + } + } + + public String marshal(Object instance) throws JAXBException { + return jaxbMarshaller.marshal(instance); + } + + public void marshal(Object instance, Writer writer) throws JAXBException { + jaxbMarshaller.marshal(instance, writer); + } + + public void setSchema(Schema schema) { + this.schema = schema; + jaxbUnmarshaller.setSchema(schema); + jaxbMarshaller.setSchema(schema); + } + + public void validate(String str) throws IOException, SAXException { + validate(new StringReader(str)); + } + + public void validate(Reader reader) throws IOException, SAXException { + schema.newValidator().validate(new StreamSource(reader)); + } +} diff --git a/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java b/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java new file mode 100644 index 000000000..7a3e13461 --- /dev/null +++ b/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java @@ -0,0 +1,33 @@ +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; + +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/src/main/java/ru/javaops/masterjava/xml/util/Schemas.java b/src/main/java/ru/javaops/masterjava/xml/util/Schemas.java new file mode 100644 index 000000000..42f41df80 --- /dev/null +++ b/src/main/java/ru/javaops/masterjava/xml/util/Schemas.java @@ -0,0 +1,48 @@ +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; + + +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/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java b/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java new file mode 100644 index 000000000..623265428 --- /dev/null +++ b/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java @@ -0,0 +1,40 @@ +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; + +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/src/test/resources/city.xml b/src/test/resources/city.xml new file mode 100644 index 000000000..8b0abcf8a --- /dev/null +++ b/src/test/resources/city.xml @@ -0,0 +1,4 @@ +Санкт-Петербург + \ No newline at end of file From 23d3c4a8f4a8f3d0721a6a0e1a9ed8aa6baceb05 Mon Sep 17 00:00:00 2001 From: booktest Date: Thu, 3 Jan 2019 13:24:48 +0300 Subject: [PATCH 11/83] #Lesson02 - StAX --- .../xml/util/StaxStreamProcessor.java | 56 +++++++++++++++++++ .../xml/util/StaxStreamProcessorTest.java | 36 ++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java create mode 100644 src/test/java/ru/javaops/masterjava/xml/util/StaxStreamProcessorTest.java diff --git a/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java b/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java new file mode 100644 index 000000000..921ca6aff --- /dev/null +++ b/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java @@ -0,0 +1,56 @@ +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; + +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 { + while (reader.hasNext()) { + int event = reader.next(); + if (event == stopEvent) { + if (value.equals(getValue(event))) { + return true; + } + } + } + return false; + } + + public String getValue(int event) throws XMLStreamException { + return (event == XMLEvent.CHARACTERS) ? reader.getText() : reader.getLocalName(); + } + + public String getElementValue(String element) throws XMLStreamException { + return doUntil(XMLEvent.START_ELEMENT, element) ? reader.getElementText() : null; + } + + public String getText() throws XMLStreamException { + return reader.getElementText(); + } + + @Override + public void close() { + if (reader != null) { + try { + reader.close(); + } catch (XMLStreamException e) { + // empty + } + } + } +} diff --git a/src/test/java/ru/javaops/masterjava/xml/util/StaxStreamProcessorTest.java b/src/test/java/ru/javaops/masterjava/xml/util/StaxStreamProcessorTest.java new file mode 100644 index 000000000..fd55963dd --- /dev/null +++ b/src/test/java/ru/javaops/masterjava/xml/util/StaxStreamProcessorTest.java @@ -0,0 +1,36 @@ +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; + +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 From eac82e9f032bf30eddf14a05259b6a216cba6657 Mon Sep 17 00:00:00 2001 From: booktest Date: Thu, 3 Jan 2019 13:47:14 +0300 Subject: [PATCH 12/83] #Lesson02 - XPath --- .../masterjava/xml/util/XPathProcessor.java | 58 +++++++++++++++++++ .../xml/util/XPathProcessorTest.java | 26 +++++++++ 2 files changed, 84 insertions(+) create mode 100644 src/main/java/ru/javaops/masterjava/xml/util/XPathProcessor.java create mode 100644 src/test/java/ru/javaops/masterjava/xml/util/XPathProcessorTest.java diff --git a/src/main/java/ru/javaops/masterjava/xml/util/XPathProcessor.java b/src/main/java/ru/javaops/masterjava/xml/util/XPathProcessor.java new file mode 100644 index 000000000..63baae5d1 --- /dev/null +++ b/src/main/java/ru/javaops/masterjava/xml/util/XPathProcessor.java @@ -0,0 +1,58 @@ +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; + +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/src/test/java/ru/javaops/masterjava/xml/util/XPathProcessorTest.java b/src/test/java/ru/javaops/masterjava/xml/util/XPathProcessorTest.java new file mode 100644 index 000000000..199f676a1 --- /dev/null +++ b/src/test/java/ru/javaops/masterjava/xml/util/XPathProcessorTest.java @@ -0,0 +1,26 @@ +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; + +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 From 396ef70e47168fe194533f75ec6a704dda2cf416 Mon Sep 17 00:00:00 2001 From: booktest Date: Thu, 3 Jan 2019 13:49:33 +0300 Subject: [PATCH 13/83] #Lesson02 - Xslt --- .../masterjava/xml/util/XsltProcessor.java | 43 +++++++++++++++++++ src/main/resources/cities.xsl | 9 ++++ .../xml/util/XsltProcessorTest.java | 18 ++++++++ 3 files changed, 70 insertions(+) create mode 100644 src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java create mode 100644 src/main/resources/cities.xsl create mode 100644 src/test/java/ru/javaops/masterjava/xml/util/XsltProcessorTest.java diff --git a/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java b/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java new file mode 100644 index 000000000..019eeed3d --- /dev/null +++ b/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java @@ -0,0 +1,43 @@ +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; + +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"; + } +} diff --git a/src/main/resources/cities.xsl b/src/main/resources/cities.xsl new file mode 100644 index 000000000..1c509124b --- /dev/null +++ b/src/main/resources/cities.xsl @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/ru/javaops/masterjava/xml/util/XsltProcessorTest.java b/src/test/java/ru/javaops/masterjava/xml/util/XsltProcessorTest.java new file mode 100644 index 000000000..d7f42a699 --- /dev/null +++ b/src/test/java/ru/javaops/masterjava/xml/util/XsltProcessorTest.java @@ -0,0 +1,18 @@ +package ru.javaops.masterjava.xml.util; + +import com.google.common.io.Resources; +import org.junit.Test; + +import java.io.InputStream; + +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)); + } + } +} From ab7ac09394e398241227d65ff7b3e18007b27c6d Mon Sep 17 00:00:00 2001 From: booktest Date: Fri, 4 Jan 2019 19:51:02 +0300 Subject: [PATCH 14/83] #Lesson03 - HW02 jaxb --- pom.xml | 13 ++ src/main/java/ru/javaops/masterjava/Main.java | 14 -- .../masterjava/xml/schema/GroupType.java | 40 ++++ .../masterjava/xml/schema/ObjectFactory.java | 24 ++ .../masterjava/xml/schema/Payload.java | 111 ++++++++- .../masterjava/xml/schema/Project.java | 216 ++++++++++++++++++ .../javaops/masterjava/xml/schema/User.java | 95 +++++--- src/main/resources/payload.xsd | 57 ++++- .../java/ru/javaops/masterjava/MainXml.java | 78 +++++++ src/test/resources/payload.xml | 36 +-- 10 files changed, 607 insertions(+), 77 deletions(-) delete mode 100644 src/main/java/ru/javaops/masterjava/Main.java create mode 100644 src/main/java/ru/javaops/masterjava/xml/schema/GroupType.java create mode 100644 src/main/java/ru/javaops/masterjava/xml/schema/Project.java create mode 100644 src/test/java/ru/javaops/masterjava/MainXml.java diff --git a/pom.xml b/pom.xml index 884be4839..880943475 100644 --- a/pom.xml +++ b/pom.xml @@ -93,6 +93,19 @@ guava 21.0 + + one.util + streamex + RELEASE + + + + com.j2html + j2html + RELEASE + + + junit junit diff --git a/src/main/java/ru/javaops/masterjava/Main.java b/src/main/java/ru/javaops/masterjava/Main.java deleted file mode 100644 index a849258c4..000000000 --- a/src/main/java/ru/javaops/masterjava/Main.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.javaops.masterjava; - -/** - * User: gkislin - * Date: 05.08.2015 - * - * @link http://caloriesmng.herokuapp.com/ - * @link https://github.com/JavaOPs/topjava - */ -public class Main { - public static void main(String[] args) { - System.out.format("Hello MasterJava!"); - } -} diff --git a/src/main/java/ru/javaops/masterjava/xml/schema/GroupType.java b/src/main/java/ru/javaops/masterjava/xml/schema/GroupType.java new file mode 100644 index 000000000..d5041640b --- /dev/null +++ b/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/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java b/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java index e8f105e2a..bfb393299 100644 --- a/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java +++ b/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java @@ -33,6 +33,14 @@ public class ObjectFactory { public ObjectFactory() { } + /** + * Create an instance of {@link Project } + * + */ + public Project createProject() { + return new Project(); + } + /** * Create an instance of {@link Payload } * @@ -41,6 +49,14 @@ 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 } * @@ -49,6 +65,14 @@ 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 } * diff --git a/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java b/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java index 2a6276490..9d4cc3046 100644 --- a/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java +++ b/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java @@ -1,13 +1,9 @@ package ru.javaops.masterjava.xml.schema; +import javax.xml.bind.annotation.*; 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; /** @@ -19,7 +15,18 @@ * <complexType> * <complexContent> * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType"> - * <all> + * <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> @@ -42,7 +49,7 @@ * </complexContent> * </complexType> * </element> - * </all> + * </sequence> * </restriction> * </complexContent> * </complexType> @@ -52,16 +59,44 @@ */ @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. * @@ -171,6 +206,66 @@ public List getCity() { } + /** + *

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. * diff --git a/src/main/java/ru/javaops/masterjava/xml/schema/Project.java b/src/main/java/ru/javaops/masterjava/xml/schema/Project.java new file mode 100644 index 000000000..180a997e9 --- /dev/null +++ b/src/main/java/ru/javaops/masterjava/xml/schema/Project.java @@ -0,0 +1,216 @@ + +package ru.javaops.masterjava.xml.schema; + +import javax.xml.bind.annotation.*; +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +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>
+ *   <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/src/main/java/ru/javaops/masterjava/xml/schema/User.java b/src/main/java/ru/javaops/masterjava/xml/schema/User.java index b3430ce71..b2cc1c449 100644 --- a/src/main/java/ru/javaops/masterjava/xml/schema/User.java +++ b/src/main/java/ru/javaops/masterjava/xml/schema/User.java @@ -1,14 +1,9 @@ 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.XmlElement; -import javax.xml.bind.annotation.XmlIDREF; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlSchemaType; -import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; /** @@ -18,16 +13,14 @@ * *
  * <complexType>
- *   <complexContent>
- *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *       <sequence>
- *         <element name="email" type="{http://www.w3.org/2001/XMLSchema}string"/>
- *         <element name="fullName" type="{http://www.w3.org/2001/XMLSchema}string"/>
- *       </sequence>
+ *   <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" />
- *     </restriction>
- *   </complexContent>
+ *       <attribute name="groupRefs" type="{http://www.w3.org/2001/XMLSchema}IDREFS" />
+ *     </extension>
+ *   </simpleContent>
  * </complexType>
  * 
* @@ -35,69 +28,72 @@ */ @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "", propOrder = { - "email", - "fullName" + "value" }) @XmlRootElement(name = "User", namespace = "http://javaops.ru") public class User { - @XmlElement(namespace = "http://javaops.ru", required = true) + @XmlValue + protected String value; + @XmlAttribute(name = "email") protected String email; - @XmlElement(namespace = "http://javaops.ru", required = true) - protected String fullName; @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 email property. + * Gets the value of the value property. * * @return * possible object is * {@link String } * */ - public String getEmail() { - return email; + public String getValue() { + return value; } /** - * Sets the value of the email property. + * Sets the value of the value property. * * @param value * allowed object is * {@link String } * */ - public void setEmail(String value) { - this.email = value; + public void setValue(String value) { + this.value = value; } /** - * Gets the value of the fullName property. + * Gets the value of the email property. * * @return * possible object is * {@link String } * */ - public String getFullName() { - return fullName; + public String getEmail() { + return email; } /** - * Sets the value of the fullName property. + * Sets the value of the email property. * * @param value * allowed object is * {@link String } * */ - public void setFullName(String value) { - this.fullName = value; + public void setEmail(String value) { + this.email = value; } /** @@ -148,4 +144,37 @@ 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/src/main/resources/payload.xsd b/src/main/resources/payload.xsd index 9ef1e46eb..3d545ec54 100644 --- a/src/main/resources/payload.xsd +++ b/src/main/resources/payload.xsd @@ -6,7 +6,14 @@ - + + + + + + + + @@ -21,18 +28,39 @@ - + - + - - + + + + + + + + + - - + + + + + + + + + + + + + + + + @@ -44,7 +72,13 @@ - + + + + + + + @@ -53,4 +87,11 @@ + + + + + + + \ No newline at end of file diff --git a/src/test/java/ru/javaops/masterjava/MainXml.java b/src/test/java/ru/javaops/masterjava/MainXml.java new file mode 100644 index 000000000..554c5fe99 --- /dev/null +++ b/src/test/java/ru/javaops/masterjava/MainXml.java @@ -0,0 +1,78 @@ +package ru.javaops.masterjava; + +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 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 j2html.TagCreator.*; + +public class MainXml { + + 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); + } + } + + public 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<>(Comparator.comparing(User::getValue).thenComparing(User::getEmail))) + ); + } + + 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(); + } +} diff --git a/src/test/resources/payload.xml b/src/test/resources/payload.xml index 796e99cb3..fadf8c64a 100644 --- a/src/test/resources/payload.xml +++ b/src/test/resources/payload.xml @@ -1,23 +1,31 @@ - - - gmail@gmail.com - Full Name - - - admin@javaops.ru - Admin - - - mail@yandex.ru - Deleted - - + + + + Topjava + + + + + + Masterjava + + + Санкт-Петербург + Москва Киев Минск + + Full Name + Admin + Deleted + User1 + User2 + User3 + \ No newline at end of file From 387269ab0b8908751bb6c39d43939ea4f6e4ff08 Mon Sep 17 00:00:00 2001 From: booktest Date: Sun, 6 Jan 2019 11:19:18 +0300 Subject: [PATCH 15/83] #Lesson03 - HW02 stax --- .../masterjava/matrix/MatrixBenchmark.java | 14 ++++- .../masterjava/xml/util/JaxbParser.java | 5 ++ .../masterjava/xml/util/JaxbUnmarshaller.java | 11 +++- .../xml/util/StaxStreamProcessor.java | 31 ++++++++-- .../java/ru/javaops/masterjava/MainXml.java | 58 ++++++++++++++++++- 5 files changed, 107 insertions(+), 12 deletions(-) diff --git a/src/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java b/src/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java index 1ef24cdbf..6a9cee992 100644 --- a/src/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java +++ b/src/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java @@ -58,7 +58,7 @@ public int[][] singleThreadMultiplyOpt2() throws Exception { return MatrixUtil.singleThreadMultiplyOpt(matrixA, matrixB); } - @Benchmark + //@Benchmark public int[][] concurrentMultiplyStreams() throws Exception { return MatrixUtil.concurrentMultiplyStreams(matrixA, matrixB, threadNumber); } @@ -68,11 +68,21 @@ public int[][] concurrentMultiply() throws Exception { return MatrixUtil.concurrentMultiply(matrixA, matrixB, executor); } - @Benchmark + // @Benchmark public int[][] concurrentMultiply2() throws Exception { return MatrixUtil.concurrentMultiply2(matrixA, matrixB, executor); } + /* @Benchmark + public static Set parseByJaxb() throws Exception { + return MainXml.parseByJaxb("masterjava", Resources.getResource("payload.xml")); + } + + @Benchmark + public static Set processByStax() throws Exception { + return MainXml.processByStax("masterjava", Resources.getResource("payload.xml")); + }*/ + @Setup public void setup() { executor = Executors.newFixedThreadPool(threadNumber); diff --git a/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java b/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java index b3a45f66c..563d53ba0 100644 --- a/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java +++ b/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java @@ -5,6 +5,7 @@ import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.PropertyException; +import javax.xml.stream.XMLStreamReader; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import java.io.*; @@ -55,6 +56,10 @@ public T unmarshal(String str) throws JAXBException { return (T) jaxbUnmarshaller.unmarshal(str); } + public T unmarshal(XMLStreamReader reader, Class elementClass) throws JAXBException { + return jaxbUnmarshaller.unmarshal(reader, elementClass); + } + // Marshaller public void setMarshallerProperty(String prop, Object value) { try { diff --git a/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java b/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java index 7a3e13461..4738e4c2c 100644 --- a/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java +++ b/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java @@ -3,6 +3,7 @@ import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; +import javax.xml.stream.XMLStreamReader; import javax.xml.validation.Schema; import java.io.InputStream; import java.io.Reader; @@ -27,7 +28,11 @@ public synchronized Object unmarshal(Reader reader) throws JAXBException { return unmarshaller.unmarshal(reader); } - public Object unmarshal(String str) throws JAXBException { - return unmarshal(new StringReader(str)); + public Object unmarshal(String xml) throws JAXBException { + return unmarshal(new StringReader(xml)); } -} + + public synchronized T unmarshal(XMLStreamReader reader, Class elementClass) throws JAXBException { + return unmarshaller.unmarshal(reader, elementClass).getValue(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java b/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java index 921ca6aff..5878118c0 100644 --- a/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java +++ b/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java @@ -15,17 +15,40 @@ public StaxStreamProcessor(InputStream is) throws XMLStreamException { reader = FACTORY.createXMLStreamReader(is); } + public boolean startElement(String element, String parent) throws XMLStreamException { + while (reader.hasNext()) { + int event = reader.next(); + if (parent != null && isElementEnd(event, parent)) { + return false; + } + if (isElementStart(event, element)) { + return true; + } + } + return false; + } + + private boolean isElementStart(int event, String el) { + return event == XMLEvent.START_ELEMENT && el.equals(reader.getLocalName()); + } + + private boolean isElementEnd(int event, String el) { + return event == XMLEvent.END_ELEMENT && el.equals(reader.getLocalName()); + } + public XMLStreamReader getReader() { return reader; } + public String getAttribute(String name) throws XMLStreamException { + return reader.getAttributeValue(null, name); + } + public boolean doUntil(int stopEvent, String value) throws XMLStreamException { while (reader.hasNext()) { int event = reader.next(); - if (event == stopEvent) { - if (value.equals(getValue(event))) { - return true; - } + if (event == stopEvent && value.equals(getValue(event))) { + return true; } } return false; diff --git a/src/test/java/ru/javaops/masterjava/MainXml.java b/src/test/java/ru/javaops/masterjava/MainXml.java index 554c5fe99..1cc1827c5 100644 --- a/src/test/java/ru/javaops/masterjava/MainXml.java +++ b/src/test/java/ru/javaops/masterjava/MainXml.java @@ -1,5 +1,6 @@ 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; @@ -9,7 +10,10 @@ 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; @@ -18,13 +22,17 @@ import java.util.*; import java.util.stream.Collectors; +import static com.google.common.base.Strings.nullToEmpty; import static j2html.TagCreator.*; public class MainXml { + private static final Comparator USER_COMPARATOR = Comparator.comparing(User::getValue).thenComparing(User::getEmail); + private static final JaxbParser parser = new JaxbParser(ObjectFactory.class); + public static void main(String[] args) throws Exception { if (args.length != 1) { - System.out.println("Format: projectName"); + System.out.println("Required argument: projectName"); System.exit(1); } String projectName = args[0]; @@ -32,16 +40,26 @@ public static void main(String[] args) throws Exception { Set users = parseByJaxb(projectName, payloadUrl); users.forEach(System.out::println); + 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); + + System.out.println(); + html = transform(projectName, payloadUrl); + try (Writer writer = Files.newBufferedWriter(Paths.get("out/groups.html"))) { + writer.write(html); + } } public static Set parseByJaxb(String projectName, URL payloadUrl) throws Exception { - JaxbParser parser = new JaxbParser(ObjectFactory.class); + //JaxbParser parser = new JaxbParser(ObjectFactory.class); parser.setSchema(Schemas.ofClasspath("payload.xsd")); Payload payload; try (InputStream is = payloadUrl.openStream()) { @@ -57,10 +75,44 @@ public static Set parseByJaxb(String projectName, URL payloadUrl) throws E return StreamEx.of(payload.getUsers().getUser()) .filter(u -> !Collections.disjoint(groups, u.getGroupRefs())) .collect( - Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getValue).thenComparing(User::getEmail))) + Collectors.toCollection(() -> new TreeSet<>(USER_COMPARATOR)) ); } + public 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 + while (processor.startElement("Project", "Projects")) { + if (projectName.equals(processor.getAttribute("name"))) { + while (processor.startElement("Group", "Project")) { + groupNames.add(processor.getAttribute("name")); + } + break; + } + } + if (groupNames.isEmpty()) { + throw new IllegalArgumentException("Invalid " + projectName + " or no groups"); + } + // Users loop + Set users = new TreeSet<>(USER_COMPARATOR); + + //JaxbParser parser = new JaxbParser(User.class); + while (processor.doUntil(XMLEvent.START_ELEMENT, "User")) { + String groupRefs = processor.getAttribute("groupRefs"); + if (!Collections.disjoint(groupNames, Splitter.on(' ').splitToList(nullToEmpty(groupRefs)))) { + parser.setSchema(null); + User user = parser.unmarshal(processor.getReader(), User.class); + users.add(user); + } + } + return users; + } + } + private static String toHtml(Set users, String projectName) { final ContainerTag table = table().with( tr().with(th("FullName"), th("email"))) From 112f11091db28b3d9548a9a51cb0c39c21c2c5ed Mon Sep 17 00:00:00 2001 From: booktest Date: Sun, 6 Jan 2019 11:36:42 +0300 Subject: [PATCH 16/83] #Lesson03 - HW02 xslt generate html table --- .../masterjava/xml/util/XsltProcessor.java | 4 ++ src/main/resources/groups.xsl | 37 +++++++++++++++++++ .../java/ru/javaops/masterjava/MainXml.java | 9 +++++ 3 files changed, 50 insertions(+) create mode 100644 src/main/resources/groups.xsl diff --git a/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java b/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java index 019eeed3d..083febb00 100644 --- a/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java +++ b/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java @@ -40,4 +40,8 @@ public void transform(Reader sourceReader, Writer result) throws TransformerExce public static String getXsltHeader(String xslt) { return "\n"; } + + public void setParameter(String name, String value) { + xformer.setParameter(name, value); + } } diff --git a/src/main/resources/groups.xsl b/src/main/resources/groups.xsl new file mode 100644 index 000000000..c5ade36b8 --- /dev/null +++ b/src/main/resources/groups.xsl @@ -0,0 +1,37 @@ + + + + + + + + + + <xsl:value-of select="$projectName"/> groups + + + +

+ groups +

+ + + + + + + + + + + +
GroupType
+ + + +
+ + +
+
\ No newline at end of file diff --git a/src/test/java/ru/javaops/masterjava/MainXml.java b/src/test/java/ru/javaops/masterjava/MainXml.java index 1cc1827c5..6bdee7eb9 100644 --- a/src/test/java/ru/javaops/masterjava/MainXml.java +++ b/src/test/java/ru/javaops/masterjava/MainXml.java @@ -127,4 +127,13 @@ private static String toHtml(Set users, String projectName) { 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); + } + } } From 867e472b9ef3841e0eb55058bd6e98db4336e9b2 Mon Sep 17 00:00:00 2001 From: booktest Date: Mon, 7 Jan 2019 22:48:39 +0300 Subject: [PATCH 17/83] Lesson04 - HW03 pom_structure --- common/pom.xml | 21 ++++ parent-web/pom.xml | 46 +++++++ parent/pom.xml | 69 +++++++++++ pom.xml | 117 ++---------------- services/mail-api/pom.xml | 26 ++++ services/mail-service/pom.xml | 26 ++++ .../service/mail/MailServiceExecutor.java | 4 +- services/pom.xml | 15 +++ test/pom.xml | 76 ++++++++++++ .../javaops/masterjava/matrix/MainMatrix.java | 0 .../masterjava/matrix/MatrixBenchmark.java | 0 .../javaops/masterjava/matrix/MatrixUtil.java | 0 web/pom.xml | 15 +++ web/upload/pom.xml | 31 +++++ .../masterjava/xml/schema/CityType.java | 9 +- .../masterjava/xml/schema/FlagType.java | 0 .../masterjava/xml/schema/GroupType.java | 0 .../masterjava/xml/schema/ObjectFactory.java | 0 .../masterjava/xml/schema/Payload.java | 0 .../masterjava/xml/schema/Project.java | 0 .../javaops/masterjava/xml/schema/User.java | 0 .../masterjava/xml/util/JaxbMarshaller.java | 0 .../masterjava/xml/util/JaxbParser.java | 0 .../masterjava/xml/util/JaxbUnmarshaller.java | 0 .../javaops/masterjava/xml/util/Schemas.java | 0 .../xml/util/StaxStreamProcessor.java | 0 .../masterjava/xml/util/XPathProcessor.java | 0 .../masterjava/xml/util/XsltProcessor.java | 0 .../upload/src}/main/resources/cities.xsl | 0 .../upload/src}/main/resources/groups.xsl | 0 .../upload/src}/main/resources/payload.xsd | 0 .../java/ru/javaops/masterjava/MainXml.java | 5 +- .../masterjava/xml/util/JaxbParserTest.java | 0 .../xml/util/StaxStreamProcessorTest.java | 0 .../xml/util/XPathProcessorTest.java | 0 .../xml/util/XsltProcessorTest.java | 0 .../upload/src}/test/resources/city.xml | 0 .../upload/src}/test/resources/payload.xml | 0 web/webapp/pom.xml | 30 +++++ 39 files changed, 368 insertions(+), 122 deletions(-) create mode 100644 common/pom.xml create mode 100644 parent-web/pom.xml create mode 100644 parent/pom.xml create mode 100644 services/mail-api/pom.xml create mode 100644 services/mail-service/pom.xml rename src/main/java/ru/javaops/masterjava/service/MailService.java => services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceExecutor.java (98%) create mode 100644 services/pom.xml create mode 100644 test/pom.xml rename {src => test/src}/main/java/ru/javaops/masterjava/matrix/MainMatrix.java (100%) rename {src => test/src}/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java (100%) rename {src => test/src}/main/java/ru/javaops/masterjava/matrix/MatrixUtil.java (100%) create mode 100644 web/pom.xml create mode 100644 web/upload/pom.xml rename {src => web/upload/src}/main/java/ru/javaops/masterjava/xml/schema/CityType.java (85%) rename {src => web/upload/src}/main/java/ru/javaops/masterjava/xml/schema/FlagType.java (100%) rename {src => web/upload/src}/main/java/ru/javaops/masterjava/xml/schema/GroupType.java (100%) rename {src => web/upload/src}/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java (100%) rename {src => web/upload/src}/main/java/ru/javaops/masterjava/xml/schema/Payload.java (100%) rename {src => web/upload/src}/main/java/ru/javaops/masterjava/xml/schema/Project.java (100%) rename {src => web/upload/src}/main/java/ru/javaops/masterjava/xml/schema/User.java (100%) rename {src => web/upload/src}/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java (100%) rename {src => web/upload/src}/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java (100%) rename {src => web/upload/src}/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java (100%) rename {src => web/upload/src}/main/java/ru/javaops/masterjava/xml/util/Schemas.java (100%) rename {src => web/upload/src}/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java (100%) rename {src => web/upload/src}/main/java/ru/javaops/masterjava/xml/util/XPathProcessor.java (100%) rename {src => web/upload/src}/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java (100%) rename {src => web/upload/src}/main/resources/cities.xsl (100%) rename {src => web/upload/src}/main/resources/groups.xsl (100%) rename {src => web/upload/src}/main/resources/payload.xsd (100%) rename {src => web/upload/src}/test/java/ru/javaops/masterjava/MainXml.java (97%) rename {src => web/upload/src}/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java (100%) rename {src => web/upload/src}/test/java/ru/javaops/masterjava/xml/util/StaxStreamProcessorTest.java (100%) rename {src => web/upload/src}/test/java/ru/javaops/masterjava/xml/util/XPathProcessorTest.java (100%) rename {src => web/upload/src}/test/java/ru/javaops/masterjava/xml/util/XsltProcessorTest.java (100%) rename {src => web/upload/src}/test/resources/city.xml (100%) rename {src => web/upload/src}/test/resources/payload.xml (100%) create mode 100644 web/webapp/pom.xml 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/parent-web/pom.xml b/parent-web/pom.xml new file mode 100644 index 000000000..e46b5a49f --- /dev/null +++ b/parent-web/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + + ru.javaops + parent + ../parent/pom.xml + 1.0-SNAPSHOT + + + parent-web + pom + 1.0-SNAPSHOT + Parent Web + + + + + org.apache.maven.plugins + maven-war-plugin + 3.2.0 + + false + + + + + + + + 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..d7b056d1d --- /dev/null +++ b/parent/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + ru.javaops + parent + pom + 1.0-SNAPSHOT + Parent + + + 1.8 + UTF-8 + UTF-8 + + + + install + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.20.1 + + -Dfile.encoding=UTF-8 + + + + + + + + com.google.guava + guava + 23.3-jre + + + one.util + streamex + RELEASE + + + + + junit + junit + 4.12 + test + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 880943475..f0c5cbbb2 100644 --- a/pom.xml +++ b/pom.xml @@ -4,119 +4,18 @@ ru.javaops masterjava - jar + pom 1.0-SNAPSHOT - Master Java + Masterjava Root https://github.com/JavaOPs/masterjava - - 1.8 - UTF-8 - UTF-8 - + + common + test - - masterjava - install - - - org.apache.maven.plugins - maven-compiler-plugin - 3.7.0 - - ${java.version} - ${java.version} - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.20.1 - - -Dfile.encoding=UTF-8 - - - - org.apache.maven.plugins - maven-shade-plugin - 3.1.0 - - - package - - shade - - - benchmarks - - - org.openjdk.jmh.Main - - - - - - *:* - - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - - - - - - - - - org.openjdk.jmh - jmh-core - RELEASE - - - org.openjdk.jmh - jmh-generator-annprocess - RELEASE - provided - - - com.google.guava - guava - 21.0 - - - one.util - streamex - RELEASE - - - - com.j2html - j2html - RELEASE - - - - - junit - junit - 4.12 - test - - - - - - - - + web + services + diff --git a/services/mail-api/pom.xml b/services/mail-api/pom.xml new file mode 100644 index 000000000..25c53388e --- /dev/null +++ b/services/mail-api/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + + ru.javaops + parent + ../../parent/pom.xml + 1.0-SNAPSHOT + + + mail-api + 1.0-SNAPSHOT + Mail API + + + + ${project.groupId} + common + ${project.version} + + + + \ No newline at end of file diff --git a/services/mail-service/pom.xml b/services/mail-service/pom.xml new file mode 100644 index 000000000..2b4be6bad --- /dev/null +++ b/services/mail-service/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + + ru.javaops + parent-web + ../../parent-web/pom.xml + 1.0-SNAPSHOT + + + mail-service + 1.0-SNAPSHOT + war + Mail Service + + + + ${project.groupId} + mail-api + ${project.version} + + + \ No newline at end of file diff --git a/src/main/java/ru/javaops/masterjava/service/MailService.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceExecutor.java similarity index 98% rename from src/main/java/ru/javaops/masterjava/service/MailService.java rename to services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceExecutor.java index cef46e55e..e26f302d2 100644 --- a/src/main/java/ru/javaops/masterjava/service/MailService.java +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceExecutor.java @@ -1,4 +1,4 @@ -package ru.javaops.masterjava.service; +package ru.javaops.masterjava.service.mail; import java.util.ArrayList; import java.util.List; @@ -6,7 +6,7 @@ import java.util.concurrent.*; import java.util.stream.Collectors; -public class MailService { +public class MailServiceExecutor { private static final String OK = "OK"; private static final String INTERRUPTED_BY_FAULTS_NUMBER = "+++ Interrupted by faults number"; diff --git a/services/pom.xml b/services/pom.xml new file mode 100644 index 000000000..62ffc5e38 --- /dev/null +++ b/services/pom.xml @@ -0,0 +1,15 @@ + + 4.0.0 + + ru.javaops + services + pom + 1.0-SNAPSHOT + + Services + + mail-api + mail-service + + diff --git a/test/pom.xml b/test/pom.xml new file mode 100644 index 000000000..f469908cf --- /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 + RELEASE + + + org.openjdk.jmh + jmh-generator-annprocess + RELEASE + provided + + + \ No newline at end of file diff --git a/src/main/java/ru/javaops/masterjava/matrix/MainMatrix.java b/test/src/main/java/ru/javaops/masterjava/matrix/MainMatrix.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/matrix/MainMatrix.java rename to test/src/main/java/ru/javaops/masterjava/matrix/MainMatrix.java diff --git a/src/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java b/test/src/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java rename to test/src/main/java/ru/javaops/masterjava/matrix/MatrixBenchmark.java diff --git a/src/main/java/ru/javaops/masterjava/matrix/MatrixUtil.java b/test/src/main/java/ru/javaops/masterjava/matrix/MatrixUtil.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/matrix/MatrixUtil.java rename to test/src/main/java/ru/javaops/masterjava/matrix/MatrixUtil.java diff --git a/web/pom.xml b/web/pom.xml new file mode 100644 index 000000000..76ff400e0 --- /dev/null +++ b/web/pom.xml @@ -0,0 +1,15 @@ + + 4.0.0 + + ru.javaops + web + pom + 1.0-SNAPSHOT + Web + + + upload + webapp + + diff --git a/web/upload/pom.xml b/web/upload/pom.xml new file mode 100644 index 000000000..881475191 --- /dev/null +++ b/web/upload/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + + ru.javaops + parent-web + ../../parent-web/pom.xml + 1.0-SNAPSHOT + + + upload + 1.0-SNAPSHOT + war + Upload + + + upload + + + + + com.j2html + j2html + 1.2.0 + + + + \ No newline at end of file diff --git a/src/main/java/ru/javaops/masterjava/xml/schema/CityType.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/CityType.java similarity index 85% rename from src/main/java/ru/javaops/masterjava/xml/schema/CityType.java rename to web/upload/src/main/java/ru/javaops/masterjava/xml/schema/CityType.java index 029e352cb..6a83965e1 100644 --- a/src/main/java/ru/javaops/masterjava/xml/schema/CityType.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/CityType.java @@ -1,13 +1,8 @@ + 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.*; import javax.xml.bind.annotation.adapters.CollapsedStringAdapter; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; diff --git a/src/main/java/ru/javaops/masterjava/xml/schema/FlagType.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/FlagType.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/xml/schema/FlagType.java rename to web/upload/src/main/java/ru/javaops/masterjava/xml/schema/FlagType.java diff --git a/src/main/java/ru/javaops/masterjava/xml/schema/GroupType.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/GroupType.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/xml/schema/GroupType.java rename to web/upload/src/main/java/ru/javaops/masterjava/xml/schema/GroupType.java diff --git a/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java rename to web/upload/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java diff --git a/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/xml/schema/Payload.java rename to web/upload/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java diff --git a/src/main/java/ru/javaops/masterjava/xml/schema/Project.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/Project.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/xml/schema/Project.java rename to web/upload/src/main/java/ru/javaops/masterjava/xml/schema/Project.java diff --git a/src/main/java/ru/javaops/masterjava/xml/schema/User.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/User.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/xml/schema/User.java rename to web/upload/src/main/java/ru/javaops/masterjava/xml/schema/User.java diff --git a/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java rename to web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java diff --git a/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java rename to web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java diff --git a/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java rename to web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java diff --git a/src/main/java/ru/javaops/masterjava/xml/util/Schemas.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/Schemas.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/xml/util/Schemas.java rename to web/upload/src/main/java/ru/javaops/masterjava/xml/util/Schemas.java diff --git a/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java rename to web/upload/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java diff --git a/src/main/java/ru/javaops/masterjava/xml/util/XPathProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/XPathProcessor.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/xml/util/XPathProcessor.java rename to web/upload/src/main/java/ru/javaops/masterjava/xml/util/XPathProcessor.java diff --git a/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java similarity index 100% rename from src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java rename to web/upload/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java diff --git a/src/main/resources/cities.xsl b/web/upload/src/main/resources/cities.xsl similarity index 100% rename from src/main/resources/cities.xsl rename to web/upload/src/main/resources/cities.xsl diff --git a/src/main/resources/groups.xsl b/web/upload/src/main/resources/groups.xsl similarity index 100% rename from src/main/resources/groups.xsl rename to web/upload/src/main/resources/groups.xsl diff --git a/src/main/resources/payload.xsd b/web/upload/src/main/resources/payload.xsd similarity index 100% rename from src/main/resources/payload.xsd rename to web/upload/src/main/resources/payload.xsd diff --git a/src/test/java/ru/javaops/masterjava/MainXml.java b/web/upload/src/test/java/ru/javaops/masterjava/MainXml.java similarity index 97% rename from src/test/java/ru/javaops/masterjava/MainXml.java rename to web/upload/src/test/java/ru/javaops/masterjava/MainXml.java index 6bdee7eb9..875be977c 100644 --- a/src/test/java/ru/javaops/masterjava/MainXml.java +++ b/web/upload/src/test/java/ru/javaops/masterjava/MainXml.java @@ -20,7 +20,6 @@ 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.*; @@ -74,9 +73,7 @@ public static Set parseByJaxb(String projectName, URL payloadUrl) throws E 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)) - ); + .toCollection(() -> new TreeSet<>(USER_COMPARATOR)); } public static Set processByStax(String projectName, URL payloadUrl) throws Exception { diff --git a/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java b/web/upload/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java similarity index 100% rename from src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java rename to web/upload/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java diff --git a/src/test/java/ru/javaops/masterjava/xml/util/StaxStreamProcessorTest.java b/web/upload/src/test/java/ru/javaops/masterjava/xml/util/StaxStreamProcessorTest.java similarity index 100% rename from src/test/java/ru/javaops/masterjava/xml/util/StaxStreamProcessorTest.java rename to web/upload/src/test/java/ru/javaops/masterjava/xml/util/StaxStreamProcessorTest.java diff --git a/src/test/java/ru/javaops/masterjava/xml/util/XPathProcessorTest.java b/web/upload/src/test/java/ru/javaops/masterjava/xml/util/XPathProcessorTest.java similarity index 100% rename from src/test/java/ru/javaops/masterjava/xml/util/XPathProcessorTest.java rename to web/upload/src/test/java/ru/javaops/masterjava/xml/util/XPathProcessorTest.java diff --git a/src/test/java/ru/javaops/masterjava/xml/util/XsltProcessorTest.java b/web/upload/src/test/java/ru/javaops/masterjava/xml/util/XsltProcessorTest.java similarity index 100% rename from src/test/java/ru/javaops/masterjava/xml/util/XsltProcessorTest.java rename to web/upload/src/test/java/ru/javaops/masterjava/xml/util/XsltProcessorTest.java diff --git a/src/test/resources/city.xml b/web/upload/src/test/resources/city.xml similarity index 100% rename from src/test/resources/city.xml rename to web/upload/src/test/resources/city.xml diff --git a/src/test/resources/payload.xml b/web/upload/src/test/resources/payload.xml similarity index 100% rename from src/test/resources/payload.xml rename to web/upload/src/test/resources/payload.xml diff --git a/web/webapp/pom.xml b/web/webapp/pom.xml new file mode 100644 index 000000000..60bb1c8c3 --- /dev/null +++ b/web/webapp/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + + ru.javaops + parent-web + ../../parent-web/pom.xml + 1.0-SNAPSHOT + + + webapp + 1.0-SNAPSHOT + war + WebApp + + + webapp + + + + + ${project.groupId} + common + ${project.version} + + + \ No newline at end of file From a8a3c69dbe55dbe22eeb217eb333514684218af7 Mon Sep 17 00:00:00 2001 From: booktest Date: Tue, 8 Jan 2019 14:17:39 +0300 Subject: [PATCH 18/83] Lesson04 - HW03 thymeleaf and jaxb-stax user upload --- parent-web/pom.xml | 5 ++ pom.xml | 2 +- web/common-web/pom.xml | 37 ++++++++++ .../common/web/ThymeleafListener.java | 20 ++++++ .../masterjava/common/web/ThymeleafUtil.java | 24 +++++++ web/pom.xml | 1 + .../ru/javaops/masterjava/model/User.java | 63 ++++++++++++++++ .../ru/javaops/masterjava/model/UserFlag.java | 11 +++ .../masterjava/upload/UploadServlet.java | 51 +++++++++++++ .../masterjava/upload/UserProcessor.java | 32 +++++++++ .../masterjava/xml/util/JaxbMarshaller.java | 15 ++-- .../masterjava/xml/util/JaxbParser.java | 71 ++++++++----------- .../masterjava/xml/util/JaxbUnmarshaller.java | 16 ++--- .../webapp/WEB-INF/templates/exception.html | 20 ++++++ .../main/webapp/WEB-INF/templates/result.html | 27 +++++++ .../main/webapp/WEB-INF/templates/upload.html | 16 +++++ .../java/ru/javaops/masterjava/MainXml.java | 15 ++-- .../masterjava/xml/util/JaxbParserTest.java | 22 +++--- 18 files changed, 375 insertions(+), 73 deletions(-) create mode 100644 web/common-web/pom.xml create mode 100644 web/common-web/src/main/java/ru/javaops/masterjava/common/web/ThymeleafListener.java create mode 100644 web/common-web/src/main/java/ru/javaops/masterjava/common/web/ThymeleafUtil.java create mode 100644 web/upload/src/main/java/ru/javaops/masterjava/model/User.java create mode 100644 web/upload/src/main/java/ru/javaops/masterjava/model/UserFlag.java create mode 100644 web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java create mode 100644 web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java create mode 100644 web/upload/src/main/webapp/WEB-INF/templates/exception.html create mode 100644 web/upload/src/main/webapp/WEB-INF/templates/result.html create mode 100644 web/upload/src/main/webapp/WEB-INF/templates/upload.html diff --git a/parent-web/pom.xml b/parent-web/pom.xml index e46b5a49f..6083235c1 100644 --- a/parent-web/pom.xml +++ b/parent-web/pom.xml @@ -30,6 +30,11 @@ + + ${project.groupId} + common-web + ${project.version} + javax.servlet javax.servlet-api diff --git a/pom.xml b/pom.xml index f0c5cbbb2..000b68b94 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ 1.0-SNAPSHOT Masterjava Root - https://github.com/JavaOPs/masterjava + https://github.com/immus/masterjava common diff --git a/web/common-web/pom.xml b/web/common-web/pom.xml new file mode 100644 index 000000000..98ba46590 --- /dev/null +++ b/web/common-web/pom.xml @@ -0,0 +1,37 @@ + + + 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.8.RELEASE + + + \ 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/pom.xml b/web/pom.xml index 76ff400e0..ddb4a428b 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -9,6 +9,7 @@ Web + common-web upload webapp diff --git a/web/upload/src/main/java/ru/javaops/masterjava/model/User.java b/web/upload/src/main/java/ru/javaops/masterjava/model/User.java new file mode 100644 index 000000000..d7c3f8cd3 --- /dev/null +++ b/web/upload/src/main/java/ru/javaops/masterjava/model/User.java @@ -0,0 +1,63 @@ +package ru.javaops.masterjava.model; + +import java.util.Objects; + +public class User { + private final Integer id; + private final String fullName; + private final String email; + private final UserFlag flag; + + public User(String fullName, String email, UserFlag flag) { + this(null, fullName, email, flag); + } + + public User(Integer id, String fullName, String email, UserFlag flag) { + this.id = id; + this.fullName = fullName; + this.email = email; + this.flag = flag; + } + + public Integer getId() { + return id; + } + + public String getFullName() { + return fullName; + } + + public String getEmail() { + return email; + } + + public UserFlag getFlag() { + return flag; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + User user = (User) o; + return Objects.equals(id, user.id) && + Objects.equals(fullName, user.fullName) && + Objects.equals(email, user.email) && + flag == user.flag; + } + + @Override + public int hashCode() { + return Objects.hash(id, fullName, email, flag); + } + + @Override + public String toString() { + return "User (" + + "id=" + id + + ", fullName='" + fullName + '\'' + + ", email='" + email + '\'' + + ", flag=" + flag + + ')'; + } +} diff --git a/web/upload/src/main/java/ru/javaops/masterjava/model/UserFlag.java b/web/upload/src/main/java/ru/javaops/masterjava/model/UserFlag.java new file mode 100644 index 000000000..603626ae1 --- /dev/null +++ b/web/upload/src/main/java/ru/javaops/masterjava/model/UserFlag.java @@ -0,0 +1,11 @@ +package ru.javaops.masterjava.model; + +/** + * gkislin + * 13.10.2016 + */ +public enum UserFlag { + active, + deleted, + superuser; +} diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java new file mode 100644 index 000000000..3831eda27 --- /dev/null +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java @@ -0,0 +1,51 @@ +package ru.javaops.masterjava.upload; + +import org.thymeleaf.context.WebContext; +import ru.javaops.masterjava.model.User; + +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(urlPatterns = "/", loadOnStartup = 1) +@MultipartConfig(fileSizeThreshold = 1024 * 1024 * 10) //10 MB in memory limit +public class UploadServlet extends HttpServlet { + + private final UserProcessor userProcessor = new UserProcessor(); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + final WebContext webContext = new WebContext(req, resp, req.getServletContext(), req.getLocale()); + engine.process("upload", webContext, resp.getWriter()); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + final WebContext webContext = new WebContext(req, resp, req.getServletContext(), req.getLocale()); + + try { +// http://docs.oracle.com/javaee/6/tutorial/doc/glraq.html + Part filePart = req.getPart("fileToUpload"); + if (filePart.getSize() == 0) { + throw new IllegalStateException("Upload file have not been selected"); + } + try (InputStream is = filePart.getInputStream()) { + List users = userProcessor.process(is); + webContext.setVariable("users", users); + engine.process("result", webContext, resp.getWriter()); + } + } catch (Exception e) { + webContext.setVariable("exception", e); + engine.process("exception", webContext, resp.getWriter()); + } + } +} diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java new file mode 100644 index 000000000..f8c574bf8 --- /dev/null +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java @@ -0,0 +1,32 @@ +package ru.javaops.masterjava.upload; + +import ru.javaops.masterjava.model.User; +import ru.javaops.masterjava.model.UserFlag; +import ru.javaops.masterjava.xml.schema.ObjectFactory; +import ru.javaops.masterjava.xml.util.JaxbParser; +import ru.javaops.masterjava.xml.util.JaxbUnmarshaller; +import ru.javaops.masterjava.xml.util.StaxStreamProcessor; + +import javax.xml.bind.JAXBException; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class UserProcessor { + private static final JaxbParser jaxbParser = new JaxbParser(ObjectFactory.class); + + public List process(final InputStream is) throws XMLStreamException, JAXBException { + final StaxStreamProcessor processor = new StaxStreamProcessor(is); + List users = new ArrayList<>(); + + JaxbUnmarshaller unmarshaller = jaxbParser.createUnmarshaller(); + while (processor.doUntil(XMLEvent.START_ELEMENT, "User")) { + ru.javaops.masterjava.xml.schema.User xmlUser = unmarshaller.unmarshal(processor.getReader(), ru.javaops.masterjava.xml.schema.User.class); + final User user = new User(xmlUser.getValue(), xmlUser.getEmail(), UserFlag.valueOf(xmlUser.getFlag().value())); + users.add(user); + } + return users; + } +} diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java index d6006800f..507825dda 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java @@ -18,11 +18,15 @@ public JaxbMarshaller(JAXBContext ctx) throws JAXBException { marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true); } - public void setProperty(String prop, Object value) throws PropertyException { - marshaller.setProperty(prop, value); + public void setProperty(String prop, Object value) { + try { + marshaller.setProperty(prop, value); + } catch (PropertyException e) { + throw new IllegalArgumentException(e); + } } - public synchronized void setSchema(Schema schema) { + public void setSchema(Schema schema) { marshaller.setSchema(schema); } @@ -32,8 +36,7 @@ public String marshal(Object instance) throws JAXBException { return sw.toString(); } - public synchronized void marshal(Object instance, Writer writer) throws JAXBException { + public void marshal(Object instance, Writer writer) throws JAXBException { marshaller.marshal(instance, writer); } - -} +} \ No newline at end of file diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java index 563d53ba0..8aff55510 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java @@ -4,28 +4,26 @@ import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; -import javax.xml.bind.PropertyException; -import javax.xml.stream.XMLStreamReader; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; -import java.io.*; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; /** - * Marshalling/Unmarshalling JAXB helper - * XML Facade + * Marshalling/Unmarshalling JAXB facade */ public class JaxbParser { - protected JaxbMarshaller jaxbMarshaller; - protected JaxbUnmarshaller jaxbUnmarshaller; + private JAXBContext ctx; protected Schema schema; public JaxbParser(Class... classesToBeBound) { try { init(JAXBContext.newInstance(classesToBeBound)); } catch (JAXBException e) { - throw new IllegalArgumentException(e); + throw new IllegalStateException(e); } } @@ -34,53 +32,42 @@ public JaxbParser(String context) { try { init(JAXBContext.newInstance(context)); } catch (JAXBException e) { - throw new IllegalArgumentException(e); + throw new IllegalStateException(e); } } - private void init(JAXBContext ctx) throws JAXBException { - jaxbMarshaller = new JaxbMarshaller(ctx); - jaxbUnmarshaller = new JaxbUnmarshaller(ctx); + private void init(JAXBContext ctx) { + this.ctx = 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); - } - - public T unmarshal(XMLStreamReader reader, Class elementClass) throws JAXBException { - return jaxbUnmarshaller.unmarshal(reader, elementClass); - } - - // Marshaller - public void setMarshallerProperty(String prop, Object value) { + // https://stackoverflow.com/a/7400735/548473 + public JaxbMarshaller createMarshaller() { try { - jaxbMarshaller.setProperty(prop, value); - } catch (PropertyException e) { - throw new IllegalArgumentException(e); + JaxbMarshaller marshaller = new JaxbMarshaller(ctx); + if (schema != null) { + marshaller.setSchema(schema); + } + return marshaller; + } catch (JAXBException e) { + throw new IllegalStateException(e); } } - public String marshal(Object instance) throws JAXBException { - return jaxbMarshaller.marshal(instance); - } - - public void marshal(Object instance, Writer writer) throws JAXBException { - jaxbMarshaller.marshal(instance, writer); + // https://stackoverflow.com/a/7400735/548473 + public JaxbUnmarshaller createUnmarshaller() { + try { + JaxbUnmarshaller unmarshaller = new JaxbUnmarshaller(ctx); + if (schema != null) { + unmarshaller.setSchema(schema); + } + return unmarshaller; + } catch (JAXBException e) { + throw new IllegalStateException(e); + } } public void setSchema(Schema schema) { this.schema = schema; - jaxbUnmarshaller.setSchema(schema); - jaxbMarshaller.setSchema(schema); } public void validate(String str) throws IOException, SAXException { diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java index 4738e4c2c..f9f29c2c9 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java @@ -16,23 +16,23 @@ public JaxbUnmarshaller(JAXBContext ctx) throws JAXBException { unmarshaller = ctx.createUnmarshaller(); } - public synchronized void setSchema(Schema schema) { + public void setSchema(Schema schema) { unmarshaller.setSchema(schema); } - public synchronized Object unmarshal(InputStream is) throws JAXBException { - return unmarshaller.unmarshal(is); + public T unmarshal(InputStream is) throws JAXBException { + return (T) unmarshaller.unmarshal(is); } - public synchronized Object unmarshal(Reader reader) throws JAXBException { - return unmarshaller.unmarshal(reader); + public T unmarshal(Reader reader) throws JAXBException { + return (T) unmarshaller.unmarshal(reader); } - public Object unmarshal(String xml) throws JAXBException { - return unmarshal(new StringReader(xml)); + public T unmarshal(String str) throws JAXBException { + return (T) unmarshal(new StringReader(str)); } - public synchronized T unmarshal(XMLStreamReader reader, Class elementClass) throws JAXBException { + public T unmarshal(XMLStreamReader reader, Class elementClass) throws JAXBException { return unmarshaller.unmarshal(reader, elementClass).getValue(); } } \ No newline at end of file diff --git a/web/upload/src/main/webapp/WEB-INF/templates/exception.html b/web/upload/src/main/webapp/WEB-INF/templates/exception.html new file mode 100644 index 000000000..2b5e6fe5a --- /dev/null +++ b/web/upload/src/main/webapp/WEB-INF/templates/exception.html @@ -0,0 +1,20 @@ + + + + Application error + + + +
+
+
+

Application error:

+ +

exception.message

+
    +
  • +
+
+
+ + \ No newline at end of file diff --git a/web/upload/src/main/webapp/WEB-INF/templates/result.html b/web/upload/src/main/webapp/WEB-INF/templates/result.html new file mode 100644 index 000000000..01ec88cfd --- /dev/null +++ b/web/upload/src/main/webapp/WEB-INF/templates/result.html @@ -0,0 +1,27 @@ + + + + Uploaded users + + +

Upload XML

+

Uploaded users

+ + + + + + + + + + + + + + + + +
Full NameEmailFlag
+ + \ No newline at end of file diff --git a/web/upload/src/main/webapp/WEB-INF/templates/upload.html b/web/upload/src/main/webapp/WEB-INF/templates/upload.html new file mode 100644 index 000000000..d48c3123e --- /dev/null +++ b/web/upload/src/main/webapp/WEB-INF/templates/upload.html @@ -0,0 +1,16 @@ + + + Upload XML + + +
+

Select xml file to upload

+

+
+

+

+ +

+
+ + \ No newline at end of file diff --git a/web/upload/src/test/java/ru/javaops/masterjava/MainXml.java b/web/upload/src/test/java/ru/javaops/masterjava/MainXml.java index 875be977c..1e3f76440 100644 --- a/web/upload/src/test/java/ru/javaops/masterjava/MainXml.java +++ b/web/upload/src/test/java/ru/javaops/masterjava/MainXml.java @@ -8,10 +8,7 @@ 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 ru.javaops.masterjava.xml.util.*; import javax.xml.stream.events.XMLEvent; import java.io.InputStream; @@ -59,10 +56,11 @@ public static void main(String[] args) throws Exception { public static Set parseByJaxb(String projectName, URL payloadUrl) throws Exception { //JaxbParser parser = new JaxbParser(ObjectFactory.class); + JaxbUnmarshaller unmarshaller = parser.createUnmarshaller(); parser.setSchema(Schemas.ofClasspath("payload.xsd")); Payload payload; try (InputStream is = payloadUrl.openStream()) { - payload = parser.unmarshal(is); + payload = unmarshaller.unmarshal(is); } Project project = StreamEx.of(payload.getProjects().getProject()) @@ -97,12 +95,13 @@ public static Set processByStax(String projectName, URL payloadUrl) throws // Users loop Set users = new TreeSet<>(USER_COMPARATOR); - //JaxbParser parser = new JaxbParser(User.class); + //JaxbParser parser = new JaxbParser(ObjectFactory.class); + JaxbUnmarshaller unmarshaller = parser.createUnmarshaller(); + unmarshaller.setSchema(null); while (processor.doUntil(XMLEvent.START_ELEMENT, "User")) { String groupRefs = processor.getAttribute("groupRefs"); if (!Collections.disjoint(groupNames, Splitter.on(' ').splitToList(nullToEmpty(groupRefs)))) { - parser.setSchema(null); - User user = parser.unmarshal(processor.getReader(), User.class); + User user = unmarshaller.unmarshal(processor.getReader(), User.class); users.add(user); } } diff --git a/web/upload/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java b/web/upload/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java index 623265428..4754b17d6 100644 --- a/web/upload/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java +++ b/web/upload/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java @@ -10,31 +10,37 @@ import javax.xml.namespace.QName; public class JaxbParserTest { - private static final JaxbParser JAXB_PARSER = new JaxbParser(ObjectFactory.class); + // https://google.github.io/styleguide/javaguide.html#s5.2.4-constant-names + private static final JaxbParser jaxbParser; + private static final JaxbMarshaller marshaller; + private static final JaxbUnmarshaller unmarshaller; static { - JAXB_PARSER.setSchema(Schemas.ofClasspath("payload.xsd")); + jaxbParser = new JaxbParser(ObjectFactory.class); + jaxbParser.setSchema(Schemas.ofClasspath("payload.xsd")); + marshaller = jaxbParser.createMarshaller(); + unmarshaller = jaxbParser.createUnmarshaller(); } @Test public void testPayload() throws Exception { // JaxbParserTest.class.getResourceAsStream("/city.xml") - Payload payload = JAXB_PARSER.unmarshal( + Payload payload = unmarshaller.unmarshal( Resources.getResource("payload.xml").openStream()); - String strPayload = JAXB_PARSER.marshal(payload); - JAXB_PARSER.validate(strPayload); + String strPayload = marshaller.marshal(payload); + jaxbParser.validate(strPayload); System.out.println(strPayload); } @Test public void testCity() throws Exception { - JAXBElement cityElement = JAXB_PARSER.unmarshal( + JAXBElement cityElement = unmarshaller.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); + String strCity = marshaller.marshal(cityElement2); + jaxbParser.validate(strCity); System.out.println(strCity); } } \ No newline at end of file From bb2ac70b892a9285a8906b06f675eed06a23d410 Mon Sep 17 00:00:00 2001 From: booktest Date: Tue, 8 Jan 2019 15:20:24 +0300 Subject: [PATCH 19/83] Lesson04 - dependencies and properties for command "mvn project-info-reports:dependencies" --- parent/pom.xml | 2 ++ pom.xml | 3 +++ 2 files changed, 5 insertions(+) diff --git a/parent/pom.xml b/parent/pom.xml index d7b056d1d..c32ae495a 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -14,6 +14,8 @@ 1.8 UTF-8 UTF-8 + + false diff --git a/pom.xml b/pom.xml index 000b68b94..bcbabf947 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,9 @@ https://github.com/immus/masterjava + parent + parent-web + common test From 1718f147fa339db02b0f72655ec01c926778aeaf Mon Sep 17 00:00:00 2001 From: booktest Date: Tue, 8 Jan 2019 15:37:53 +0300 Subject: [PATCH 20/83] Lesson04 - fix convergence for command "mvn project-info-reports:dependency-convergence" --- parent/pom.xml | 2 +- test/pom.xml | 4 ++-- web/upload/pom.xml | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index c32ae495a..e69f61baf 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -50,7 +50,7 @@ one.util streamex - RELEASE + 0.6.6 diff --git a/test/pom.xml b/test/pom.xml index f469908cf..5ad3d7da2 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -64,12 +64,12 @@ org.openjdk.jmh jmh-core - RELEASE + 1.19 org.openjdk.jmh jmh-generator-annprocess - RELEASE + 1.19 provided
diff --git a/web/upload/pom.xml b/web/upload/pom.xml index 881475191..2c4d5ab15 100644 --- a/web/upload/pom.xml +++ b/web/upload/pom.xml @@ -25,6 +25,12 @@ com.j2html j2html 1.2.0 + + + com.google.guava + guava + + From 15ab3e4803891c6ab675a3542d1ead774648754f Mon Sep 17 00:00:00 2001 From: booktest Date: Tue, 8 Jan 2019 15:49:13 +0300 Subject: [PATCH 21/83] Lesson04 - enforce plugin for searching dependency convergence --- parent/pom.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/parent/pom.xml b/parent/pom.xml index e69f61baf..464395e02 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -38,6 +38,24 @@ -Dfile.encoding=UTF-8 + + org.apache.maven.plugins + maven-enforcer-plugin + 1.4.1 + + + enforce + + + + + + + enforce + + + + From 4baf8d07f8dd73af99d13c7efea741ac9a02f998 Mon Sep 17 00:00:00 2001 From: booktest Date: Tue, 8 Jan 2019 20:24:14 +0300 Subject: [PATCH 22/83] Lesson04 - logging --- parent-web/pom.xml | 13 +++++++++++++ parent/pom.xml | 30 ++++++++++++++++++++++++++++++ web/common-web/pom.xml | 6 ++++++ 3 files changed, 49 insertions(+) diff --git a/parent-web/pom.xml b/parent-web/pom.xml index 6083235c1..57c9ba570 100644 --- a/parent-web/pom.xml +++ b/parent-web/pom.xml @@ -27,6 +27,19 @@ + + + + ${masterjava.config} + true + + logback.xml + + + + src/main/resources + + diff --git a/parent/pom.xml b/parent/pom.xml index 464395e02..197c1eb2a 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -14,7 +14,11 @@ 1.8 UTF-8 UTF-8 + + 1.2.3 + 1.7.25 + /apps_settings/masterjava/config/ false @@ -57,6 +61,18 @@ + + + + ${masterjava.config} + + logback-test.xml + + + + src/test/resources + + @@ -71,6 +87,20 @@ 0.6.6 + + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + runtime + + + + ch.qos.logback + logback-classic + ${logback.version} + + junit diff --git a/web/common-web/pom.xml b/web/common-web/pom.xml index 98ba46590..37ca1f642 100644 --- a/web/common-web/pom.xml +++ b/web/common-web/pom.xml @@ -32,6 +32,12 @@ org.thymeleaf thymeleaf 3.0.8.RELEASE + + + org.slf4j + slf4j-api + + \ No newline at end of file From c075d8127b2bedb77f99992ab71f4347c0d0a970 Mon Sep 17 00:00:00 2001 From: booktest Date: Tue, 8 Jan 2019 20:40:53 +0300 Subject: [PATCH 23/83] Lesson04 - config files logging and context for ${TOMCAT_HOME}/conf --- config_templates/context.xml | 49 +++++++++++++++++++++++++++++++ config_templates/logback-test.xml | 20 +++++++++++++ config_templates/logback.xml | 39 ++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 config_templates/context.xml create mode 100644 config_templates/logback-test.xml create mode 100644 config_templates/logback.xml diff --git a/config_templates/context.xml b/config_templates/context.xml new file mode 100644 index 000000000..40bb8aa0c --- /dev/null +++ b/config_templates/context.xml @@ -0,0 +1,49 @@ + + + + + + + + WEB-INF/web.xml + ${catalina.base}/conf/web.xml + + + + + + + + + diff --git a/config_templates/logback-test.xml b/config_templates/logback-test.xml new file mode 100644 index 000000000..b6815ab8d --- /dev/null +++ b/config_templates/logback-test.xml @@ -0,0 +1,20 @@ + + + + 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..95fd3863f --- /dev/null +++ b/config_templates/logback.xml @@ -0,0 +1,39 @@ + + + + + + + + + + ${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 + + + + + + + + + + From 689b8cb901c0eaa6e5bd0be1a95df33612f17f90 Mon Sep 17 00:00:00 2001 From: booktest Date: Sat, 12 Jan 2019 18:47:55 +0300 Subject: [PATCH 24/83] Lesson04 - add persist module --- config_templates/logback-test.xml | 1 + config_templates/sql/initDB.sql | 15 ++++++ persist/pom.xml | 46 ++++++++++++++++ .../masterjava/persist/DBIProvider.java | 52 +++++++++++++++++++ .../masterjava/persist/dao/AbstractDao.java | 5 ++ .../masterjava/persist/dao/UserDao.java | 37 +++++++++++++ .../masterjava/persist/model/BaseEntity.java | 37 +++++++++++++ .../masterjava/persist}/model/User.java | 43 ++++++++++----- .../masterjava/persist}/model/UserFlag.java | 2 +- .../masterjava/persist/DBITestProvider.java | 20 +++++++ .../masterjava/persist/UserTestData.java | 37 +++++++++++++ .../persist/dao/AbstractDaoTest.java | 16 ++++++ .../masterjava/persist/dao/UserDaoTest.java | 35 +++++++++++++ pom.xml | 1 + web/upload/pom.xml | 5 ++ .../masterjava/upload/UploadServlet.java | 2 +- .../masterjava/upload/UserProcessor.java | 4 +- 17 files changed, 342 insertions(+), 16 deletions(-) create mode 100644 config_templates/sql/initDB.sql create mode 100644 persist/pom.xml create mode 100644 persist/src/main/java/ru/javaops/masterjava/persist/DBIProvider.java create mode 100644 persist/src/main/java/ru/javaops/masterjava/persist/dao/AbstractDao.java create mode 100644 persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java create mode 100644 persist/src/main/java/ru/javaops/masterjava/persist/model/BaseEntity.java rename {web/upload/src/main/java/ru/javaops/masterjava => persist/src/main/java/ru/javaops/masterjava/persist}/model/User.java (69%) rename {web/upload/src/main/java/ru/javaops/masterjava => persist/src/main/java/ru/javaops/masterjava/persist}/model/UserFlag.java (68%) create mode 100644 persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java create mode 100644 persist/src/test/java/ru/javaops/masterjava/persist/UserTestData.java create mode 100644 persist/src/test/java/ru/javaops/masterjava/persist/dao/AbstractDaoTest.java create mode 100644 persist/src/test/java/ru/javaops/masterjava/persist/dao/UserDaoTest.java diff --git a/config_templates/logback-test.xml b/config_templates/logback-test.xml index b6815ab8d..739dd6dcf 100644 --- a/config_templates/logback-test.xml +++ b/config_templates/logback-test.xml @@ -12,6 +12,7 @@ + diff --git a/config_templates/sql/initDB.sql b/config_templates/sql/initDB.sql new file mode 100644 index 000000000..888ba8141 --- /dev/null +++ b/config_templates/sql/initDB.sql @@ -0,0 +1,15 @@ +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 +); + diff --git a/persist/pom.xml b/persist/pom.xml new file mode 100644 index 000000000..6efb20ca3 --- /dev/null +++ b/persist/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + + ru.javaops + parent + ../parent/pom.xml + 1.0-SNAPSHOT + + + persist + 1.0-SNAPSHOT + Persist + + + + ${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 + 42.1.4 + + + \ 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..7cfaab77f --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/DBIProvider.java @@ -0,0 +1,52 @@ +package ru.javaops.masterjava.persist; + +import org.skife.jdbi.v2.DBI; +import org.skife.jdbi.v2.logging.SLF4JLog; +import org.skife.jdbi.v2.tweak.ConnectionFactory; +import org.slf4j.Logger; +import ru.javaops.masterjava.persist.dao.AbstractDao; + +import javax.naming.InitialContext; +import javax.sql.DataSource; + +import static org.slf4j.LoggerFactory.getLogger; + +public class DBIProvider { + private static final Logger log = getLogger(DBIProvider.class); + + 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..8b3ee1099 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/AbstractDao.java @@ -0,0 +1,5 @@ +package ru.javaops.masterjava.persist.dao; + +public interface AbstractDao { + void clean(); +} 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..e0230cccf --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java @@ -0,0 +1,37 @@ +package ru.javaops.masterjava.persist.dao; + +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.model.User; + +import java.util.List; + +@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; + } + + @SqlUpdate("INSERT INTO users (full_name, email, flag) VALUES (:fullName, :email, CAST(:flag AS user_flag)) ") + @GetGeneratedKeys + abstract int insertGeneratedId(@BindBean User user); + + @SqlUpdate("INSERT INTO users (id, full_name, email, flag) VALUES (:id, :fullName, :email, CAST(:flag AS user_flag)) ") + 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") + @Override + public abstract void clean(); +} 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..415d3d037 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/BaseEntity.java @@ -0,0 +1,37 @@ +package ru.javaops.masterjava.persist.model; + +abstract public class BaseEntity { + protected BaseEntity() { + } + + protected BaseEntity(Integer id) { + this.id = id; + } + + protected Integer id; + + public Integer getId() { + return id; + } + + protected void setId(Integer id) { + this.id = 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/web/upload/src/main/java/ru/javaops/masterjava/model/User.java b/persist/src/main/java/ru/javaops/masterjava/persist/model/User.java similarity index 69% rename from web/upload/src/main/java/ru/javaops/masterjava/model/User.java rename to persist/src/main/java/ru/javaops/masterjava/persist/model/User.java index d7c3f8cd3..f4445cb50 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/model/User.java +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/User.java @@ -1,28 +1,31 @@ -package ru.javaops.masterjava.model; +package ru.javaops.masterjava.persist.model; + +import com.bertoncelj.jdbi.entitymapper.Column; import java.util.Objects; -public class User { - private final Integer id; - private final String fullName; - private final String email; - private final UserFlag flag; +public class User extends BaseEntity { + @Column("full_name") + private String fullName; + + private String email; + + private UserFlag flag; + + public User() { + } public User(String fullName, String email, UserFlag flag) { this(null, fullName, email, flag); } public User(Integer id, String fullName, String email, UserFlag flag) { - this.id = id; + super(id); this.fullName = fullName; this.email = email; this.flag = flag; } - public Integer getId() { - return id; - } - public String getFullName() { return fullName; } @@ -35,6 +38,22 @@ public UserFlag getFlag() { return flag; } + public void setId(Integer id) { + this.id = id; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setFlag(UserFlag flag) { + this.flag = flag; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -60,4 +79,4 @@ public String toString() { ", flag=" + flag + ')'; } -} +} \ No newline at end of file diff --git a/web/upload/src/main/java/ru/javaops/masterjava/model/UserFlag.java b/persist/src/main/java/ru/javaops/masterjava/persist/model/UserFlag.java similarity index 68% rename from web/upload/src/main/java/ru/javaops/masterjava/model/UserFlag.java rename to persist/src/main/java/ru/javaops/masterjava/persist/model/UserFlag.java index 603626ae1..bc2f69183 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/model/UserFlag.java +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/UserFlag.java @@ -1,4 +1,4 @@ -package ru.javaops.masterjava.model; +package ru.javaops.masterjava.persist.model; /** * gkislin 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..2b2f1d71b --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java @@ -0,0 +1,20 @@ +package ru.javaops.masterjava.persist; + +import java.sql.DriverManager; + +public class DBITestProvider { + public static void initDBI() { + initDBI("jdbc:postgresql://localhost:5432/masterjava", "user", "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/UserTestData.java b/persist/src/test/java/ru/javaops/masterjava/persist/UserTestData.java new file mode 100644 index 000000000..7c13c0499 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/UserTestData.java @@ -0,0 +1,37 @@ +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; + +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() { + ADMIN = new User("Admin", "admin@javaops.ru", UserFlag.superuser); + DELETED = new User("Deleted", "deleted@yandex.ru", UserFlag.deleted); + FULL_NAME = new User("Full Name", "gmail@gmail.com", UserFlag.active); + USER1 = new User("User1", "user1@gmail.com", UserFlag.active); + USER2 = new User("User2", "user2@yandex.ru", UserFlag.active); + USER3 = new User("User3", "user3@yandex.ru", UserFlag.active); + 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..a65302fdd --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/dao/AbstractDaoTest.java @@ -0,0 +1,16 @@ +package ru.javaops.masterjava.persist.dao; + +import ru.javaops.masterjava.persist.DBIProvider; +import ru.javaops.masterjava.persist.DBITestProvider; + +public abstract class AbstractDaoTest { + static { + DBITestProvider.initDBI(); + } + + protected DAO dao; + + protected AbstractDaoTest(Class daoClass) { + this.dao = DBIProvider.getDao(daoClass); + } +} 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..b833a0577 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/dao/UserDaoTest.java @@ -0,0 +1,35 @@ +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; + +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); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index bcbabf947..f436d4226 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,7 @@ parent-web common + persist test web diff --git a/web/upload/pom.xml b/web/upload/pom.xml index 2c4d5ab15..b6521abf5 100644 --- a/web/upload/pom.xml +++ b/web/upload/pom.xml @@ -21,6 +21,11 @@ + + ${project.groupId} + persist + ${project.version} + com.j2html j2html diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java index 3831eda27..a2b583aa3 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java @@ -1,7 +1,7 @@ package ru.javaops.masterjava.upload; import org.thymeleaf.context.WebContext; -import ru.javaops.masterjava.model.User; +import ru.javaops.masterjava.persist.model.User; import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java index f8c574bf8..5cb3deb56 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java @@ -1,7 +1,7 @@ package ru.javaops.masterjava.upload; -import ru.javaops.masterjava.model.User; -import ru.javaops.masterjava.model.UserFlag; +import ru.javaops.masterjava.persist.model.User; +import ru.javaops.masterjava.persist.model.UserFlag; import ru.javaops.masterjava.xml.schema.ObjectFactory; import ru.javaops.masterjava.xml.util.JaxbParser; import ru.javaops.masterjava.xml.util.JaxbUnmarshaller; From f6d3df957a31d03950883509279f7bd1cd760d2f Mon Sep 17 00:00:00 2001 From: booktest Date: Sun, 13 Jan 2019 15:04:37 +0300 Subject: [PATCH 25/83] Lesson05 - HW4 upload chunk --- parent-web/pom.xml | 41 +++++++++++++++++ .../masterjava/persist/dao/UserDao.java | 8 +++- .../masterjava/persist/dao/UserDaoTest.java | 7 +++ .../masterjava/upload/UploadServlet.java | 46 +++++++++++++------ .../masterjava/upload/UserProcessor.java | 6 ++- .../main/webapp/WEB-INF/templates/upload.html | 8 +++- 6 files changed, 98 insertions(+), 18 deletions(-) diff --git a/parent-web/pom.xml b/parent-web/pom.xml index 57c9ba570..bf211d9dc 100644 --- a/parent-web/pom.xml +++ b/parent-web/pom.xml @@ -26,6 +26,47 @@ false + + + + org.codehaus.cargo + cargo-maven2-plugin + 1.6.5 + + + tomcat8x + + UTF-8 + + + + org.postgresql + postgresql + + + + + + + ${masterjava.config}/context.xml + conf/Catalina/localhost/ + context.xml.default + + + + + + ${project.groupId} + ${project.artifactId} + war + + ${project.build.finalName} + + + + + + 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 index e0230cccf..ff760f7bc 100644 --- a/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java @@ -2,6 +2,7 @@ import com.bertoncelj.jdbi.entitymapper.EntityMapperFactory; 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.model.User; @@ -20,11 +21,11 @@ public User insert(User user) { return user; } - @SqlUpdate("INSERT INTO users (full_name, email, flag) VALUES (:fullName, :email, CAST(:flag AS user_flag)) ") + @SqlUpdate("INSERT INTO users (full_name, email, flag) VALUES (:fullName, :email, CAST(:flag AS USER_FLAG)) ") @GetGeneratedKeys abstract int insertGeneratedId(@BindBean User user); - @SqlUpdate("INSERT INTO users (id, full_name, email, flag) VALUES (:id, :fullName, :email, CAST(:flag AS user_flag)) ") + @SqlUpdate("INSERT INTO users (id, full_name, email, flag) VALUES (:id, :fullName, :email, CAST(:flag AS USER_FLAG)) ") abstract void insertWitId(@BindBean User user); @SqlQuery("SELECT * FROM users ORDER BY full_name, email LIMIT :it") @@ -34,4 +35,7 @@ public User insert(User user) { @SqlUpdate("TRUNCATE users") @Override public abstract void clean(); + + @SqlBatch("INSERT INTO users (full_name, email, flag) VALUES (:fullName, :email, CAST(:flag AS USER_FLAG))") + public abstract void insertBatch(@BindBean List users, @BatchChunkSize int chunkSize); } 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 index b833a0577..92102ea59 100644 --- a/persist/src/test/java/ru/javaops/masterjava/persist/dao/UserDaoTest.java +++ b/persist/src/test/java/ru/javaops/masterjava/persist/dao/UserDaoTest.java @@ -32,4 +32,11 @@ 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()); + } } \ No newline at end of file diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java index a2b583aa3..7591e7142 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java @@ -1,5 +1,8 @@ package ru.javaops.masterjava.upload; +import com.google.common.collect.ImmutableMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.thymeleaf.context.WebContext; import ru.javaops.masterjava.persist.model.User; @@ -19,33 +22,48 @@ @WebServlet(urlPatterns = "/", loadOnStartup = 1) @MultipartConfig(fileSizeThreshold = 1024 * 1024 * 10) //10 MB in memory limit public class UploadServlet extends HttpServlet { + private static final Logger log = LoggerFactory.getLogger(UploadServlet.class); + private static final int CHUNK_SIZE = 2000; private final UserProcessor userProcessor = new UserProcessor(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - final WebContext webContext = new WebContext(req, resp, req.getServletContext(), req.getLocale()); - engine.process("upload", webContext, resp.getWriter()); + out(req, resp, "", CHUNK_SIZE); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - final WebContext webContext = new WebContext(req, resp, req.getServletContext(), req.getLocale()); - + String message; + int chunkSize = CHUNK_SIZE; try { // http://docs.oracle.com/javaee/6/tutorial/doc/glraq.html - Part filePart = req.getPart("fileToUpload"); - if (filePart.getSize() == 0) { - throw new IllegalStateException("Upload file have not been selected"); - } - try (InputStream is = filePart.getInputStream()) { - List users = userProcessor.process(is); - webContext.setVariable("users", users); - engine.process("result", webContext, resp.getWriter()); + 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 users = userProcessor.process(is, chunkSize); + log.info("Successfully uploaded " + users.size() + " users"); + final WebContext webContext = + new WebContext(req, resp, req.getServletContext(), req.getLocale(), + ImmutableMap.of("users", users)); + engine.process("result", webContext, resp.getWriter()); + return; + } } } catch (Exception e) { - webContext.setVariable("exception", e); - engine.process("exception", webContext, resp.getWriter()); + log.info(e.getMessage(), e); + message = e.toString(); } + out(req, resp, message, chunkSize); + } + + private void out(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("upload", webContext, resp.getWriter()); } } diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java index 5cb3deb56..31f4254a5 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java @@ -1,5 +1,7 @@ package ru.javaops.masterjava.upload; +import ru.javaops.masterjava.persist.DBIProvider; +import ru.javaops.masterjava.persist.dao.UserDao; import ru.javaops.masterjava.persist.model.User; import ru.javaops.masterjava.persist.model.UserFlag; import ru.javaops.masterjava.xml.schema.ObjectFactory; @@ -16,8 +18,9 @@ public class UserProcessor { private static final JaxbParser jaxbParser = new JaxbParser(ObjectFactory.class); + private static UserDao userDao = DBIProvider.getDao(UserDao.class); - public List process(final InputStream is) throws XMLStreamException, JAXBException { + public List process(final InputStream is, int chunkSize) throws XMLStreamException, JAXBException { final StaxStreamProcessor processor = new StaxStreamProcessor(is); List users = new ArrayList<>(); @@ -27,6 +30,7 @@ public List process(final InputStream is) throws XMLStreamException, JAXBE final User user = new User(xmlUser.getValue(), xmlUser.getEmail(), UserFlag.valueOf(xmlUser.getFlag().value())); users.add(user); } + userDao.insertBatch(users, chunkSize); return users; } } diff --git a/web/upload/src/main/webapp/WEB-INF/templates/upload.html b/web/upload/src/main/webapp/WEB-INF/templates/upload.html index d48c3123e..12351c91c 100644 --- a/web/upload/src/main/webapp/WEB-INF/templates/upload.html +++ b/web/upload/src/main/webapp/WEB-INF/templates/upload.html @@ -1,12 +1,18 @@ + Upload XML
+ +

Message

Select xml file to upload

-
+ + +

+

From cce580cec63b9d5839fc479a2faaa2bf41d612c6 Mon Sep 17 00:00:00 2001 From: booktest Date: Sun, 13 Jan 2019 15:06:05 +0300 Subject: [PATCH 26/83] Lesson05 - HW4 webapp display users from base --- web/webapp/pom.xml | 2 +- .../masterjava/webapp/UsersServlet.java | 27 +++++++++++++++++++ .../main/webapp/WEB-INF/templates/users.html | 27 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 web/webapp/src/main/java/ru/javaops/masterjava/webapp/UsersServlet.java create mode 100644 web/webapp/src/main/webapp/WEB-INF/templates/users.html diff --git a/web/webapp/pom.xml b/web/webapp/pom.xml index 60bb1c8c3..de3a56fc2 100644 --- a/web/webapp/pom.xml +++ b/web/webapp/pom.xml @@ -23,7 +23,7 @@ ${project.groupId} - common + persist ${project.version} diff --git a/web/webapp/src/main/java/ru/javaops/masterjava/webapp/UsersServlet.java b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/UsersServlet.java new file mode 100644 index 000000000..5b587128f --- /dev/null +++ b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/UsersServlet.java @@ -0,0 +1,27 @@ +package ru.javaops.masterjava.webapp; + +import com.google.common.collect.ImmutableMap; +import org.thymeleaf.context.WebContext; +import ru.javaops.masterjava.persist.DBIProvider; +import ru.javaops.masterjava.persist.dao.UserDao; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static ru.javaops.masterjava.common.web.ThymeleafListener.engine; + +@WebServlet("") +public class UsersServlet extends HttpServlet { + private UserDao userDao = DBIProvider.getDao(UserDao.class); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + final WebContext webContext = new WebContext(req, resp, req.getServletContext(), req.getLocale(), + ImmutableMap.of("users", userDao.getWithLimit(20))); + engine.process("users", webContext, resp.getWriter()); + } +} diff --git a/web/webapp/src/main/webapp/WEB-INF/templates/users.html b/web/webapp/src/main/webapp/WEB-INF/templates/users.html new file mode 100644 index 000000000..0ae713167 --- /dev/null +++ b/web/webapp/src/main/webapp/WEB-INF/templates/users.html @@ -0,0 +1,27 @@ + + + + Users + + + + + + + + + + + + + + + + + + + + +
#Full NameEmailFlag
+ + \ No newline at end of file From 0a731777b80784aa8ec489f56841dad23372bbf6 Mon Sep 17 00:00:00 2001 From: booktest Date: Sun, 13 Jan 2019 15:13:48 +0300 Subject: [PATCH 27/83] Lesson05 - HW4 check for duplicate users when loading in the upload module --- config_templates/sql/initDB.sql | 1 + .../ru/javaops/masterjava/persist/dao/UserDao.java | 7 +++++-- .../ru/javaops/masterjava/upload/UploadServlet.java | 6 +++--- .../ru/javaops/masterjava/upload/UserProcessor.java | 11 +++++++++-- .../src/main/webapp/WEB-INF/templates/result.html | 4 ++-- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/config_templates/sql/initDB.sql b/config_templates/sql/initDB.sql index 888ba8141..09b463d69 100644 --- a/config_templates/sql/initDB.sql +++ b/config_templates/sql/initDB.sql @@ -13,3 +13,4 @@ CREATE TABLE users ( flag user_flag NOT NULL ); +CREATE UNIQUE INDEX email_idx ON users (email); \ No newline at end of file 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 index ff760f7bc..50bf5bc0b 100644 --- a/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java @@ -36,6 +36,9 @@ public User insert(User user) { @Override public abstract void clean(); - @SqlBatch("INSERT INTO users (full_name, email, flag) VALUES (:fullName, :email, CAST(:flag AS USER_FLAG))") - public abstract void insertBatch(@BindBean List users, @BatchChunkSize int chunkSize); + // https://habrahabr.ru/post/264281/ + @SqlBatch("INSERT INTO users (full_name, email, flag) VALUES (:fullName, :email, CAST(:flag AS USER_FLAG))" + + "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); } diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java index 7591e7142..5ef87b0c5 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java @@ -44,11 +44,11 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S } else { Part filePart = req.getPart("fileToUpload"); try (InputStream is = filePart.getInputStream()) { - List users = userProcessor.process(is, chunkSize); - log.info("Successfully uploaded " + users.size() + " users"); + List alreadyPresentUsers = userProcessor.process(is, chunkSize); + log.info("Already present in DB " + alreadyPresentUsers.size() + " users"); final WebContext webContext = new WebContext(req, resp, req.getServletContext(), req.getLocale(), - ImmutableMap.of("users", users)); + ImmutableMap.of("users", alreadyPresentUsers)); engine.process("result", webContext, resp.getWriter()); return; } diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java index 31f4254a5..ec7e05827 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java @@ -1,5 +1,6 @@ package ru.javaops.masterjava.upload; +import one.util.streamex.IntStreamEx; import ru.javaops.masterjava.persist.DBIProvider; import ru.javaops.masterjava.persist.dao.UserDao; import ru.javaops.masterjava.persist.model.User; @@ -20,6 +21,9 @@ public class UserProcessor { private static final JaxbParser jaxbParser = new JaxbParser(ObjectFactory.class); private static UserDao userDao = DBIProvider.getDao(UserDao.class); + /* + * return users, already present in DB + */ public List process(final InputStream is, int chunkSize) throws XMLStreamException, JAXBException { final StaxStreamProcessor processor = new StaxStreamProcessor(is); List users = new ArrayList<>(); @@ -30,7 +34,10 @@ public List process(final InputStream is, int chunkSize) throws XMLStreamE final User user = new User(xmlUser.getValue(), xmlUser.getEmail(), UserFlag.valueOf(xmlUser.getFlag().value())); users.add(user); } - userDao.insertBatch(users, chunkSize); - return users; + int[] result = userDao.insertBatch(users, chunkSize); + return IntStreamEx.range(0, users.size()) + .filter(i -> result[i] == 0) + .mapToObj(users::get) + .toList(); } } diff --git a/web/upload/src/main/webapp/WEB-INF/templates/result.html b/web/upload/src/main/webapp/WEB-INF/templates/result.html index 01ec88cfd..3d0e3ebcb 100644 --- a/web/upload/src/main/webapp/WEB-INF/templates/result.html +++ b/web/upload/src/main/webapp/WEB-INF/templates/result.html @@ -1,11 +1,11 @@ - Uploaded users + Already present users

Upload XML

-

Uploaded users

+

Already present users

From 42e97e719e1235ba3fe62a9da4be1718eaa05f8f Mon Sep 17 00:00:00 2001 From: booktest Date: Sun, 13 Jan 2019 20:19:52 +0300 Subject: [PATCH 28/83] Lesson05 - HW4 parallel upload users --- config_templates/context.xml | 4 +- .../masterjava/persist/dao/UserDao.java | 25 +++- .../masterjava/persist/DBITestProvider.java | 2 +- .../masterjava/persist/dao/UserDaoTest.java | 7 ++ .../masterjava/upload/UploadServlet.java | 7 +- .../masterjava/upload/UserProcessor.java | 107 +++++++++++++++--- .../main/webapp/WEB-INF/templates/result.html | 25 +--- 7 files changed, 133 insertions(+), 44 deletions(-) diff --git a/config_templates/context.xml b/config_templates/context.xml index 40bb8aa0c..1ee3c15ac 100644 --- a/config_templates/context.xml +++ b/config_templates/context.xml @@ -42,8 +42,8 @@ testOnBorrow="true" logAbandoned="true" driverClassName="org.postgresql.Driver" - username="user" - password="password" + username="postgres" + password="root" url="jdbc:postgresql://localhost:5432/masterjava"/> 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 index 50bf5bc0b..c202666a9 100644 --- a/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java @@ -1,9 +1,11 @@ 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; @@ -21,6 +23,18 @@ public User insert(User user) { return user; } + @SqlQuery("SELECT nextval('user_seq')") + abstract int getNextVal(); + + @Transaction + public int getSeqAndSkip(int step) { + int id = getNextVal(); + //до postgres 10+ + //DBIProvider.getDBI().useHandle(h -> h.execute("ALTER SEQUENCE user_seq RESTART WITH " + (id + step))); + DBIProvider.getDBI().useHandle(h -> h.execute("SELECT setval('user_seq', " + (id + step - 1) + ")")); + return id; + } + @SqlUpdate("INSERT INTO users (full_name, email, flag) VALUES (:fullName, :email, CAST(:flag AS USER_FLAG)) ") @GetGeneratedKeys abstract int insertGeneratedId(@BindBean User user); @@ -37,8 +51,17 @@ public User insert(User user) { public abstract void clean(); // https://habrahabr.ru/post/264281/ - @SqlBatch("INSERT INTO users (full_name, email, flag) VALUES (:fullName, :email, CAST(:flag AS USER_FLAG))" + + @SqlBatch("INSERT INTO users (id, full_name, email, flag) VALUES (:id, :fullName, :email, CAST(:flag AS USER_FLAG))" + "ON CONFLICT DO NOTHING") // "ON CONFLICT (email) DO UPDATE SET full_name=:fullName, flag=CAST(:flag AS USER_FLAG)") public abstract int[] insertBatch(@BindBean List users, @BatchChunkSize int chunkSize); + + + public List insertAndGetConflictEmails(List users) { + int[] result = insertBatch(users, users.size()); + return IntStreamEx.range(0, users.size()) + .filter(i -> result[i] == 0) + .mapToObj(index -> users.get(index).getEmail()) + .toList(); + } } diff --git a/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java b/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java index 2b2f1d71b..c1b1b89ce 100644 --- a/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java +++ b/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java @@ -4,7 +4,7 @@ public class DBITestProvider { public static void initDBI() { - initDBI("jdbc:postgresql://localhost:5432/masterjava", "user", "password"); + initDBI("jdbc:postgresql://localhost:5432/masterjava", "postgres", "root"); } public static void initDBI(String dbUrl, String dbUser, String dbPassword) { 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 index 92102ea59..b5078d3b1 100644 --- a/persist/src/test/java/ru/javaops/masterjava/persist/dao/UserDaoTest.java +++ b/persist/src/test/java/ru/javaops/masterjava/persist/dao/UserDaoTest.java @@ -39,4 +39,11 @@ public void insertBatch() throws Exception { 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/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java index 5ef87b0c5..835c4c284 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java @@ -4,7 +4,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.thymeleaf.context.WebContext; -import ru.javaops.masterjava.persist.model.User; import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; @@ -44,11 +43,11 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S } else { Part filePart = req.getPart("fileToUpload"); try (InputStream is = filePart.getInputStream()) { - List alreadyPresentUsers = userProcessor.process(is, chunkSize); - log.info("Already present in DB " + alreadyPresentUsers.size() + " users"); + List failed = userProcessor.process(is, chunkSize); + log.info("Failed users: " + failed); final WebContext webContext = new WebContext(req, resp, req.getServletContext(), req.getLocale(), - ImmutableMap.of("users", alreadyPresentUsers)); + ImmutableMap.of("users", failed)); engine.process("result", webContext, resp.getWriter()); return; } diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java index ec7e05827..068343a38 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java @@ -1,6 +1,8 @@ package ru.javaops.masterjava.upload; -import one.util.streamex.IntStreamEx; +import one.util.streamex.StreamEx; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ru.javaops.masterjava.persist.DBIProvider; import ru.javaops.masterjava.persist.dao.UserDao; import ru.javaops.masterjava.persist.model.User; @@ -16,28 +18,99 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; public class UserProcessor { + private static final Logger log = LoggerFactory.getLogger(UserProcessor.class); + private static final int NUMBER_THREADS = 4; + private static final JaxbParser jaxbParser = new JaxbParser(ObjectFactory.class); private static UserDao userDao = DBIProvider.getDao(UserDao.class); + private ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_THREADS); + + public static class FailedChunk { + public String emailOrRange; + public String reason; + + public FailedChunk(String emailOrRange, String reason) { + this.emailOrRange = emailOrRange; + this.reason = reason; + } + + @Override + public String toString() { + return emailOrRange + " : " + reason; + } + } + /* - * return users, already present in DB + * return failed users chunks */ - public List process(final InputStream is, int chunkSize) throws XMLStreamException, JAXBException { - final StaxStreamProcessor processor = new StaxStreamProcessor(is); - List users = new ArrayList<>(); - - JaxbUnmarshaller unmarshaller = jaxbParser.createUnmarshaller(); - while (processor.doUntil(XMLEvent.START_ELEMENT, "User")) { - ru.javaops.masterjava.xml.schema.User xmlUser = unmarshaller.unmarshal(processor.getReader(), ru.javaops.masterjava.xml.schema.User.class); - final User user = new User(xmlUser.getValue(), xmlUser.getEmail(), UserFlag.valueOf(xmlUser.getFlag().value())); - users.add(user); - } - int[] result = userDao.insertBatch(users, chunkSize); - return IntStreamEx.range(0, users.size()) - .filter(i -> result[i] == 0) - .mapToObj(users::get) - .toList(); + public List process(final InputStream is, int chunkSize) throws XMLStreamException, JAXBException { + log.info("Start processing with chunkSize=" + chunkSize); + + return new Callable>() { + class ChunkFuture { + String emailRange; + Future> future; + + public ChunkFuture(List chunk, Future> future) { + this.future = future; + this.emailRange = chunk.get(0).getEmail(); + if (chunk.size() > 1) { + this.emailRange += '-' + chunk.get(chunk.size() - 1).getEmail(); + } + } + } + + @Override + public List call() throws XMLStreamException, JAXBException { + List futures = new ArrayList<>(); + + int id = userDao.getSeqAndSkip(chunkSize); + List chunk = new ArrayList<>(chunkSize); + final StaxStreamProcessor processor = new StaxStreamProcessor(is); + JaxbUnmarshaller unmarshaller = jaxbParser.createUnmarshaller(); + + while (processor.doUntil(XMLEvent.START_ELEMENT, "User")) { + ru.javaops.masterjava.xml.schema.User xmlUser = unmarshaller.unmarshal(processor.getReader(), ru.javaops.masterjava.xml.schema.User.class); + final User user = new User(id++, xmlUser.getValue(), xmlUser.getEmail(), UserFlag.valueOf(xmlUser.getFlag().value())); + chunk.add(user); + if (chunk.size() == chunkSize) { + futures.add(submit(chunk)); + chunk = new ArrayList<>(chunkSize); + id = userDao.getSeqAndSkip(chunkSize); + } + } + + if (!chunk.isEmpty()) { + futures.add(submit(chunk)); + } + + List failed = new ArrayList<>(); + futures.forEach(cf -> { + try { + failed.addAll(StreamEx.of(cf.future.get()).map(email -> new FailedChunk(email, "already present")).toList()); + log.info(cf.emailRange + " successfully executed"); + } catch (Exception e) { + log.error(cf.emailRange + " failed", e); + failed.add(new FailedChunk(cf.emailRange, e.toString())); + } + }); + return failed; + } + + private ChunkFuture submit(List chunk) { + ChunkFuture chunkFuture = new ChunkFuture(chunk, + executorService.submit(() -> userDao.insertAndGetConflictEmails(chunk)) + ); + log.info("Submit chunk: " + chunkFuture.emailRange); + return chunkFuture; + } + }.call(); } } diff --git a/web/upload/src/main/webapp/WEB-INF/templates/result.html b/web/upload/src/main/webapp/WEB-INF/templates/result.html index 3d0e3ebcb..57d01fd24 100644 --- a/web/upload/src/main/webapp/WEB-INF/templates/result.html +++ b/web/upload/src/main/webapp/WEB-INF/templates/result.html @@ -1,27 +1,14 @@ - Already present users + Failed users

Upload XML

-

Already present users

-
- - - - - - - - - - - - - - - -
Full NameEmailFlag
+

Failed users

+
    + +
  • +
\ No newline at end of file From 5cd83c5750bd7c961bfa42d03cc0f8f40d7c30f9 Mon Sep 17 00:00:00 2001 From: booktest Date: Mon, 14 Jan 2019 21:27:25 +0300 Subject: [PATCH 29/83] Lesson05 - HW4 parallel upload refactoring --- .../masterjava/upload/UploadServlet.java | 2 +- .../masterjava/upload/UserProcessor.java | 111 ++++++++---------- .../main/webapp/WEB-INF/templates/result.html | 2 +- 3 files changed, 51 insertions(+), 64 deletions(-) diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java index 835c4c284..a7e90be4d 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java @@ -43,7 +43,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S } else { Part filePart = req.getPart("fileToUpload"); try (InputStream is = filePart.getInputStream()) { - List failed = userProcessor.process(is, chunkSize); + List failed = userProcessor.process(is, chunkSize); log.info("Failed users: " + failed); final WebContext webContext = new WebContext(req, resp, req.getServletContext(), req.getLocale(), diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java index 068343a38..3a351d71f 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java @@ -1,6 +1,5 @@ package ru.javaops.masterjava.upload; -import one.util.streamex.StreamEx; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.javaops.masterjava.persist.DBIProvider; @@ -17,8 +16,10 @@ import javax.xml.stream.events.XMLEvent; import java.io.InputStream; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; -import java.util.concurrent.Callable; +import java.util.Map; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -32,85 +33,71 @@ public class UserProcessor { private ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_THREADS); - public static class FailedChunk { - public String emailOrRange; + public static class FailedEmails { + public String emailsOrRange; public String reason; - public FailedChunk(String emailOrRange, String reason) { - this.emailOrRange = emailOrRange; + public FailedEmails(String emailsOrRange, String reason) { + this.emailsOrRange = emailsOrRange; this.reason = reason; } @Override public String toString() { - return emailOrRange + " : " + reason; + return emailsOrRange + " : " + reason; } } /* * return failed users chunks */ - public List process(final InputStream is, int chunkSize) throws XMLStreamException, JAXBException { + public List process(final InputStream is, int chunkSize) throws XMLStreamException, JAXBException { log.info("Start processing with chunkSize=" + chunkSize); - return new Callable>() { - class ChunkFuture { - String emailRange; - Future> future; - - public ChunkFuture(List chunk, Future> future) { - this.future = future; - this.emailRange = chunk.get(0).getEmail(); - if (chunk.size() > 1) { - this.emailRange += '-' + chunk.get(chunk.size() - 1).getEmail(); - } - } + Map>> chunkFutures = new LinkedHashMap<>(); // ordered map (emailRange -> chunk future) + + int id = userDao.getSeqAndSkip(chunkSize); + List chunk = new ArrayList<>(chunkSize); + final StaxStreamProcessor processor = new StaxStreamProcessor(is); + JaxbUnmarshaller unmarshaller = jaxbParser.createUnmarshaller(); + + while (processor.doUntil(XMLEvent.START_ELEMENT, "User")) { + ru.javaops.masterjava.xml.schema.User xmlUser = unmarshaller.unmarshal(processor.getReader(), ru.javaops.masterjava.xml.schema.User.class); + final User user = new User(id++, xmlUser.getValue(), xmlUser.getEmail(), UserFlag.valueOf(xmlUser.getFlag().value())); + chunk.add(user); + if (chunk.size() == chunkSize) { + addChunkFutures(chunkFutures, chunk); + chunk = new ArrayList<>(chunkSize); + id = userDao.getSeqAndSkip(chunkSize); } + } - @Override - public List call() throws XMLStreamException, JAXBException { - List futures = new ArrayList<>(); - - int id = userDao.getSeqAndSkip(chunkSize); - List chunk = new ArrayList<>(chunkSize); - final StaxStreamProcessor processor = new StaxStreamProcessor(is); - JaxbUnmarshaller unmarshaller = jaxbParser.createUnmarshaller(); - - while (processor.doUntil(XMLEvent.START_ELEMENT, "User")) { - ru.javaops.masterjava.xml.schema.User xmlUser = unmarshaller.unmarshal(processor.getReader(), ru.javaops.masterjava.xml.schema.User.class); - final User user = new User(id++, xmlUser.getValue(), xmlUser.getEmail(), UserFlag.valueOf(xmlUser.getFlag().value())); - chunk.add(user); - if (chunk.size() == chunkSize) { - futures.add(submit(chunk)); - chunk = new ArrayList<>(chunkSize); - id = userDao.getSeqAndSkip(chunkSize); - } - } - - if (!chunk.isEmpty()) { - futures.add(submit(chunk)); - } + if (!chunk.isEmpty()) { + addChunkFutures(chunkFutures, chunk); + } - List failed = new ArrayList<>(); - futures.forEach(cf -> { - try { - failed.addAll(StreamEx.of(cf.future.get()).map(email -> new FailedChunk(email, "already present")).toList()); - log.info(cf.emailRange + " successfully executed"); - } catch (Exception e) { - log.error(cf.emailRange + " failed", e); - failed.add(new FailedChunk(cf.emailRange, e.toString())); - } - }); - return failed; + List failed = new ArrayList<>(); + List allAlreadyPresents = new ArrayList<>(); + chunkFutures.forEach((emailRange, future) -> { + try { + List alreadyPresentsInChunk = future.get(); + log.info("{} successfully executed with already presents: {}", emailRange, alreadyPresentsInChunk); + allAlreadyPresents.addAll(alreadyPresentsInChunk); + } catch (InterruptedException | ExecutionException e) { + log.error(emailRange + " failed", e); + failed.add(new FailedEmails(emailRange, e.toString())); } + }); + if (!allAlreadyPresents.isEmpty()) { + failed.add(new FailedEmails(allAlreadyPresents.toString(), "already presents")); + } + return failed; + } - private ChunkFuture submit(List chunk) { - ChunkFuture chunkFuture = new ChunkFuture(chunk, - executorService.submit(() -> userDao.insertAndGetConflictEmails(chunk)) - ); - log.info("Submit chunk: " + chunkFuture.emailRange); - return chunkFuture; - } - }.call(); + private void addChunkFutures(Map>> chunkFutures, List chunk) { + String emailRange = String.format("[%s-%s]", chunk.get(0).getEmail(), chunk.get(chunk.size() - 1).getEmail()); + Future> future = executorService.submit(() -> userDao.insertAndGetConflictEmails(chunk)); + chunkFutures.put(emailRange, future); + log.info("Submit chunk: " + emailRange); } } diff --git a/web/upload/src/main/webapp/WEB-INF/templates/result.html b/web/upload/src/main/webapp/WEB-INF/templates/result.html index 57d01fd24..497dffe3f 100644 --- a/web/upload/src/main/webapp/WEB-INF/templates/result.html +++ b/web/upload/src/main/webapp/WEB-INF/templates/result.html @@ -7,7 +7,7 @@

Upload XML

Failed users

    - +
From bb60c80eda909e6eb6b666b444c15de1834ad8ea Mon Sep 17 00:00:00 2001 From: booktest Date: Mon, 14 Jan 2019 22:56:18 +0300 Subject: [PATCH 30/83] Lesson05 - Add typesafe config. If you remove the include, the fields will drag out of the config in the resources, otherwise from the config by the specified path(/apps_settings/masterjava/config/persist.conf). --- common/pom.xml | 30 +++++++++++++++++++ .../ru/javaops/masterjava/config/Configs.java | 15 ++++++++++ parent/pom.xml | 25 ---------------- persist/src/main/resources/persist.conf | 6 ++++ .../masterjava/persist/DBITestProvider.java | 3 ++ 5 files changed, 54 insertions(+), 25 deletions(-) create mode 100644 common/src/main/java/ru/javaops/masterjava/config/Configs.java create mode 100644 persist/src/main/resources/persist.conf diff --git a/common/pom.xml b/common/pom.xml index 33d3da8ed..6e769be52 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -17,5 +17,35 @@ Common + + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + runtime + + + + ch.qos.logback + logback-classic + ${logback.version} + + + + com.google.guava + guava + 23.3-jre + + + one.util + streamex + 0.6.6 + + + + com.typesafe + config + 1.3.2 + \ No newline at end of file diff --git a/common/src/main/java/ru/javaops/masterjava/config/Configs.java b/common/src/main/java/ru/javaops/masterjava/config/Configs.java new file mode 100644 index 000000000..0834e95aa --- /dev/null +++ b/common/src/main/java/ru/javaops/masterjava/config/Configs.java @@ -0,0 +1,15 @@ +package ru.javaops.masterjava.config; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; + +public class Configs { + + public static Config getConfig(String resource) { + return ConfigFactory.parseResources(resource).resolve(); + } + + public static Config getConfig(String resource, String domain) { + return getConfig(resource).getConfig(domain); + } +} diff --git a/parent/pom.xml b/parent/pom.xml index 197c1eb2a..727bed894 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -76,31 +76,6 @@ - - com.google.guava - guava - 23.3-jre - - - one.util - streamex - 0.6.6 - - - - - org.slf4j - jcl-over-slf4j - ${slf4j.version} - runtime - - - - ch.qos.logback - logback-classic - ${logback.version} - - junit diff --git a/persist/src/main/resources/persist.conf b/persist/src/main/resources/persist.conf new file mode 100644 index 000000000..cc127ae70 --- /dev/null +++ b/persist/src/main/resources/persist.conf @@ -0,0 +1,6 @@ +db { + url = "jdbc:postgresql://localhost:5432/masterjava" + user = user + password = password +} +include required(file("/apps_settings/masterjava/config/persist.conf")) diff --git a/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java b/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java index c1b1b89ce..1fec7123c 100644 --- a/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java +++ b/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java @@ -1,5 +1,8 @@ package ru.javaops.masterjava.persist; +import com.typesafe.config.Config; +import ru.javaops.masterjava.config.Configs; + import java.sql.DriverManager; public class DBITestProvider { From 6b289b11f93a233989b98d3eb8053797e18dbf1d Mon Sep 17 00:00:00 2001 From: booktest Date: Mon, 14 Jan 2019 23:02:04 +0300 Subject: [PATCH 31/83] Lesson05 - Add to previous commit --- .../java/ru/javaops/masterjava/persist/DBITestProvider.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java b/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java index 1fec7123c..5d2016876 100644 --- a/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java +++ b/persist/src/test/java/ru/javaops/masterjava/persist/DBITestProvider.java @@ -7,7 +7,8 @@ public class DBITestProvider { public static void initDBI() { - initDBI("jdbc:postgresql://localhost:5432/masterjava", "postgres", "root"); + 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) { From 41db0d5f31bb9f721363c740d345d742e138090a Mon Sep 17 00:00:00 2001 From: booktest Date: Sat, 2 Feb 2019 16:09:13 +0300 Subject: [PATCH 32/83] Lesson05 - Added lombok --- parent/pom.xml | 7 ++ .../masterjava/persist/DBIProvider.java | 6 +- .../masterjava/persist/model/BaseEntity.java | 21 ++--- .../masterjava/persist/model/User.java | 82 +++---------------- pom.xml | 2 +- .../masterjava/upload/UploadServlet.java | 5 +- .../masterjava/upload/UserProcessor.java | 18 ++-- 7 files changed, 36 insertions(+), 105 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index 727bed894..1f0770b8a 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -76,6 +76,13 @@ + + org.projectlombok + lombok + 1.16.18 + provided + + junit diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/DBIProvider.java b/persist/src/main/java/ru/javaops/masterjava/persist/DBIProvider.java index 7cfaab77f..f3d115dcf 100644 --- a/persist/src/main/java/ru/javaops/masterjava/persist/DBIProvider.java +++ b/persist/src/main/java/ru/javaops/masterjava/persist/DBIProvider.java @@ -1,18 +1,16 @@ 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 org.slf4j.Logger; import ru.javaops.masterjava.persist.dao.AbstractDao; import javax.naming.InitialContext; import javax.sql.DataSource; -import static org.slf4j.LoggerFactory.getLogger; - +@Slf4j public class DBIProvider { - private static final Logger log = getLogger(DBIProvider.class); private volatile static ConnectionFactory connectionFactory = null; 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 index 415d3d037..ee44b5f71 100644 --- a/persist/src/main/java/ru/javaops/masterjava/persist/model/BaseEntity.java +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/BaseEntity.java @@ -1,23 +1,16 @@ package ru.javaops.masterjava.persist.model; -abstract public class BaseEntity { - protected BaseEntity() { - } +import lombok.*; - protected BaseEntity(Integer id) { - this.id = id; - } +@NoArgsConstructor +@AllArgsConstructor +@ToString +abstract public class BaseEntity { + @Getter + @Setter protected Integer id; - public Integer getId() { - return id; - } - - protected void setId(Integer id) { - this.id = id; - } - public boolean isNew() { return id == null; } 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 index f4445cb50..927a299f3 100644 --- a/persist/src/main/java/ru/javaops/masterjava/persist/model/User.java +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/User.java @@ -1,82 +1,20 @@ package ru.javaops.masterjava.persist.model; import com.bertoncelj.jdbi.entitymapper.Column; +import lombok.*; -import java.util.Objects; - +@Data +@RequiredArgsConstructor +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor public class User extends BaseEntity { @Column("full_name") - private String fullName; - - private String email; - - private UserFlag flag; - - public User() { - } - - public User(String fullName, String email, UserFlag flag) { - this(null, fullName, email, flag); - } + private @NonNull String fullName; + private @NonNull String email; + private @NonNull UserFlag flag; public User(Integer id, String fullName, String email, UserFlag flag) { - super(id); - this.fullName = fullName; - this.email = email; - this.flag = flag; - } - - public String getFullName() { - return fullName; - } - - public String getEmail() { - return email; - } - - public UserFlag getFlag() { - return flag; - } - - public void setId(Integer id) { - this.id = id; - } - - public void setFullName(String fullName) { - this.fullName = fullName; - } - - public void setEmail(String email) { - this.email = email; - } - - public void setFlag(UserFlag flag) { - this.flag = flag; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - User user = (User) o; - return Objects.equals(id, user.id) && - Objects.equals(fullName, user.fullName) && - Objects.equals(email, user.email) && - flag == user.flag; - } - - @Override - public int hashCode() { - return Objects.hash(id, fullName, email, flag); - } - - @Override - public String toString() { - return "User (" + - "id=" + id + - ", fullName='" + fullName + '\'' + - ", email='" + email + '\'' + - ", flag=" + flag + - ')'; + this(fullName, email, flag); + this.id=id; } } \ No newline at end of file diff --git a/pom.xml b/pom.xml index f436d4226..92f1c0e64 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ 1.0-SNAPSHOT Masterjava Root - https://github.com/immus/masterjava + https://github.com/immmus/masterjava parent diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java index a7e90be4d..732c25cb1 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java @@ -1,8 +1,7 @@ package ru.javaops.masterjava.upload; import com.google.common.collect.ImmutableMap; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.thymeleaf.context.WebContext; import javax.servlet.ServletException; @@ -20,8 +19,8 @@ @WebServlet(urlPatterns = "/", loadOnStartup = 1) @MultipartConfig(fileSizeThreshold = 1024 * 1024 * 10) //10 MB in memory limit +@Slf4j public class UploadServlet extends HttpServlet { - private static final Logger log = LoggerFactory.getLogger(UploadServlet.class); private static final int CHUNK_SIZE = 2000; private final UserProcessor userProcessor = new UserProcessor(); diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java index 3a351d71f..d7b52ef30 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java @@ -1,14 +1,14 @@ package ru.javaops.masterjava.upload; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import lombok.val; import ru.javaops.masterjava.persist.DBIProvider; import ru.javaops.masterjava.persist.dao.UserDao; import ru.javaops.masterjava.persist.model.User; import ru.javaops.masterjava.persist.model.UserFlag; import ru.javaops.masterjava.xml.schema.ObjectFactory; import ru.javaops.masterjava.xml.util.JaxbParser; -import ru.javaops.masterjava.xml.util.JaxbUnmarshaller; import ru.javaops.masterjava.xml.util.StaxStreamProcessor; import javax.xml.bind.JAXBException; @@ -24,8 +24,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; +@Slf4j public class UserProcessor { - private static final Logger log = LoggerFactory.getLogger(UserProcessor.class); private static final int NUMBER_THREADS = 4; private static final JaxbParser jaxbParser = new JaxbParser(ObjectFactory.class); @@ -33,15 +33,11 @@ public class UserProcessor { private ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_THREADS); + @AllArgsConstructor public static class FailedEmails { public String emailsOrRange; public String reason; - public FailedEmails(String emailsOrRange, String reason) { - this.emailsOrRange = emailsOrRange; - this.reason = reason; - } - @Override public String toString() { return emailsOrRange + " : " + reason; @@ -58,8 +54,8 @@ public List process(final InputStream is, int chunkSize) throws XM int id = userDao.getSeqAndSkip(chunkSize); List chunk = new ArrayList<>(chunkSize); - final StaxStreamProcessor processor = new StaxStreamProcessor(is); - JaxbUnmarshaller unmarshaller = jaxbParser.createUnmarshaller(); + val processor = new StaxStreamProcessor(is); + val unmarshaller = jaxbParser.createUnmarshaller(); while (processor.doUntil(XMLEvent.START_ELEMENT, "User")) { ru.javaops.masterjava.xml.schema.User xmlUser = unmarshaller.unmarshal(processor.getReader(), ru.javaops.masterjava.xml.schema.User.class); From 4e9f4ea05427ce90174ed90c31957694875fc3dc Mon Sep 17 00:00:00 2001 From: booktest Date: Sat, 23 Feb 2019 10:28:28 +0300 Subject: [PATCH 33/83] Lesson06 - HW5 model sql --- config_templates/sql/initDB.sql => initDB.sql | 0 .../masterjava/persist/model/BaseEntity.java | 21 +++--------- .../masterjava/persist/model/City.java | 16 +++++++++ .../masterjava/persist/model/Group.java | 17 ++++++++++ .../masterjava/persist/model/Project.java | 14 ++++++++ .../masterjava/persist/model/RefEntity.java | 12 +++++++ .../masterjava/persist/model/User.java | 12 ++++--- .../masterjava/persist/model/UserGroup.java | 15 ++++++++ .../persist/model/type/GroupType.java | 7 ++++ .../model/{ => type/type}/UserFlag.java | 2 +- sql/databaseChangeLog.sql | 34 +++++++++++++++++++ sql/lb_apply.bat | 8 +++++ 12 files changed, 136 insertions(+), 22 deletions(-) rename config_templates/sql/initDB.sql => initDB.sql (100%) create mode 100644 persist/src/main/java/ru/javaops/masterjava/persist/model/City.java create mode 100644 persist/src/main/java/ru/javaops/masterjava/persist/model/Group.java create mode 100644 persist/src/main/java/ru/javaops/masterjava/persist/model/Project.java create mode 100644 persist/src/main/java/ru/javaops/masterjava/persist/model/RefEntity.java create mode 100644 persist/src/main/java/ru/javaops/masterjava/persist/model/UserGroup.java create mode 100644 persist/src/main/java/ru/javaops/masterjava/persist/model/type/GroupType.java rename persist/src/main/java/ru/javaops/masterjava/persist/model/{ => type/type}/UserFlag.java (66%) create mode 100644 sql/databaseChangeLog.sql create mode 100644 sql/lb_apply.bat diff --git a/config_templates/sql/initDB.sql b/initDB.sql similarity index 100% rename from config_templates/sql/initDB.sql rename to initDB.sql 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 index ee44b5f71..83ed0a9f2 100644 --- a/persist/src/main/java/ru/javaops/masterjava/persist/model/BaseEntity.java +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/BaseEntity.java @@ -1,30 +1,17 @@ package ru.javaops.masterjava.persist.model; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +@Data @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..974f9868a --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/City.java @@ -0,0 +1,16 @@ +package ru.javaops.masterjava.persist.model; + +import lombok.*; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class City extends RefEntity { + public City(String ref, String name) { + super(ref); + this.name = name; + } + + @NonNull private String name; +} \ 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..71a648731 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/Group.java @@ -0,0 +1,17 @@ +package ru.javaops.masterjava.persist.model; + +import com.bertoncelj.jdbi.entitymapper.Column; +import lombok.*; +import ru.javaops.masterjava.persist.model.type.GroupType; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class Group extends BaseEntity { + + @NonNull private String name; + @NonNull private GroupType type; + @NonNull @Column("project_id") private int projectId; +} 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..c040121e4 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/Project.java @@ -0,0 +1,14 @@ +package ru.javaops.masterjava.persist.model; + +import lombok.*; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class Project extends BaseEntity { + + @NonNull private String name; + @NonNull private String description; +} diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/model/RefEntity.java b/persist/src/main/java/ru/javaops/masterjava/persist/model/RefEntity.java new file mode 100644 index 000000000..249189604 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/RefEntity.java @@ -0,0 +1,12 @@ +package ru.javaops.masterjava.persist.model; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@ToString +@EqualsAndHashCode +abstract public class RefEntity { + @Getter + @NonNull private String ref; +} 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 index 927a299f3..c34f3020b 100644 --- a/persist/src/main/java/ru/javaops/masterjava/persist/model/User.java +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/User.java @@ -2,19 +2,23 @@ import com.bertoncelj.jdbi.entitymapper.Column; import lombok.*; +import ru.javaops.masterjava.persist.model.type.UserFlag; @Data -@RequiredArgsConstructor -@EqualsAndHashCode(callSuper = true) @NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +@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_ref") + private @NonNull String cityRef; - public User(Integer id, String fullName, String email, UserFlag flag) { - this(fullName, email, flag); + public User(Integer id, String fullName, String email, UserFlag flag, String cityRef) { + this(fullName, email, flag, cityRef); this.id=id; } } \ No newline at end of file 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..f647dfcff --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/UserGroup.java @@ -0,0 +1,15 @@ +package ru.javaops.masterjava.persist.model; + +import com.bertoncelj.jdbi.entitymapper.Column; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +@Data +@NoArgsConstructor +@AllArgsConstructor +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/java/ru/javaops/masterjava/persist/model/type/GroupType.java b/persist/src/main/java/ru/javaops/masterjava/persist/model/type/GroupType.java new file mode 100644 index 000000000..8750d8613 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/type/GroupType.java @@ -0,0 +1,7 @@ +package ru.javaops.masterjava.persist.model.type; + +public enum GroupType { + REGISTERING, + CURRENT, + FINISHED; +} diff --git a/persist/src/main/java/ru/javaops/masterjava/persist/model/UserFlag.java b/persist/src/main/java/ru/javaops/masterjava/persist/model/type/type/UserFlag.java similarity index 66% rename from persist/src/main/java/ru/javaops/masterjava/persist/model/UserFlag.java rename to persist/src/main/java/ru/javaops/masterjava/persist/model/type/type/UserFlag.java index bc2f69183..0aff2a46d 100644 --- a/persist/src/main/java/ru/javaops/masterjava/persist/model/UserFlag.java +++ b/persist/src/main/java/ru/javaops/masterjava/persist/model/type/type/UserFlag.java @@ -1,4 +1,4 @@ -package ru.javaops.masterjava.persist.model; +package ru.javaops.masterjava.persist.model.type; /** * gkislin diff --git a/sql/databaseChangeLog.sql b/sql/databaseChangeLog.sql new file mode 100644 index 000000000..60ec863d9 --- /dev/null +++ b/sql/databaseChangeLog.sql @@ -0,0 +1,34 @@ +--liquibase formatted sql + +--changeset gkislin:1 +CREATE SEQUENCE common_seq START 100000; + +CREATE TABLE city ( + ref TEXT PRIMARY KEY, + name TEXT NOT NULL +); + +ALTER TABLE users + ADD COLUMN city_ref TEXT REFERENCES city (ref) ON UPDATE CASCADE; + +--changeset gkislin:2 +CREATE TABLE project ( + id INTEGER PRIMARY KEY DEFAULT nextval('common_seq'), + name TEXT UNIQUE NOT NULL, + description TEXT +); + +CREATE TYPE GROUP_TYPE AS ENUM ('REGISTERING', 'CURRENT', 'FINISHED'); + +CREATE TABLE groups ( + id INTEGER PRIMARY KEY DEFAULT nextval('common_seq'), + name TEXT UNIQUE NOT NULL, + 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) +); 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 From 1d0bd869f3f9492f5ef6bf304eb458ecf63c8750 Mon Sep 17 00:00:00 2001 From: booktest Date: Sat, 23 Feb 2019 10:29:12 +0300 Subject: [PATCH 34/83] Lesson06 - HW5 dao test --- .../masterjava/persist/dao/CityDao.java | 37 +++++++++++++ .../masterjava/persist/dao/GroupDao.java | 39 ++++++++++++++ .../masterjava/persist/dao/ProjectDao.java | 40 ++++++++++++++ .../masterjava/persist/dao/UserGroupDao.java | 32 +++++++++++ .../masterjava/persist/CityTestData.java | 36 +++++++++++++ .../masterjava/persist/GroupTestData.java | 53 +++++++++++++++++++ .../masterjava/persist/ProjectTestData.java | 34 ++++++++++++ .../masterjava/persist/UserGroupTestData.java | 41 ++++++++++++++ .../masterjava/persist/UserTestData.java | 19 ++++--- .../persist/dao/AbstractDaoTest.java | 19 +++++++ .../masterjava/persist/dao/CityDaoTest.java | 36 +++++++++++++ .../masterjava/persist/dao/GroupDaoTest.java | 36 +++++++++++++ .../persist/dao/ProjectDaoTest.java | 36 +++++++++++++ .../persist/dao/UserGroupDaoTest.java | 39 ++++++++++++++ 14 files changed, 490 insertions(+), 7 deletions(-) create mode 100644 persist/src/main/java/ru/javaops/masterjava/persist/dao/CityDao.java create mode 100644 persist/src/main/java/ru/javaops/masterjava/persist/dao/GroupDao.java create mode 100644 persist/src/main/java/ru/javaops/masterjava/persist/dao/ProjectDao.java create mode 100644 persist/src/main/java/ru/javaops/masterjava/persist/dao/UserGroupDao.java create mode 100644 persist/src/test/java/ru/javaops/masterjava/persist/CityTestData.java create mode 100644 persist/src/test/java/ru/javaops/masterjava/persist/GroupTestData.java create mode 100644 persist/src/test/java/ru/javaops/masterjava/persist/ProjectTestData.java create mode 100644 persist/src/test/java/ru/javaops/masterjava/persist/UserGroupTestData.java create mode 100644 persist/src/test/java/ru/javaops/masterjava/persist/dao/CityDaoTest.java create mode 100644 persist/src/test/java/ru/javaops/masterjava/persist/dao/GroupDaoTest.java create mode 100644 persist/src/test/java/ru/javaops/masterjava/persist/dao/ProjectDaoTest.java create mode 100644 persist/src/test/java/ru/javaops/masterjava/persist/dao/UserGroupDaoTest.java 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..5ebdb1afa --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/CityDao.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.SqlBatch; +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.City; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static java.util.function.Function.identity; + +@RegisterMapperFactory(EntityMapperFactory.class) +public abstract class CityDao implements AbstractDao { + + @SqlUpdate("TRUNCATE city CASCADE ") + @Override + public abstract void clean(); + + @SqlQuery("SELECT * FROM city") + public abstract List getAll(); + + public Map getAsMap() { + return StreamEx.of(getAll()).toMap(City::getRef, identity()); + } + + @SqlUpdate("INSERT INTO city (ref, name) VALUES (:ref, :name)") + public abstract void insert(@BindBean City city); + + @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..d2f0908b6 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/GroupDao.java @@ -0,0 +1,39 @@ +package ru.javaops.masterjava.persist.dao; + +import com.bertoncelj.jdbi.entitymapper.EntityMapperFactory; +import one.util.streamex.StreamEx; +import org.skife.jdbi.v2.sqlobject.BindBean; +import org.skife.jdbi.v2.sqlobject.GetGeneratedKeys; +import org.skife.jdbi.v2.sqlobject.SqlQuery; +import org.skife.jdbi.v2.sqlobject.SqlUpdate; +import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapperFactory; +import ru.javaops.masterjava.persist.model.Group; + +import java.util.List; +import java.util.Map; + +@RegisterMapperFactory(EntityMapperFactory.class) +public abstract class GroupDao implements AbstractDao { + + @SqlUpdate("TRUNCATE groups CASCADE ") + @Override + public abstract void clean(); + + @SqlQuery("SELECT * FROM groups ORDER BY name") + public abstract List getAll(); + + public Map getAsMap() { + return StreamEx.of(getAll()).toMap(Group::getName, g -> g); + } + + @SqlUpdate("INSERT INTO groups (name, type, project_id) VALUES (:name, CAST(:type AS group_type), :projectId)") + @GetGeneratedKeys + public abstract int insertGeneratedId(@BindBean Group groups); + + @Override + public T insert(T group) { + int id = insertGeneratedId(group); + group.setId(id); + return group; + } +} 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..8e63c985a --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/ProjectDao.java @@ -0,0 +1,40 @@ +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.BaseEntity; +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); + + @Override + public T insert(T project) { + int id = insertGeneratedId(project); + project.setId(id); + return project; + } +} 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..2306a85e4 --- /dev/null +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserGroupDao.java @@ -0,0 +1,32 @@ +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; + +@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/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..1e3fef700 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/CityTestData.java @@ -0,0 +1,36 @@ +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; + +public class CityTestData { + public static City KIEV; + public static City MINSK; + public static City MOSCOW; + public static City SPB; + + public static Map CITIES; + + public static void init() { + KIEV = new City("kiv", "Киев"); + MINSK = new City("mnsk", "Минск"); + MOSCOW = new City("mow", "Москва"); + SPB = new City("spb", "Санкт-Петербург"); + 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/GroupTestData.java b/persist/src/test/java/ru/javaops/masterjava/persist/GroupTestData.java new file mode 100644 index 000000000..2bbd47d7d --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/GroupTestData.java @@ -0,0 +1,53 @@ +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.type.GroupType.CURRENT; +import static ru.javaops.masterjava.persist.model.type.GroupType.FINISHED; + +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.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..cd45980d0 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/ProjectTestData.java @@ -0,0 +1,34 @@ +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; + +public class ProjectTestData { + public static Project TOPJAVA; + public static Project MASTERJAVA; + public static Map PROJECTS; + + public static int TOPJAVA_ID; + public static int MASTERJAVA_ID; + + public static void init() { + TOPJAVA = new Project("topjava", "Topjava"); + MASTERJAVA = new Project("masterjava", "Masterjava"); + PROJECTS = ImmutableMap.of( + TOPJAVA.getName(), TOPJAVA, + MASTERJAVA.getName(), MASTERJAVA); + } + + 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..06c2d4be9 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/UserGroupTestData.java @@ -0,0 +1,41 @@ +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.GroupTestData.*; +import static ru.javaops.masterjava.persist.dao.UserGroupDao.toUserGroups; + +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 index 7c13c0499..ff1779c5e 100644 --- a/persist/src/test/java/ru/javaops/masterjava/persist/UserTestData.java +++ b/persist/src/test/java/ru/javaops/masterjava/persist/UserTestData.java @@ -3,10 +3,12 @@ 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 ru.javaops.masterjava.persist.model.type.UserFlag; import java.util.List; +import static ru.javaops.masterjava.persist.CityTestData.*; + public class UserTestData { public static User ADMIN; public static User DELETED; @@ -17,12 +19,15 @@ public class UserTestData { public static List FIST5_USERS; public static void init() { - ADMIN = new User("Admin", "admin@javaops.ru", UserFlag.superuser); - DELETED = new User("Deleted", "deleted@yandex.ru", UserFlag.deleted); - FULL_NAME = new User("Full Name", "gmail@gmail.com", UserFlag.active); - USER1 = new User("User1", "user1@gmail.com", UserFlag.active); - USER2 = new User("User2", "user2@yandex.ru", UserFlag.active); - USER3 = new User("User3", "user3@yandex.ru", UserFlag.active); + CityTestData.init(); + CityTestData.setUp(); + + ADMIN = new User("Admin", "admin@javaops.ru", UserFlag.superuser, SPB.getRef()); + DELETED = new User("Deleted", "deleted@yandex.ru", UserFlag.deleted, SPB.getRef()); + FULL_NAME = new User("Full Name", "gmail@gmail.com", UserFlag.active, KIEV.getRef()); + USER1 = new User("User1", "user1@gmail.com", UserFlag.active, MOSCOW.getRef()); + USER2 = new User("User2", "user2@yandex.ru", UserFlag.active, KIEV.getRef()); + USER3 = new User("User3", "user3@yandex.ru", UserFlag.active, MINSK.getRef()); FIST5_USERS = ImmutableList.of(ADMIN, DELETED, FULL_NAME, USER1, USER2); } 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 index a65302fdd..3e8a891a5 100644 --- a/persist/src/test/java/ru/javaops/masterjava/persist/dao/AbstractDaoTest.java +++ b/persist/src/test/java/ru/javaops/masterjava/persist/dao/AbstractDaoTest.java @@ -1,13 +1,32 @@ 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) { 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..d4dbf19ab --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/dao/CityDaoTest.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.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); + } + + @BeforeClass + public static void init() throws Exception { + CityTestData.init(); + } + + @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..b364e3f02 --- /dev/null +++ b/persist/src/test/java/ru/javaops/masterjava/persist/dao/ProjectDaoTest.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.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); + } + + @BeforeClass + public static void init() throws Exception { + ProjectTestData.init(); + } + + @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/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 From 04fbf5e3dcf9058b59fda074f0425bd6c1c5e604 Mon Sep 17 00:00:00 2001 From: booktest Date: Sat, 23 Feb 2019 10:30:08 +0300 Subject: [PATCH 35/83] Lesson06 - my fix in dao --- .../javaops/masterjava/persist/dao/AbstractDao.java | 5 ++++- .../ru/javaops/masterjava/persist/dao/UserDao.java | 13 +++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) 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 index 8b3ee1099..f73f4bb38 100644 --- a/persist/src/main/java/ru/javaops/masterjava/persist/dao/AbstractDao.java +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/AbstractDao.java @@ -1,5 +1,8 @@ package ru.javaops.masterjava.persist.dao; -public interface AbstractDao { +import ru.javaops.masterjava.persist.model.BaseEntity; + +public interface AbstractDao { void clean(); + T insert(T bean); } 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 index c202666a9..5174fed43 100644 --- a/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java @@ -11,9 +11,10 @@ import java.util.List; @RegisterMapperFactory(EntityMapperFactory.class) -public abstract class UserDao implements AbstractDao { +public abstract class UserDao implements AbstractDao { - public User insert(User user) { + @Override + public T insert(T user) { if (user.isNew()) { int id = insertGeneratedId(user); user.setId(id); @@ -35,23 +36,23 @@ public int getSeqAndSkip(int step) { return id; } - @SqlUpdate("INSERT INTO users (full_name, email, flag) VALUES (:fullName, :email, CAST(:flag AS USER_FLAG)) ") + @SqlUpdate("INSERT INTO users (full_name, email, flag, city_ref) VALUES (:fullName, :email, CAST(:flag AS USER_FLAG), :cityRef) ") @GetGeneratedKeys abstract int insertGeneratedId(@BindBean User user); - @SqlUpdate("INSERT INTO users (id, full_name, email, flag) VALUES (:id, :fullName, :email, CAST(:flag AS USER_FLAG)) ") + @SqlUpdate("INSERT INTO users (id, full_name, email, flag, city_ref) VALUES (:id, :fullName, :email, CAST(:flag AS USER_FLAG), :cityRef) ") 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") + @SqlUpdate("TRUNCATE users CASCADE") @Override public abstract void clean(); // https://habrahabr.ru/post/264281/ - @SqlBatch("INSERT INTO users (id, full_name, email, flag) VALUES (:id, :fullName, :email, CAST(:flag AS USER_FLAG))" + + @SqlBatch("INSERT INTO users (id, full_name, email, flag, city_ref) VALUES (:id, :fullName, :email, CAST(:flag AS USER_FLAG), :cityRef)" + "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); From 5f3885b59dc7e4a8d6b5ea47b28d43ec24fe1c68 Mon Sep 17 00:00:00 2001 From: booktest Date: Sat, 23 Feb 2019 10:38:08 +0300 Subject: [PATCH 36/83] Lesson06 - HW5 add PayloadProcessor --- .../masterjava/upload/PayloadProcessor.java | 33 ++++++++++++++++ .../masterjava/upload/UploadServlet.java | 4 +- .../masterjava/upload/UserProcessor.java | 39 ++++++++----------- 3 files changed, 51 insertions(+), 25 deletions(-) create mode 100644 web/upload/src/main/java/ru/javaops/masterjava/upload/PayloadProcessor.java diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/PayloadProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/PayloadProcessor.java new file mode 100644 index 000000000..38af0f35a --- /dev/null +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/PayloadProcessor.java @@ -0,0 +1,33 @@ +package ru.javaops.masterjava.upload; + +import lombok.AllArgsConstructor; +import lombok.val; +import ru.javaops.masterjava.xml.util.StaxStreamProcessor; + +import javax.xml.bind.JAXBException; +import javax.xml.stream.XMLStreamException; +import java.io.InputStream; +import java.util.List; + +public class PayloadProcessor { + private final CityProcessor cityProcessor = new CityProcessor(); + private final UserProcessor userProcessor = new UserProcessor(); + + @AllArgsConstructor + public static class FailedEmails { + public String emailsOrRange; + public String reason; + + @Override + public String toString() { + return emailsOrRange + " : " + reason; + } + } + + + public List process(InputStream is, int chunkSize) throws XMLStreamException, JAXBException { + final StaxStreamProcessor processor = new StaxStreamProcessor(is); + val cities = cityProcessor.process(processor); + return userProcessor.process(processor, cities, chunkSize); + } +} \ No newline at end of file diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java index 732c25cb1..fafe55db0 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UploadServlet.java @@ -23,7 +23,7 @@ public class UploadServlet extends HttpServlet { private static final int CHUNK_SIZE = 2000; - private final UserProcessor userProcessor = new UserProcessor(); + private final PayloadProcessor payloadProcessor = new PayloadProcessor(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { @@ -42,7 +42,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S } else { Part filePart = req.getPart("fileToUpload"); try (InputStream is = filePart.getInputStream()) { - List failed = userProcessor.process(is, chunkSize); + List failed = payloadProcessor.process(is, chunkSize); log.info("Failed users: " + failed); final WebContext webContext = new WebContext(req, resp, req.getServletContext(), req.getLocale(), diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java index d7b52ef30..170521ac9 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java @@ -1,12 +1,13 @@ package ru.javaops.masterjava.upload; -import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import lombok.val; import ru.javaops.masterjava.persist.DBIProvider; import ru.javaops.masterjava.persist.dao.UserDao; +import ru.javaops.masterjava.persist.model.City; import ru.javaops.masterjava.persist.model.User; -import ru.javaops.masterjava.persist.model.UserFlag; +import ru.javaops.masterjava.persist.model.type.UserFlag; +import ru.javaops.masterjava.upload.PayloadProcessor.FailedEmails; import ru.javaops.masterjava.xml.schema.ObjectFactory; import ru.javaops.masterjava.xml.util.JaxbParser; import ru.javaops.masterjava.xml.util.StaxStreamProcessor; @@ -14,7 +15,6 @@ import javax.xml.bind.JAXBException; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.XMLEvent; -import java.io.InputStream; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -33,38 +33,32 @@ public class UserProcessor { private ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_THREADS); - @AllArgsConstructor - public static class FailedEmails { - public String emailsOrRange; - public String reason; - - @Override - public String toString() { - return emailsOrRange + " : " + reason; - } - } - /* * return failed users chunks */ - public List process(final InputStream is, int chunkSize) throws XMLStreamException, JAXBException { + public List process(final StaxStreamProcessor processor, Map cities, int chunkSize) throws XMLStreamException, JAXBException { log.info("Start processing with chunkSize=" + chunkSize); Map>> chunkFutures = new LinkedHashMap<>(); // ordered map (emailRange -> chunk future) int id = userDao.getSeqAndSkip(chunkSize); List chunk = new ArrayList<>(chunkSize); - val processor = new StaxStreamProcessor(is); val unmarshaller = jaxbParser.createUnmarshaller(); + List failed = new ArrayList<>(); while (processor.doUntil(XMLEvent.START_ELEMENT, "User")) { + String cityRef = processor.getAttribute("city"); // unmarshal doesn't get city ref ru.javaops.masterjava.xml.schema.User xmlUser = unmarshaller.unmarshal(processor.getReader(), ru.javaops.masterjava.xml.schema.User.class); - final User user = new User(id++, xmlUser.getValue(), xmlUser.getEmail(), UserFlag.valueOf(xmlUser.getFlag().value())); - chunk.add(user); - if (chunk.size() == chunkSize) { - addChunkFutures(chunkFutures, chunk); - chunk = new ArrayList<>(chunkSize); - id = userDao.getSeqAndSkip(chunkSize); + if (cities.get(cityRef) == null) { + failed.add(new FailedEmails(xmlUser.getEmail(), "City '" + cityRef + "' is not present in DB")); + } else { + final User user = new User(id++, xmlUser.getValue(), xmlUser.getEmail(), UserFlag.valueOf(xmlUser.getFlag().value()), cityRef); + chunk.add(user); + if (chunk.size() == chunkSize) { + addChunkFutures(chunkFutures, chunk); + chunk = new ArrayList<>(chunkSize); + id = userDao.getSeqAndSkip(chunkSize); + } } } @@ -72,7 +66,6 @@ public List process(final InputStream is, int chunkSize) throws XM addChunkFutures(chunkFutures, chunk); } - List failed = new ArrayList<>(); List allAlreadyPresents = new ArrayList<>(); chunkFutures.forEach((emailRange, future) -> { try { From 8371fb5b27ec07a57a3d5498a46b15cd3c9d7f1e Mon Sep 17 00:00:00 2001 From: booktest Date: Sat, 23 Feb 2019 10:39:40 +0300 Subject: [PATCH 37/83] Lesson06 - HW5 add CityProcessor --- .../masterjava/upload/CityProcessor.java | 31 +++++++++++++++++++ web/upload/src/test/resources/payload_bad.xml | 31 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 web/upload/src/main/java/ru/javaops/masterjava/upload/CityProcessor.java create mode 100644 web/upload/src/test/resources/payload_bad.xml diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/CityProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/CityProcessor.java new file mode 100644 index 000000000..a19b931ce --- /dev/null +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/CityProcessor.java @@ -0,0 +1,31 @@ +package ru.javaops.masterjava.upload; + +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import ru.javaops.masterjava.persist.DBIProvider; +import ru.javaops.masterjava.persist.dao.CityDao; +import ru.javaops.masterjava.persist.model.City; +import ru.javaops.masterjava.xml.util.StaxStreamProcessor; + +import javax.xml.stream.XMLStreamException; +import java.util.*; + +@Slf4j +public class CityProcessor { + private final CityDao cityDao = DBIProvider.getDao(CityDao.class); + + public Map process(StaxStreamProcessor processor) throws XMLStreamException { + val map = cityDao.getAsMap(); + val newCities = new ArrayList(); + + while (processor.startElement("City", "Cities")) { + val ref = processor.getAttribute("id"); + if (!map.containsKey(ref)) { + newCities.add(new City(ref, processor.getText())); + } + } + log.info("Insert batch " + newCities); + cityDao.insertBatch(newCities); + return cityDao.getAsMap(); + } +} diff --git a/web/upload/src/test/resources/payload_bad.xml b/web/upload/src/test/resources/payload_bad.xml new file mode 100644 index 000000000..72a2e0422 --- /dev/null +++ b/web/upload/src/test/resources/payload_bad.xml @@ -0,0 +1,31 @@ + + + + + Topjava + + + + + + Masterjava + + + + + Санкт-Петербург + Москва + Киев + Минск + + + Full Name + Admin + Deleted + User1 + User2 + User3 + + \ No newline at end of file From 6d20b259963ee66f2f276c8a141d45579291fe71 Mon Sep 17 00:00:00 2001 From: booktest Date: Sat, 23 Feb 2019 12:12:57 +0300 Subject: [PATCH 38/83] Lesson06 - HW5 added web services --- .../masterjava/service/mail/Addressee.java | 13 ++++++++++++ .../masterjava/service/mail/MailService.java | 17 ++++++++++++++++ .../masterjava/service/mail/MailSender.java | 12 +++++++++++ .../service/mail/MailServiceImpl.java | 11 ++++++++++ .../service/mail/MailServiceClient.java | 20 +++++++++++++++++++ .../service/mail/MailServicePublisher.java | 10 ++++++++++ 6 files changed, 83 insertions(+) create mode 100644 services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Addressee.java create mode 100644 services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailService.java create mode 100644 services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailSender.java create mode 100644 services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java create mode 100644 services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java create mode 100644 services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServicePublisher.java 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..04d3ba259 --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Addressee.java @@ -0,0 +1,13 @@ +package ru.javaops.masterjava.service.mail; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Addressee { + private String email; + private String name; +} diff --git a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailService.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailService.java new file mode 100644 index 000000000..a35a779a9 --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailService.java @@ -0,0 +1,17 @@ +package ru.javaops.masterjava.service.mail; + +import javax.jws.WebMethod; +import javax.jws.WebParam; +import javax.jws.WebService; +import java.util.List; + +@WebService +public interface MailService { + + @WebMethod + void sendMail( + @WebParam(name = "to") List to, + @WebParam(name = "cc") List cc, + @WebParam(name = "subject") String subject, + @WebParam(name = "body") String body); +} \ No newline at end of file diff --git a/services/mail-service/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..ecfff61c6 --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailSender.java @@ -0,0 +1,12 @@ +package ru.javaops.masterjava.service.mail; + +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +@Slf4j +public class MailSender { + static void sendMail(List to, List cc, String subject, String body) { + log.info("Send mail to \'" + to + "\' cc \'" + cc + "\' subject \'" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); + } +} diff --git a/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java new file mode 100644 index 000000000..fd649330a --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java @@ -0,0 +1,11 @@ +package ru.javaops.masterjava.service.mail; + +import javax.jws.WebService; +import java.util.List; + +@WebService(endpointInterface = "ru.javaops.masterjava.service.mail.MailService") +public class MailServiceImpl implements MailService { + public void sendMail(List to, List cc, String subject, String body) { + MailSender.sendMail(to, cc, subject, body); + } +} \ No newline at end of file diff --git a/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java new file mode 100644 index 000000000..f2bfc79ad --- /dev/null +++ b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java @@ -0,0 +1,20 @@ +package ru.javaops.masterjava.service.mail; + +import com.google.common.collect.ImmutableList; + +import javax.xml.namespace.QName; +import javax.xml.ws.Service; +import java.net.MalformedURLException; +import java.net.URL; + +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.service.masterjava.javaops.ru/", "MailServiceImplService")); + + MailService mailService = service.getPort(MailService.class); + mailService.sendMail(ImmutableList.of(new Addressee("masterjava@javaops.ru", null)), null, "Subject", "Body"); + } +} diff --git a/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServicePublisher.java b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServicePublisher.java new file mode 100644 index 000000000..40e6db8eb --- /dev/null +++ b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServicePublisher.java @@ -0,0 +1,10 @@ +package ru.javaops.masterjava.service.mail; + +import javax.xml.ws.Endpoint; + +public class MailServicePublisher { + + public static void main(String[] args) { + Endpoint.publish("http://localhost:8080/mail/mailService", new MailServiceImpl()); + } +} From 994c01d449f204dc7270054b9e939d67b9a20522 Mon Sep 17 00:00:00 2001 From: booktest Date: Tue, 5 Mar 2019 15:50:56 +0300 Subject: [PATCH 39/83] Lesson07 - HW6 Implementation of the MailSender --- .../masterjava/service/mail/Addressee.java | 22 ++++-- .../masterjava/service/mail/MailConfig.java | 67 +++++++++++++++++++ .../masterjava/service/mail/MailSender.java | 30 +++++++++ .../mail-service/src/main/resources/mail.conf | 12 ++++ 4 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailConfig.java create mode 100644 services/mail-service/src/main/resources/mail.conf 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 index 04d3ba259..c931e09aa 100644 --- 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 @@ -1,13 +1,27 @@ package ru.javaops.masterjava.service.mail; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; +import lombok.*; @Data @AllArgsConstructor @NoArgsConstructor public class Addressee { - private String email; + private @NonNull String email; 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 + '>'; + } } 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/MailSender.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailSender.java index ecfff61c6..f2d7d1764 100644 --- 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 @@ -1,6 +1,13 @@ 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 org.apache.commons.mail.EmailException; +import ru.javaops.masterjava.persist.DBIProvider; +import ru.javaops.masterjava.service.mail.persist.MailCase; +import ru.javaops.masterjava.service.mail.persist.MailCaseDao; import java.util.List; @@ -8,5 +15,28 @@ public class MailSender { static void sendMail(List to, List cc, String subject, String body) { log.info("Send mail to \'" + to + "\' cc \'" + cc + "\' subject \'" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); + 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()); + } + + // https://yandex.ru/blog/company/66296 + email.setHeaders(ImmutableMap.of("List-Unsubscribe", "")); + + email.send(); + } catch (EmailException e) { + log.error(e.getMessage(), e); + state = e.getMessage(); + } + MAIL_CASE_DAO.insert(MailCase.of(to, cc, subject, state)); + log.info("Sent with state: " + state); + return state; } } 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..774fb9b7e --- /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: true + fromName: MasterJava +} + +include required(file("/apps_settings/masterjava/config/mail.conf")) From 566f8cb58b9e9d0d086369bb900069ecf137c7a7 Mon Sep 17 00:00:00 2001 From: booktest Date: Tue, 5 Mar 2019 16:00:59 +0300 Subject: [PATCH 40/83] Lesson07 - HW6 Saving results of sending to DB --- persist/pom.xml | 19 ++++++++ .../masterjava/service/mail/MailSender.java | 9 +++- .../service/mail/persist/MailCase.java | 27 ++++++++++++ .../service/mail/persist/MailCaseDao.java | 24 ++++++++++ .../service/mail/persist/MailCaseDaoTest.java | 22 ++++++++++ .../mail/persist/MailCaseTestData.java | 44 +++++++++++++++++++ sql/databaseChangeLog.sql | 13 ++++++ sql/lb_apply.bat | 4 +- 8 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/persist/MailCase.java create mode 100644 services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/persist/MailCaseDao.java create mode 100644 services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/persist/MailCaseDaoTest.java create mode 100644 services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/persist/MailCaseTestData.java diff --git a/persist/pom.xml b/persist/pom.xml index 6efb20ca3..9a116c786 100644 --- a/persist/pom.xml +++ b/persist/pom.xml @@ -15,6 +15,25 @@ 1.0-SNAPSHOT Persist + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.0.2 + + + + test-jar + + + + + + + ${project.groupId} 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 index f2d7d1764..415132494 100644 --- 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 @@ -13,7 +13,14 @@ @Slf4j public class MailSender { - static void sendMail(List to, List cc, String subject, String body) { + private static final MailCaseDao MAIL_CASE_DAO = DBIProvider.getDao(MailCaseDao.class); + + static MailResult sendTo(Addressee to, String subject, String body) { + 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) { log.info("Send mail to \'" + to + "\' cc \'" + cc + "\' subject \'" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); String state = MailResult.OK; try { 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..225cc9f73 --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/persist/MailCase.java @@ -0,0 +1,27 @@ +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(callSuper = false) // 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 state; + private Date datetime; + + public static MailCase of(Set to, Set cc, String subject, String state){ + return new MailCase(Joiner.on(", ").join(to), Joiner.on(", ").join(cc), subject, 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..21bdfebff --- /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 datetime >= :after ORDER BY datetime DESC") + public abstract List getAfter(@Bind("after") Date date); + + @SqlUpdate("INSERT INTO mail_hist (list_to, list_cc, subject, state, datetime) VALUES (:listTo, :listCc, :subject, :state, :datetime)") + @GetGeneratedKeys + public abstract int insert(@BindBean MailCase mails); +} 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..42765e78f --- /dev/null +++ b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/persist/MailCaseTestData.java @@ -0,0 +1,44 @@ +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; + +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", "state1" + ), + new MailCase("toMail2@ya.ru", null, "subject2", "state2", + Date.from(now.minus(Duration.ofMinutes(1)))), + new MailCase(null, "ccMail3@ya.ru", "subject3", "state3", DATE_FROM) + ); + + private static final MailCase MAIL_CASE_EXCLUDED = + new MailCase("toMail4@ya.ru", "ccMail4@ya.ru", "subject4", "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); + }); + } +} \ No newline at end of file diff --git a/sql/databaseChangeLog.sql b/sql/databaseChangeLog.sql index 60ec863d9..588c0f69b 100644 --- a/sql/databaseChangeLog.sql +++ b/sql/databaseChangeLog.sql @@ -32,3 +32,16 @@ CREATE TABLE user_group ( 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, + state TEXT NOT NULL, + datetime TIMESTAMP NOT NULL +); + +COMMENT ON TABLE mail_hist IS 'История отправки email'; +COMMENT ON COLUMN mail_hist.datetime IS 'Время отправки'; \ No newline at end of file diff --git a/sql/lb_apply.bat b/sql/lb_apply.bat index 80f23598b..010f8266b 100644 --- a/sql/lb_apply.bat +++ b/sql/lb_apply.bat @@ -3,6 +3,6 @@ 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 ^ +--username=postgres ^ +--password=root ^ migrate \ No newline at end of file From 55e73daa94ad88840aef9231770fe5af51e70562 Mon Sep 17 00:00:00 2001 From: booktest Date: Tue, 5 Mar 2019 16:04:24 +0300 Subject: [PATCH 41/83] Lesson07 - HW6 Import projects and groups in web-upload module. --- .../masterjava/persist/dao/GroupDao.java | 9 +-- .../masterjava/persist/dao/UserDao.java | 4 +- .../masterjava/upload/PayloadProcessor.java | 8 ++- .../upload/ProjectGroupProcessor.java | 52 ++++++++++++++ .../masterjava/upload/UserProcessor.java | 71 ++++++++++++++----- web/upload/src/test/resources/payload_bad.xml | 2 +- 6 files changed, 118 insertions(+), 28 deletions(-) create mode 100644 web/upload/src/main/java/ru/javaops/masterjava/upload/ProjectGroupProcessor.java 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 index d2f0908b6..54cd09a77 100644 --- a/persist/src/main/java/ru/javaops/masterjava/persist/dao/GroupDao.java +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/GroupDao.java @@ -2,13 +2,11 @@ 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.*; 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; @@ -36,4 +34,7 @@ public T insert(T group) { group.setId(id); return group; } + + @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/UserDao.java b/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java index 5174fed43..38ac1ca1b 100644 --- a/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java +++ b/persist/src/main/java/ru/javaops/masterjava/persist/dao/UserDao.java @@ -58,11 +58,11 @@ public int getSeqAndSkip(int step) { public abstract int[] insertBatch(@BindBean List users, @BatchChunkSize int chunkSize); - public List insertAndGetConflictEmails(List users) { + public List insertAndGetConflictEmails(List users) { int[] result = insertBatch(users, users.size()); return IntStreamEx.range(0, users.size()) .filter(i -> result[i] == 0) - .mapToObj(index -> users.get(index).getEmail()) + .mapToObj(users::get) .toList(); } } diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/PayloadProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/PayloadProcessor.java index 38af0f35a..c22da637d 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/PayloadProcessor.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/PayloadProcessor.java @@ -2,6 +2,8 @@ import lombok.AllArgsConstructor; import lombok.val; +import ru.javaops.masterjava.xml.schema.ObjectFactory; +import ru.javaops.masterjava.xml.util.JaxbParser; import ru.javaops.masterjava.xml.util.StaxStreamProcessor; import javax.xml.bind.JAXBException; @@ -10,6 +12,8 @@ import java.util.List; public class PayloadProcessor { + public static final JaxbParser jaxbParser = new JaxbParser(ObjectFactory.class); + private final ProjectGroupProcessor projectGroupProcessor = new ProjectGroupProcessor(); private final CityProcessor cityProcessor = new CityProcessor(); private final UserProcessor userProcessor = new UserProcessor(); @@ -24,10 +28,10 @@ public String toString() { } } - public List process(InputStream is, int chunkSize) throws XMLStreamException, JAXBException { final StaxStreamProcessor processor = new StaxStreamProcessor(is); + val groups = projectGroupProcessor.process(processor); val cities = cityProcessor.process(processor); - return userProcessor.process(processor, cities, chunkSize); + return userProcessor.process(processor, groups, cities, chunkSize); } } \ No newline at end of file diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/ProjectGroupProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/ProjectGroupProcessor.java new file mode 100644 index 000000000..791e2e9d2 --- /dev/null +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/ProjectGroupProcessor.java @@ -0,0 +1,52 @@ +package ru.javaops.masterjava.upload; + +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.Project; +import ru.javaops.masterjava.persist.model.type.GroupType; +import ru.javaops.masterjava.xml.schema.Payload; +import ru.javaops.masterjava.xml.util.StaxStreamProcessor; + +import javax.xml.bind.JAXBException; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; +import java.util.ArrayList; +import java.util.Map; + +import static ru.javaops.masterjava.upload.PayloadProcessor.jaxbParser; + +@Slf4j +public class ProjectGroupProcessor { + private final ProjectDao projectDao = DBIProvider.getDao(ProjectDao.class); + private final GroupDao groupDao = DBIProvider.getDao(GroupDao.class); + + public Map process(StaxStreamProcessor processor) throws XMLStreamException, JAXBException { + val projectMap = projectDao.getAsMap(); + val groupMap = groupDao.getAsMap(); + val newGroups = new ArrayList(); + + processor.doUntil(XMLEvent.START_ELEMENT, "Projects"); + Payload.Projects projects = jaxbParser.createUnmarshaller().unmarshal(processor.getReader(), Payload.Projects.class); + projects.getProject().forEach(p -> { + Project project = projectMap.get(p.getName()); + if (project == null) { + project = new Project(p.getName(), p.getDescription()); + log.info("Insert project " + project); + projectDao.insert(project); + } + final int projectId = project.getId(); + p.getGroup().forEach(g -> { + if (!groupMap.containsKey(g.getName())) { + newGroups.add(new Group(g.getName(), GroupType.valueOf(g.getType().value()), projectId)); + } + }); + }); + log.info("Insert groups " + newGroups); + groupDao.insertBatch(newGroups); + return groupDao.getAsMap(); + } +} \ No newline at end of file diff --git a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java index 170521ac9..0a5c867e2 100644 --- a/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java +++ b/web/upload/src/main/java/ru/javaops/masterjava/upload/UserProcessor.java @@ -1,34 +1,35 @@ package ru.javaops.masterjava.upload; +import com.google.common.base.Splitter; import lombok.extern.slf4j.Slf4j; import lombok.val; +import one.util.streamex.StreamEx; 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.City; +import ru.javaops.masterjava.persist.model.Group; import ru.javaops.masterjava.persist.model.User; +import ru.javaops.masterjava.persist.model.UserGroup; import ru.javaops.masterjava.persist.model.type.UserFlag; import ru.javaops.masterjava.upload.PayloadProcessor.FailedEmails; -import ru.javaops.masterjava.xml.schema.ObjectFactory; -import ru.javaops.masterjava.xml.util.JaxbParser; import ru.javaops.masterjava.xml.util.StaxStreamProcessor; import javax.xml.bind.JAXBException; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.XMLEvent; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import static ru.javaops.masterjava.upload.PayloadProcessor.jaxbParser; + @Slf4j public class UserProcessor { private static final int NUMBER_THREADS = 4; - private static final JaxbParser jaxbParser = new JaxbParser(ObjectFactory.class); private static UserDao userDao = DBIProvider.getDao(UserDao.class); private ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_THREADS); @@ -36,34 +37,48 @@ public class UserProcessor { /* * return failed users chunks */ - public List process(final StaxStreamProcessor processor, Map cities, int chunkSize) throws XMLStreamException, JAXBException { + public List process(final StaxStreamProcessor processor, Map groups, Map cities, int chunkSize) throws XMLStreamException, JAXBException { log.info("Start processing with chunkSize=" + chunkSize); Map>> chunkFutures = new LinkedHashMap<>(); // ordered map (emailRange -> chunk future) int id = userDao.getSeqAndSkip(chunkSize); List chunk = new ArrayList<>(chunkSize); + List chunkUserGroups = new ArrayList<>(chunkSize); val unmarshaller = jaxbParser.createUnmarshaller(); List failed = new ArrayList<>(); while (processor.doUntil(XMLEvent.START_ELEMENT, "User")) { - String cityRef = processor.getAttribute("city"); // unmarshal doesn't get city ref + // unmarshal doesn't get refs + val cityRef = processor.getAttribute("city"); + val groupRefs = processor.getAttribute("groupRefs"); ru.javaops.masterjava.xml.schema.User xmlUser = unmarshaller.unmarshal(processor.getReader(), ru.javaops.masterjava.xml.schema.User.class); + String email = xmlUser.getEmail(); if (cities.get(cityRef) == null) { - failed.add(new FailedEmails(xmlUser.getEmail(), "City '" + cityRef + "' is not present in DB")); + failed.add(new FailedEmails(email, "City '" + cityRef + "' is not present in DB")); } else { - final User user = new User(id++, xmlUser.getValue(), xmlUser.getEmail(), UserFlag.valueOf(xmlUser.getFlag().value()), cityRef); - chunk.add(user); - if (chunk.size() == chunkSize) { - addChunkFutures(chunkFutures, chunk); - chunk = new ArrayList<>(chunkSize); - id = userDao.getSeqAndSkip(chunkSize); + List groupNames = (groupRefs == null) ? + Collections.emptyList() : + Splitter.on(' ').splitToList(groupRefs); + if (!groups.keySet().containsAll(groupNames)) { + failed.add(new FailedEmails(email, "One of group from '" + groupRefs + "' is not present in DB")); + } else { + final User user = new User(id++, xmlUser.getValue(), email, UserFlag.valueOf(xmlUser.getFlag().value()), cityRef); + chunk.add(user); + List userGroups = StreamEx.of(groupNames).map(name -> new UserGroup(user.getId(), groups.get(name).getId())).toList(); + chunkUserGroups.addAll(userGroups); + if (chunk.size() == chunkSize) { + addChunkFutures(chunkFutures, chunk, chunkUserGroups); + chunk = new ArrayList<>(chunkSize); + chunkUserGroups = new ArrayList<>(chunkSize); + id = userDao.getSeqAndSkip(chunkSize); + } } } } if (!chunk.isEmpty()) { - addChunkFutures(chunkFutures, chunk); + addChunkFutures(chunkFutures, chunk, chunkUserGroups); } List allAlreadyPresents = new ArrayList<>(); @@ -83,9 +98,27 @@ public List process(final StaxStreamProcessor processor, Map>> chunkFutures, List chunk) { + private void addChunkFutures(Map>> chunkFutures, List chunk, List chunkUserGroups) { String emailRange = String.format("[%s-%s]", chunk.get(0).getEmail(), chunk.get(chunk.size() - 1).getEmail()); - Future> future = executorService.submit(() -> userDao.insertAndGetConflictEmails(chunk)); + Future> future = executorService.submit(() -> { + // https://www.programcreek.com/java-api-examples/index.php?api=org.skife.jdbi.v2.TransactionCallback + List alreadyPresentsEmails = DBIProvider.getDBI().inTransaction((handle, status) -> { + UserDao tUserDao = handle.attach(UserDao.class); + UserGroupDao tUserGroupDao = handle.attach(UserGroupDao.class); + List alreadyPresents = tUserDao.insertAndGetConflictEmails(chunk); + Set alreadyPresentsIds = StreamEx.of(alreadyPresents).map(User::getId).toSet(); + tUserGroupDao.insertBatch( + StreamEx.of(chunkUserGroups) + .filter(ug -> !alreadyPresentsIds.contains(ug.getUserId())) + .toList() + ); + return StreamEx.of(alreadyPresents).map(User::getEmail).toList(); + }); + // let gc clear chunk after insert + chunk.clear(); + chunkUserGroups.clear(); + return alreadyPresentsEmails; + }); chunkFutures.put(emailRange, future); log.info("Submit chunk: " + emailRange); } diff --git a/web/upload/src/test/resources/payload_bad.xml b/web/upload/src/test/resources/payload_bad.xml index 72a2e0422..61ec8613f 100644 --- a/web/upload/src/test/resources/payload_bad.xml +++ b/web/upload/src/test/resources/payload_bad.xml @@ -21,7 +21,7 @@ Минск - Full Name + Full Name Admin Deleted User1 From a650e6daa420f0c6c75012d66ebd3b2376a3e386 Mon Sep 17 00:00:00 2001 From: booktest Date: Tue, 5 Mar 2019 16:07:05 +0300 Subject: [PATCH 42/83] Lesson07 - customize WSDL --- .../main/webapp/WEB-INF/wsdl/mailService.wsdl | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 services/mail-service/src/main/webapp/WEB-INF/wsdl/mailService.wsdl diff --git a/services/mail-service/src/main/webapp/WEB-INF/wsdl/mailService.wsdl b/services/mail-service/src/main/webapp/WEB-INF/wsdl/mailService.wsdl new file mode 100644 index 000000000..f9deeed29 --- /dev/null +++ b/services/mail-service/src/main/webapp/WEB-INF/wsdl/mailService.wsdl @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From bac978bd7753be6dd362b76348719bfc481af8fd Mon Sep 17 00:00:00 2001 From: booktest Date: Tue, 5 Mar 2019 16:08:45 +0300 Subject: [PATCH 43/83] Lesson07 - publish customized WSDL --- .../masterjava/service/mail/MailService.java | 6 +++++- .../service/mail/MailServiceImpl.java | 4 +++- .../service/mail/MailServiceClient.java | 5 +++-- .../service/mail/MailServicePublisher.java | 17 ++++++++++++++++- 4 files changed, 27 insertions(+), 5 deletions(-) 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 index a35a779a9..8b814c37c 100644 --- 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 @@ -5,7 +5,11 @@ import javax.jws.WebService; import java.util.List; -@WebService +@WebService(targetNamespace = "http://mail.javaops.ru/") +//@SOAPBinding( +// style = SOAPBinding.Style.DOCUMENT, +// use= SOAPBinding.Use.LITERAL, +// parameterStyle = SOAPBinding.ParameterStyle.WRAPPED) public interface MailService { @WebMethod 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 index fd649330a..d122011f3 100644 --- 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 @@ -3,7 +3,9 @@ import javax.jws.WebService; import java.util.List; -@WebService(endpointInterface = "ru.javaops.masterjava.service.mail.MailService") +@WebService(endpointInterface = "ru.javaops.masterjava.service.mail.MailService", targetNamespace = "http://mail.javaops.ru/" +// , wsdlLocation = "WEB-INF/wsdl/mailService.wsdl" +) public class MailServiceImpl implements MailService { public void sendMail(List to, List cc, String subject, String body) { MailSender.sendMail(to, cc, subject, body); 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 index f2bfc79ad..52f70371f 100644 --- 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 @@ -12,9 +12,10 @@ 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.service.masterjava.javaops.ru/", "MailServiceImplService")); + new QName("http://mail.javaops.ru/", "MailServiceImplService")); MailService mailService = service.getPort(MailService.class); - mailService.sendMail(ImmutableList.of(new Addressee("masterjava@javaops.ru", null)), null, "Subject", "Body"); + mailService.sendToGroup(ImmutableSet.of( + new Addressee("masterjava@javaops.ru", null)), null, "Subject", "Body"); } } diff --git a/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServicePublisher.java b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServicePublisher.java index 40e6db8eb..1901d0fc3 100644 --- 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 @@ -1,10 +1,25 @@ package ru.javaops.masterjava.service.mail; +import com.google.common.collect.ImmutableList; +import ru.javaops.masterjava.persist.DBITestProvider; + +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; import javax.xml.ws.Endpoint; +import java.io.File; +import java.util.List; public class MailServicePublisher { public static void main(String[] args) { - Endpoint.publish("http://localhost:8080/mail/mailService", new MailServiceImpl()); + DBITestProvider.initDBI(); + + Endpoint endpoint = Endpoint.create(new MailServiceImpl()); + List metadata = ImmutableList.of( + new StreamSource( + new File("services/mail-service/src/main/webapp/WEB-INF/wsdl/mailService.wsdl"))); + + endpoint.setMetadata(metadata); + endpoint.publish("http://localhost:8080/mail/mailService"); } } From 049626166b15fb8986dacff22a64cb4edd3cb2d3 Mon Sep 17 00:00:00 2001 From: booktest Date: Tue, 5 Mar 2019 16:11:28 +0300 Subject: [PATCH 44/83] Lesson07 - HW06 Added maven dependencies and endpoint jax-ws config for deploy MailService in Tomcat --- services/mail-service/pom.xml | 55 +++++++++++++++++++ .../src/main/webapp/WEB-INF/sun-jaxws.xml | 5 ++ 2 files changed, 60 insertions(+) create mode 100644 services/mail-service/src/main/webapp/WEB-INF/sun-jaxws.xml diff --git a/services/mail-service/pom.xml b/services/mail-service/pom.xml index 2b4be6bad..6a28399a3 100644 --- a/services/mail-service/pom.xml +++ b/services/mail-service/pom.xml @@ -22,5 +22,60 @@ mail-api ${project.version} + + org.apache.commons + commons-email + 1.5 + + + ${project.groupId} + persist + ${project.version} + + + ${project.groupId} + persist + ${project.version} + test-jar + test + + + com.sun.xml.ws + jaxws-rt + 2.3.0 + + + 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 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 From 67604c9d5bcdef01797b90b18a026303adcced3b Mon Sep 17 00:00:00 2001 From: booktest Date: Wed, 6 Mar 2019 10:05:39 +0300 Subject: [PATCH 45/83] Lesson07 - create client for mail service --- .../ru/javaops/masterjava/web/WsClient.java | 40 +++++++++++++++++++ common/src/main/resources/hosts.conf | 4 ++ .../masterjava/service/mail/MailWSClient.java | 26 ++++++++++++ .../service/mail/MailWSClientMain.java | 11 +++++ 4 files changed, 81 insertions(+) create mode 100644 common/src/main/java/ru/javaops/masterjava/web/WsClient.java create mode 100644 common/src/main/resources/hosts.conf create mode 100644 services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java create mode 100644 services/mail-api/src/test/java/ru/javaops/masterjava/service/mail/MailWSClientMain.java diff --git a/common/src/main/java/ru/javaops/masterjava/web/WsClient.java b/common/src/main/java/ru/javaops/masterjava/web/WsClient.java new file mode 100644 index 000000000..13f027ff4 --- /dev/null +++ b/common/src/main/java/ru/javaops/masterjava/web/WsClient.java @@ -0,0 +1,40 @@ +package ru.javaops.masterjava.web; + +import com.typesafe.config.Config; +import ru.javaops.masterjava.config.Configs; + +import javax.xml.namespace.QName; +import javax.xml.ws.BindingProvider; +import javax.xml.ws.Service; +import java.net.URL; +import java.util.Map; + +public class WsClient { + private static Config HOSTS; + + private final Class serviceClass; + private final Service service; + private String endpointAddress; + + 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.endpointAddress = HOSTS.getString(host) + endpointAddress; + } + + // Post is not thread-safe (http://stackoverflow.com/a/10601916/548473) + public T getPort() { + T port = service.getPort(serviceClass); + BindingProvider bp = (BindingProvider) port; + Map requestContext = bp.getRequestContext(); + requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointAddress); + return port; + } +} diff --git a/common/src/main/resources/hosts.conf b/common/src/main/resources/hosts.conf new file mode 100644 index 000000000..b30de2893 --- /dev/null +++ b/common/src/main/resources/hosts.conf @@ -0,0 +1,4 @@ +hosts { + mail = "http://localhost:8080" +} +include file("/apps_settings/masterjava/config/hosts.conf") 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..5b94a754d --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java @@ -0,0 +1,26 @@ +package ru.javaops.masterjava.service.mail; + +import com.google.common.io.Resources; +import lombok.extern.slf4j.Slf4j; +import ru.javaops.masterjava.web.WsClient; + +import javax.xml.namespace.QName; +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 void 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); + } +} 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..57b478980 --- /dev/null +++ b/services/mail-api/src/test/java/ru/javaops/masterjava/service/mail/MailWSClientMain.java @@ -0,0 +1,11 @@ +package ru.javaops.masterjava.service.mail; + +import com.google.common.collect.ImmutableSet; + +public class MailWSClientMain { + public static void main(String[] args) { + MailWSClient.sendToGroup( + ImmutableSet.of(new Addressee("To ")), + ImmutableSet.of(new Addressee("Copy ")), "Subject", "Body"); + } +} \ No newline at end of file From 15668c1256108dcef51bf974c2f4fa6f3f3ff78c Mon Sep 17 00:00:00 2001 From: booktest Date: Wed, 6 Mar 2019 10:07:37 +0300 Subject: [PATCH 46/83] Lesson07 - Implementation of mass and group send mail --- .../masterjava/service/mail/Addressee.java | 1 + .../masterjava/service/mail/GroupResult.java | 23 +++++ .../masterjava/service/mail/MailResult.java | 25 +++++ .../masterjava/service/mail/MailService.java | 8 +- .../masterjava/service/mail/MailSender.java | 2 +- .../service/mail/MailServiceExecutor.java | 99 +++---------------- .../service/mail/MailServiceImpl.java | 6 +- .../service/mail/MailServiceClient.java | 2 +- 8 files changed, 70 insertions(+), 96 deletions(-) create mode 100644 services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/GroupResult.java create mode 100644 services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailResult.java 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 index c931e09aa..f34c087bb 100644 --- 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 @@ -5,6 +5,7 @@ @Data @AllArgsConstructor @NoArgsConstructor +@EqualsAndHashCode(of = "email") public class Addressee { private @NonNull String email; private String name; 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..35da521e0 --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/GroupResult.java @@ -0,0 +1,23 @@ +package ru.javaops.masterjava.service.mail; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GroupResult { + private int success; // number of successfully sent email + private List failed; // failed emails with causes + private String failedCause; // global fail cause + + @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/MailResult.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailResult.java new file mode 100644 index 000000000..6666fbe7b --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailResult.java @@ -0,0 +1,25 @@ +package ru.javaops.masterjava.service.mail; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class MailResult { + 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 + '\''; + } +} \ No newline at end of file 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 index 8b814c37c..fdfb4612f 100644 --- 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 @@ -3,7 +3,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/") //@SOAPBinding( @@ -13,9 +13,9 @@ public interface MailService { @WebMethod - void sendMail( - @WebParam(name = "to") List to, - @WebParam(name = "cc") List cc, + String sendToGroup( + @WebParam(name = "to") Set to, + @WebParam(name = "cc") Set cc, @WebParam(name = "subject") String subject, @WebParam(name = "body") String body); } \ No newline at end of file diff --git a/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailSender.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailSender.java index 415132494..a09ff7c7d 100644 --- 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 @@ -9,7 +9,7 @@ import ru.javaops.masterjava.service.mail.persist.MailCase; import ru.javaops.masterjava.service.mail.persist.MailCaseDao; -import java.util.List; +import java.util.Set; @Slf4j public class MailSender { 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 index e26f302d2..db64ec60d 100644 --- 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 @@ -1,26 +1,28 @@ package ru.javaops.masterjava.service.mail; +import lombok.extern.slf4j.Slf4j; +import one.util.streamex.StreamEx; + import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.*; -import java.util.stream.Collectors; +@Slf4j public class MailServiceExecutor { - private static final String OK = "OK"; private static final String INTERRUPTED_BY_FAULTS_NUMBER = "+++ Interrupted by faults number"; private static final String INTERRUPTED_BY_TIMEOUT = "+++ Interrupted by timeout"; private static final String INTERRUPTED_EXCEPTION = "+++ InterruptedException"; - private final ExecutorService mailExecutor = Executors.newFixedThreadPool(8); + private static final ExecutorService mailExecutor = Executors.newFixedThreadPool(8); - public GroupResult sendToList(final String template, final Set emails) throws Exception { + public static GroupResult sendBulk(final Set addressees, final String subject, final String body) { final CompletionService completionService = new ExecutorCompletionService<>(mailExecutor); - List> futures = emails.stream() - .map(email -> completionService.submit(() -> sendToUser(template, email))) - .collect(Collectors.toList()); + List> futures = StreamEx.of(addressees) + .map(addressee -> completionService.submit(() -> MailSender.sendTo(addressee, subject, body))) + .toList(); return new Callable() { private int success = 0; @@ -50,29 +52,9 @@ public GroupResult call() { return cancelWithFail(INTERRUPTED_EXCEPTION); } } -/* - for (Future future : futures) { - MailResult mailResult; - try { - mailResult = future.get(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - return cancelWithFail(INTERRUPTED_EXCEPTION); - } catch (ExecutionException e) { - return cancelWithFail(e.getCause().toString()); - } catch (TimeoutException e) { - return cancelWithFail(INTERRUPTED_BY_TIMEOUT); - } - if (mailResult.isOk()) { - success++; - } else { - failed.add(mailResult); - if (failed.size() >= 5) { - return cancelWithFail(INTERRUPTED_BY_FAULTS_NUMBER); - } - } - } -*/ - return new GroupResult(success, failed, null); + GroupResult groupResult = new GroupResult(success, failed, null); + log.info("groupResult: {}", groupResult); + return groupResult; } private GroupResult cancelWithFail(String cause) { @@ -81,61 +63,4 @@ private GroupResult cancelWithFail(String cause) { } }.call(); } - - // dummy realization - public MailResult sendToUser(String template, String email) throws Exception { - try { - Thread.sleep(500); //delay - } catch (InterruptedException e) { - // log cancel; - return null; - } - return Math.random() < 0.7 ? MailResult.ok(email) : MailResult.error(email, "Error"); - } - - public static class MailResult { - private final String email; - private final String result; - - private static MailResult ok(String email) { - return new MailResult(email, OK); - } - - private static MailResult error(String email, String error) { - return new MailResult(email, error); - } - - public boolean isOk() { - return OK.equals(result); - } - - private MailResult(String email, String cause) { - this.email = email; - this.result = cause; - } - - @Override - public String toString() { - return '(' + email + ',' + result + ')'; - } - } - - public static class GroupResult { - private final int success; // number of successfully sent email - private final List failed; // failed emails with causes - private final String failedCause; // global fail cause - - public GroupResult(int success, List failed, String failedCause) { - this.success = success; - this.failed = failed; - this.failedCause = failedCause; - } - - @Override - public String toString() { - return "Success: " + success + '\n' + - "Failed: " + failed.toString() + '\n' + - (failedCause == null ? "" : "Failed cause" + failedCause); - } - } } \ No newline at end of file diff --git a/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java index d122011f3..04c4ac1be 100644 --- 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 @@ -1,13 +1,13 @@ package ru.javaops.masterjava.service.mail; 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/" // , wsdlLocation = "WEB-INF/wsdl/mailService.wsdl" ) public class MailServiceImpl implements MailService { - public void sendMail(List to, List cc, String subject, String body) { - MailSender.sendMail(to, cc, subject, body); + public String sendToGroup(Set to, Set cc, String subject, String body) { + return MailSender.sendToGroup(to, cc, subject, body); } } \ No newline at end of file diff --git a/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java index 52f70371f..fee740f69 100644 --- 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 @@ -1,6 +1,6 @@ package ru.javaops.masterjava.service.mail; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import javax.xml.namespace.QName; import javax.xml.ws.Service; From f9739dfc2c0d71fc23816526a19d0d7112f38fed Mon Sep 17 00:00:00 2001 From: booktest Date: Wed, 6 Mar 2019 10:11:46 +0300 Subject: [PATCH 47/83] Lesson07 - Added method send bulk --- .../ru/javaops/masterjava/service/mail/MailService.java | 7 +++++++ .../javaops/masterjava/service/mail/MailServiceImpl.java | 5 +++++ 2 files changed, 12 insertions(+) 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 index fdfb4612f..0e5fb3df4 100644 --- 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 @@ -18,4 +18,11 @@ String sendToGroup( @WebParam(name = "cc") Set cc, @WebParam(name = "subject") String subject, @WebParam(name = "body") String body); + + @WebMethod + GroupResult sendBulk( + @WebParam(name = "to") Set to, + @WebParam(name = "subject") String subject, + @WebParam(name = "body") String body); + } \ 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 index 04c4ac1be..81c64b1ac 100644 --- 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 @@ -10,4 +10,9 @@ public class MailServiceImpl implements MailService { public String sendToGroup(Set to, Set cc, String subject, String body) { return MailSender.sendToGroup(to, cc, subject, body); } + + @Override + public GroupResult sendBulk(Set to, String subject, String body) { + return MailServiceExecutor.sendBulk(to, subject, body); + } } \ No newline at end of file From df20bc54f2b4f7d20332c419ddf167e986c9f7b5 Mon Sep 17 00:00:00 2001 From: booktest Date: Wed, 6 Mar 2019 10:13:29 +0300 Subject: [PATCH 48/83] Lesson07 - wsdl for test services/mail-api --- .../src/test/resources/wsdl/mailService.wsdl | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 services/mail-api/src/test/resources/wsdl/mailService.wsdl diff --git a/services/mail-api/src/test/resources/wsdl/mailService.wsdl b/services/mail-api/src/test/resources/wsdl/mailService.wsdl new file mode 100644 index 000000000..f9deeed29 --- /dev/null +++ b/services/mail-api/src/test/resources/wsdl/mailService.wsdl @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 53a05c7ed25527ad86b17d97381b4f87da4e2ad7 Mon Sep 17 00:00:00 2001 From: booktest Date: Sat, 9 Mar 2019 14:03:08 +0300 Subject: [PATCH 49/83] Lesson08 - HW7 wsdl share and added maven settings.xml for apps_settings config path in windows --- config_templates/maven/settings.xml | 24 +++++++ .../wsdl/mailService.wsdl | 0 parent/pom.xml | 1 + services/mail-api/pom.xml | 11 ++++ services/mail-service/pom.xml | 23 +++++++ .../main/webapp/WEB-INF/wsdl/mailService.wsdl | 65 ------------------- 6 files changed, 59 insertions(+), 65 deletions(-) create mode 100644 config_templates/maven/settings.xml rename {services/mail-api/src/test/resources => config_templates}/wsdl/mailService.wsdl (100%) delete mode 100644 services/mail-service/src/main/webapp/WEB-INF/wsdl/mailService.wsdl diff --git a/config_templates/maven/settings.xml b/config_templates/maven/settings.xml new file mode 100644 index 000000000..4d6b6c011 --- /dev/null +++ b/config_templates/maven/settings.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + windows + + C:/apps_settings/masterjava/config/ + + + true + + + + + \ No newline at end of file diff --git a/services/mail-api/src/test/resources/wsdl/mailService.wsdl b/config_templates/wsdl/mailService.wsdl similarity index 100% rename from services/mail-api/src/test/resources/wsdl/mailService.wsdl rename to config_templates/wsdl/mailService.wsdl diff --git a/parent/pom.xml b/parent/pom.xml index 1f0770b8a..fe96ac976 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -18,6 +18,7 @@ 1.2.3 1.7.25 + /apps_settings/masterjava/config/ false diff --git a/services/mail-api/pom.xml b/services/mail-api/pom.xml index 25c53388e..beec94956 100644 --- a/services/mail-api/pom.xml +++ b/services/mail-api/pom.xml @@ -15,6 +15,17 @@ 1.0-SNAPSHOT Mail API + + + + ${masterjava.config} + + wsdl/mailService.wsdl + + + + + ${project.groupId} diff --git a/services/mail-service/pom.xml b/services/mail-service/pom.xml index 6a28399a3..ae7996a72 100644 --- a/services/mail-service/pom.xml +++ b/services/mail-service/pom.xml @@ -16,6 +16,29 @@ war Mail Service + + mail + + + org.apache.maven.plugins + maven-war-plugin + 3.2.0 + + false + + + ${masterjava.config} + + wsdl/mailService.wsdl + + WEB-INF + + + + + + + ${project.groupId} diff --git a/services/mail-service/src/main/webapp/WEB-INF/wsdl/mailService.wsdl b/services/mail-service/src/main/webapp/WEB-INF/wsdl/mailService.wsdl deleted file mode 100644 index f9deeed29..000000000 --- a/services/mail-service/src/main/webapp/WEB-INF/wsdl/mailService.wsdl +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 878121ee00edd7eeab3f5b78c117b03c563fbc66 Mon Sep 17 00:00:00 2001 From: booktest Date: Sat, 9 Mar 2019 19:39:04 +0300 Subject: [PATCH 50/83] Lesson08 - Made access to maven variables in the entire application (app.conf). --- .../java/ru/javaops/masterjava/config/Configs.java | 10 ++++++++++ config_templates/app.conf | 6 ++++++ config_templates/version.html | 11 +++++++++++ parent-web/pom.xml | 13 +++++++++++++ services/mail-service/pom.xml | 10 ++++++++++ .../service/mail/MailServicePublisher.java | 5 ++--- 6 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 config_templates/app.conf create mode 100644 config_templates/version.html diff --git a/common/src/main/java/ru/javaops/masterjava/config/Configs.java b/common/src/main/java/ru/javaops/masterjava/config/Configs.java index 0834e95aa..1c9b1ccd7 100644 --- a/common/src/main/java/ru/javaops/masterjava/config/Configs.java +++ b/common/src/main/java/ru/javaops/masterjava/config/Configs.java @@ -3,6 +3,8 @@ import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; +import java.io.File; + public class Configs { public static Config getConfig(String resource) { @@ -12,4 +14,12 @@ public static Config getConfig(String resource) { public static Config getConfig(String resource, String domain) { return getConfig(resource).getConfig(domain); } + + 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/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/version.html b/config_templates/version.html new file mode 100644 index 000000000..dd8e39570 --- /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/parent-web/pom.xml b/parent-web/pom.xml index bf211d9dc..f6a907233 100644 --- a/parent-web/pom.xml +++ b/parent-web/pom.xml @@ -24,6 +24,18 @@ 3.2.0 false + + + src/main/webapp + + + ${masterjava.config} + + version.html + + true + + @@ -75,6 +87,7 @@ true logback.xml + app.conf diff --git a/services/mail-service/pom.xml b/services/mail-service/pom.xml index ae7996a72..eafeab8a3 100644 --- a/services/mail-service/pom.xml +++ b/services/mail-service/pom.xml @@ -26,6 +26,16 @@ false + + src/main/webapp + + + ${masterjava.config} + + version.html + + true + ${masterjava.config} 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 index 1901d0fc3..88a747cdd 100644 --- 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 @@ -1,12 +1,12 @@ 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.io.File; import java.util.List; public class MailServicePublisher { @@ -16,8 +16,7 @@ public static void main(String[] args) { Endpoint endpoint = Endpoint.create(new MailServiceImpl()); List metadata = ImmutableList.of( - new StreamSource( - new File("services/mail-service/src/main/webapp/WEB-INF/wsdl/mailService.wsdl"))); + new StreamSource(Configs.getConfigFile("wsdl/mailService.wsdl"))); endpoint.setMetadata(metadata); endpoint.publish("http://localhost:8080/mail/mailService"); From 2539c8873fd5c87623ab82ed19344e67eebc30e5 Mon Sep 17 00:00:00 2001 From: booktest Date: Sat, 9 Mar 2019 19:40:33 +0300 Subject: [PATCH 51/83] Lesson08 - HW7 update wsdl --- config_templates/wsdl/mailService.wsdl | 79 ++++++++++++++++--- .../service/mail/MailServiceClient.java | 11 ++- 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/config_templates/wsdl/mailService.wsdl b/config_templates/wsdl/mailService.wsdl index f9deeed29..6fc641cc5 100644 --- a/config_templates/wsdl/mailService.wsdl +++ b/config_templates/wsdl/mailService.wsdl @@ -8,10 +8,12 @@ name="MailServiceImplService"> - - + + + + - + @@ -19,6 +21,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -33,21 +68,43 @@ - - + + + + + - - + + + + + - - - + + + + + + + - + + + + + + + + + + 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 index fee740f69..1495d8a5e 100644 --- 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 @@ -15,7 +15,14 @@ public static void main(String[] args) throws MalformedURLException { new QName("http://mail.javaops.ru/", "MailServiceImplService")); MailService mailService = service.getPort(MailService.class); - mailService.sendToGroup(ImmutableSet.of( - new Addressee("masterjava@javaops.ru", null)), null, "Subject", "Body"); + + String state = mailService.sendToGroup(ImmutableSet.of(new Addressee("masterjava@javaops.ru", null)), null, + "Group mail subject", "Group mail body"); + System.out.println("Group mail state: " + state); + + GroupResult groupResult = mailService.sendBulk(ImmutableSet.of( + new Addressee("Мастер Java "), + new Addressee("Bad Email ")), "Bulk mail subject", "Bulk mail body"); + System.out.println("\nBulk mail groupResult:\n" + groupResult); } } From af44c67aaa3a02ce0298a276218faa3d0d5be9b6 Mon Sep 17 00:00:00 2001 From: booktest Date: Sat, 9 Mar 2019 19:50:55 +0300 Subject: [PATCH 52/83] Lesson08 - HW7 Sending mail from the web/webapp module --- .../masterjava/service/mail/MailWSClient.java | 23 +++++- web/webapp/pom.xml | 5 ++ .../masterjava/webapp/SendServlet.java | 36 +++++++++ .../main/webapp/WEB-INF/templates/users.html | 75 ++++++++++++++----- 4 files changed, 117 insertions(+), 22 deletions(-) create mode 100644 web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java 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 index 5b94a754d..784e74b05 100644 --- 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 @@ -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; @@ -19,8 +22,22 @@ public class MailWSClient { WS_CLIENT.init("mail", "/mail/mailService?wsdl"); } - public static void 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); + public static String sendToGroup(final Set to, final Set cc, final String subject, final String body) { + log.info("Send to group to '" + to + "' cc '" + cc + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); + String status = WS_CLIENT.getPort().sendToGroup(to, cc, subject, body); + log.info("Send to group with status: " + status); + return status; + } + + public static GroupResult sendBulk(final Set to, final String subject, final String body) { + log.info("Send bulk to '" + to + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); + GroupResult result = WS_CLIENT.getPort().sendBulk(to, subject, body); + log.info("Sent bulk 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)); } } diff --git a/web/webapp/pom.xml b/web/webapp/pom.xml index de3a56fc2..e892e75e0 100644 --- a/web/webapp/pom.xml +++ b/web/webapp/pom.xml @@ -26,5 +26,10 @@ persist ${project.version}
+ + ${project.groupId} + mail-api + ${project.version} +
\ No newline at end of file diff --git a/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java new file mode 100644 index 000000000..250b94866 --- /dev/null +++ b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java @@ -0,0 +1,36 @@ +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 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; + +@WebServlet("/send") +@Slf4j +public class SendServlet extends HttpServlet { + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String result; + try { + log.info("Start sending"); + req.setCharacterEncoding("UTF-8"); + resp.setCharacterEncoding("UTF-8"); + String users = req.getParameter("users"); + String subject = req.getParameter("subject"); + String body = req.getParameter("body"); + GroupResult groupResult = MailWSClient.sendBulk(MailWSClient.split(users), subject, body); + result = groupResult.toString(); + log.info("Processing finished with result: {}", result); + } catch (Exception e) { + log.error("Processing failed", e); + result = e.toString(); + } + resp.getWriter().write(result); + } +} diff --git a/web/webapp/src/main/webapp/WEB-INF/templates/users.html b/web/webapp/src/main/webapp/WEB-INF/templates/users.html index 0ae713167..758b36052 100644 --- a/web/webapp/src/main/webapp/WEB-INF/templates/users.html +++ b/web/webapp/src/main/webapp/WEB-INF/templates/users.html @@ -3,25 +3,62 @@ Users + + + - - - - - - - - - - - - - - - - - - -
#Full NameEmailFlag
+
+ + + + + + + + + + + + + + + + + + + +
#Full NameEmailFlag +
+
+

+ +

+

+
+

+

+ +

+

+
+ \ No newline at end of file From 87cb72d4b8bbd52c644ff640cbd2a4eeeda96336 Mon Sep 17 00:00:00 2001 From: booktest Date: Sat, 9 Mar 2019 21:31:43 +0300 Subject: [PATCH 53/83] Lesson08 - Added SOAP Exception. Allocation of the general part of the scheme for soap exception in common.xsd --- .../ru/javaops/masterjava/ExceptionType.java | 32 +++++++++ config_templates/wsdl/common.xsd | 33 +++++++++ config_templates/wsdl/mailService.wsdl | 18 ++++- services/common-ws/pom.xml | 71 +++++++++++++++++++ .../main/java/ru/javaops/web/FaultInfo.java | 19 +++++ .../ru/javaops/web/WebStateException.java | 32 +++++++++ .../main/java/ru/javaops}/web/WsClient.java | 7 +- services/mail-api/pom.xml | 3 +- .../masterjava/service/mail/GroupResult.java | 4 +- .../masterjava/service/mail/MailResult.java | 2 +- .../masterjava/service/mail/MailService.java | 6 +- .../masterjava/service/mail/MailWSClient.java | 7 +- .../service/mail/MailWSClientMain.java | 8 ++- services/mail-service/pom.xml | 39 +--------- .../masterjava/service/mail/MailSender.java | 13 +++- .../service/mail/MailServiceExecutor.java | 26 ++++--- .../service/mail/MailServiceImpl.java | 6 +- .../service/mail/MailServiceClient.java | 3 +- .../service/mail/MailServicePublisher.java | 3 +- services/pom.xml | 1 + 20 files changed, 264 insertions(+), 69 deletions(-) create mode 100644 common/src/main/java/ru/javaops/masterjava/ExceptionType.java create mode 100644 config_templates/wsdl/common.xsd create mode 100644 services/common-ws/pom.xml create mode 100644 services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java create mode 100644 services/common-ws/src/main/java/ru/javaops/web/WebStateException.java rename {common/src/main/java/ru/javaops/masterjava => services/common-ws/src/main/java/ru/javaops}/web/WsClient.java (81%) 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..829f7b29b --- /dev/null +++ b/common/src/main/java/ru/javaops/masterjava/ExceptionType.java @@ -0,0 +1,32 @@ +package ru.javaops.masterjava; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ExceptionType { + SYSTEM("Системная ошибка"), + DATA_BASE("Ошибка базы данных"), + STATE("Неверное состояние приложения"), + AUTHORIZATION("Ошибка авторизации"), + CONFIGURATION("Ошибка конфигурирования"), + ILLEGAL_ARGUMENT("Неверный аргумент"), + BPM("Ошибка бизнес-процесса"), + FILE("Ошибка при работе с файловой системой"), + REPORTS("Ошибка в отчете"), + EMAIL("Ошибка при отправке почты"), + TEMPLATE("Ошибка в шаблонах"), + ONE_C("Ошибка в системе 1C"), + ATTACH("Ошибка вложенного файла"), + LDAP("Ошибка соединения с LDAP"), + SOAP("Ошибка веб-сервиса"), + NETWORK("Сетевая Ошибка"); + + final private String descr; + + @Override + public String toString() { + return name() + " (" + descr + ')'; + } +} diff --git a/config_templates/wsdl/common.xsd b/config_templates/wsdl/common.xsd new file mode 100644 index 000000000..b3c9cd3d3 --- /dev/null +++ b/config_templates/wsdl/common.xsd @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config_templates/wsdl/mailService.wsdl b/config_templates/wsdl/mailService.wsdl index 6fc641cc5..6cbc96ffe 100644 --- a/config_templates/wsdl/mailService.wsdl +++ b/config_templates/wsdl/mailService.wsdl @@ -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"> + + @@ -40,7 +43,6 @@ - @@ -80,17 +82,25 @@ + + + + + + @@ -103,6 +113,9 @@ + + + @@ -112,6 +125,9 @@ + + + diff --git a/services/common-ws/pom.xml b/services/common-ws/pom.xml new file mode 100644 index 000000000..2db9fc35f --- /dev/null +++ b/services/common-ws/pom.xml @@ -0,0 +1,71 @@ + + + 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.3.0 + + + 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 + + + + + + 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/FaultInfo.java b/services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java new file mode 100644 index 000000000..c2c644ecc --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java @@ -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(); + } +} 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..6268f58b0 --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/web/WebStateException.java @@ -0,0 +1,32 @@ +package ru.javaops.web; + + +import lombok.Getter; +import ru.javaops.masterjava.ExceptionType; + +import javax.xml.ws.WebFault; + +@WebFault(name = "webStateException", targetNamespace = "http://common.javaops.ru/") +@Getter +public class WebStateException extends Exception { + private FaultInfo faultInfo; + + public WebStateException(String cause, FaultInfo faultInfo) { + super(cause); + this.faultInfo = faultInfo; + } + + public WebStateException(String cause, ExceptionType type) { + this(cause, new FaultInfo(type)); + } + + public WebStateException(Throwable cause, ExceptionType type) { + super(cause); + this.faultInfo = new FaultInfo(type); + } + + @Override + public String toString() { + return faultInfo.toString() + '\n' + super.toString(); + } +} diff --git a/common/src/main/java/ru/javaops/masterjava/web/WsClient.java b/services/common-ws/src/main/java/ru/javaops/web/WsClient.java similarity index 81% rename from common/src/main/java/ru/javaops/masterjava/web/WsClient.java rename to services/common-ws/src/main/java/ru/javaops/web/WsClient.java index 13f027ff4..a2d62ffdb 100644 --- a/common/src/main/java/ru/javaops/masterjava/web/WsClient.java +++ b/services/common-ws/src/main/java/ru/javaops/web/WsClient.java @@ -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,8 @@ public T getPort() { requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointAddress); return port; } + + public static WebStateException getWebStateException(Throwable t, ExceptionType type) { + return (t instanceof WebStateException) ? (WebStateException) t : new WebStateException(t, type); + } } diff --git a/services/mail-api/pom.xml b/services/mail-api/pom.xml index beec94956..3ff36485e 100644 --- a/services/mail-api/pom.xml +++ b/services/mail-api/pom.xml @@ -21,6 +21,7 @@ ${masterjava.config} wsdl/mailService.wsdl + wsdl/common.xsd @@ -29,7 +30,7 @@ ${project.groupId} - common + common-ws ${project.version} 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 index 35da521e0..1770fa646 100644 --- 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 @@ -12,12 +12,10 @@ public class GroupResult { private int success; // number of successfully sent email private List failed; // failed emails with causes - private String failedCause; // global fail cause @Override public String toString() { return "Success: " + success + '\n' + - (failed == null ? "" : "Failed: " + failed.toString() + '\n') + - (failedCause == null ? "" : "Failed cause: " + failedCause); + (failed == null ? "" : "Failed: " + failed.toString()); } } \ 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 index 6666fbe7b..35c43a3c8 100644 --- 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 @@ -20,6 +20,6 @@ public boolean isOk() { @Override public String toString() { - return '\'' + email + "' result '" + result + '\''; + return "'" + email + "' result: " + result; } } \ No newline at end of file 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 index 0e5fb3df4..f7e9cb3ae 100644 --- 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 @@ -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 @@ String sendToGroup( @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 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 index 784e74b05..e3d1e7598 100644 --- 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 @@ -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; @@ -22,14 +23,14 @@ public class MailWSClient { WS_CLIENT.init("mail", "/mail/mailService?wsdl"); } - 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 to group to '" + to + "' cc '" + cc + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); String status = WS_CLIENT.getPort().sendToGroup(to, cc, subject, body); log.info("Send to group with status: " + status); 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 bulk to '" + to + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); GroupResult result = WS_CLIENT.getPort().sendBulk(to, subject, body); log.info("Sent bulk with result: " + result); 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 index 57b478980..6b3d0eeef 100644 --- 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 @@ -1,11 +1,15 @@ package ru.javaops.masterjava.service.mail; import com.google.common.collect.ImmutableSet; +import lombok.extern.slf4j.Slf4j; +import ru.javaops.web.WebStateException; +@Slf4j public class MailWSClientMain { - public static void main(String[] args) { - MailWSClient.sendToGroup( + public static void main(String[] args) throws WebStateException { + String state = MailWSClient.sendToGroup( ImmutableSet.of(new Addressee("To ")), ImmutableSet.of(new Addressee("Copy ")), "Subject", "Body"); + System.out.println(state); } } \ No newline at end of file diff --git a/services/mail-service/pom.xml b/services/mail-service/pom.xml index eafeab8a3..819c187de 100644 --- a/services/mail-service/pom.xml +++ b/services/mail-service/pom.xml @@ -40,6 +40,7 @@ ${masterjava.config} wsdl/mailService.wsdl + wsdl/common.xsd WEB-INF @@ -72,43 +73,5 @@ test-jar test
- - com.sun.xml.ws - jaxws-rt - 2.3.0 - - - 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 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 index a09ff7c7d..23d51ffeb 100644 --- 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 @@ -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; @@ -15,12 +17,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 { @@ -42,7 +44,12 @@ static String sendToGroup(Set to, Set cc, String subject, log.error(e.getMessage(), e); state = e.getMessage(); } - MAIL_CASE_DAO.insert(MailCase.of(to, cc, subject, state)); + try { + MAIL_CASE_DAO.insert(MailCase.of(to, cc, subject, state)); + } catch (Exception e) { + log.error("Mail history saving exception", e); + throw new WebStateException(e, ExceptionType.DATA_BASE); + } log.info("Sent with state: " + state); return state; } 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 index db64ec60d..1314b1335 100644 --- 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 @@ -2,6 +2,9 @@ import lombok.extern.slf4j.Slf4j; import one.util.streamex.StreamEx; +import ru.javaops.masterjava.ExceptionType; +import ru.javaops.web.WebStateException; +import ru.javaops.web.WsClient; import java.util.ArrayList; import java.util.List; @@ -13,11 +16,10 @@ 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 Set addressees, final String subject, final String body) { + public static GroupResult sendBulk(final Set addressees, final String subject, final String body) throws WebStateException { final CompletionService completionService = new ExecutorCompletionService<>(mailExecutor); List> futures = StreamEx.of(addressees) @@ -29,12 +31,12 @@ public static GroupResult sendBulk(final Set addressees, final String private List failed = new ArrayList<>(); @Override - public GroupResult call() { + public GroupResult call() throws WebStateException { while (!futures.isEmpty()) { try { Future future = completionService.poll(10, TimeUnit.SECONDS); if (future == null) { - return cancelWithFail(INTERRUPTED_BY_TIMEOUT); + cancel(INTERRUPTED_BY_TIMEOUT, null); } futures.remove(future); MailResult mailResult = future.get(); @@ -43,23 +45,27 @@ public GroupResult call() { } else { failed.add(mailResult); if (failed.size() >= 5) { - return cancelWithFail(INTERRUPTED_BY_FAULTS_NUMBER); + cancel(INTERRUPTED_BY_FAULTS_NUMBER, null); } } } catch (ExecutionException e) { - return cancelWithFail(e.getCause().toString()); + cancel(null, e.getCause()); } catch (InterruptedException e) { - return cancelWithFail(INTERRUPTED_EXCEPTION); + cancel(INTERRUPTED_BY_TIMEOUT, null); } } - GroupResult groupResult = new GroupResult(success, failed, null); + GroupResult groupResult = new GroupResult(success, failed); log.info("groupResult: {}", groupResult); return groupResult; } - private GroupResult cancelWithFail(String cause) { + private void cancel(String cause, Throwable t) throws WebStateException { futures.forEach(f -> f.cancel(true)); - return new GroupResult(success, failed, cause); + if (cause != null) { + throw new WebStateException(cause, ExceptionType.EMAIL); + } else { + throw WsClient.getWebStateException(t, ExceptionType.EMAIL); + } } }.call(); } 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 index 81c64b1ac..90ac47717 100644 --- 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 @@ -1,5 +1,7 @@ package ru.javaops.masterjava.service.mail; +import ru.javaops.web.WebStateException; + import javax.jws.WebService; import java.util.Set; @@ -7,12 +9,12 @@ // , wsdlLocation = "WEB-INF/wsdl/mailService.wsdl" ) public class MailServiceImpl implements MailService { - 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 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 index 1495d8a5e..5ec260012 100644 --- 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 @@ -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; @@ -9,7 +10,7 @@ public class MailServiceClient { - public static void main(String[] args) throws MalformedURLException { + public static void main(String[] args) throws MalformedURLException, WebStateException { Service service = Service.create( new URL("http://localhost:8080/mail/mailService?wsdl"), new QName("http://mail.javaops.ru/", "MailServiceImplService")); 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 index 88a747cdd..1d77f0f27 100644 --- 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 @@ -16,7 +16,8 @@ public static void main(String[] args) { 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"); diff --git a/services/pom.xml b/services/pom.xml index 62ffc5e38..4d04cba32 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -9,6 +9,7 @@ Services + common-ws mail-api mail-service From de4130855dcf665b3d0e8847145809dcb82d4b2e Mon Sep 17 00:00:00 2001 From: booktest Date: Sun, 10 Mar 2019 00:07:12 +0300 Subject: [PATCH 54/83] Lesson08 - fix wsdl and schema --- .../java/ru/javaops/masterjava/ExceptionType.java | 3 +++ config_templates/wsdl/common.xsd | 6 ++++-- .../src/main/java/ru/javaops/web/FaultInfo.java | 3 +++ .../javaops/masterjava/service/mail/Addressee.java | 10 +++++++++- .../javaops/masterjava/service/mail/GroupResult.java | 7 +++++-- .../javaops/masterjava/service/mail/MailResult.java | 12 ++++++++++-- services/mail-service/src/main/resources/mail.conf | 2 +- 7 files changed, 35 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/ru/javaops/masterjava/ExceptionType.java b/common/src/main/java/ru/javaops/masterjava/ExceptionType.java index 829f7b29b..90b6c3e05 100644 --- a/common/src/main/java/ru/javaops/masterjava/ExceptionType.java +++ b/common/src/main/java/ru/javaops/masterjava/ExceptionType.java @@ -3,8 +3,11 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import javax.xml.bind.annotation.XmlType; + @Getter @AllArgsConstructor +@XmlType(namespace = "http://common.javaops.ru/") public enum ExceptionType { SYSTEM("Системная ошибка"), DATA_BASE("Ошибка базы данных"), diff --git a/config_templates/wsdl/common.xsd b/config_templates/wsdl/common.xsd index b3c9cd3d3..e996681a5 100644 --- a/config_templates/wsdl/common.xsd +++ b/config_templates/wsdl/common.xsd @@ -1,12 +1,14 @@ + - + - + 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 index c2c644ecc..d3525c5b9 100644 --- a/services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java +++ b/services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java @@ -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/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 index f34c087bb..13ee1e74c 100644 --- 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 @@ -2,12 +2,20 @@ import lombok.*; -@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; + @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode(of = "email") +@XmlAccessorType(XmlAccessType.FIELD) +@Getter public class Addressee { + @XmlAttribute private @NonNull String email; + @XmlValue private String name; public Addressee(String email){ 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 index 1770fa646..adf7b5364 100644 --- 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 @@ -1,14 +1,17 @@ package ru.javaops.masterjava.service.mail; import lombok.AllArgsConstructor; -import lombok.Data; +import lombok.Getter; import lombok.NoArgsConstructor; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; import java.util.List; -@Data @AllArgsConstructor @NoArgsConstructor +@XmlAccessorType(XmlAccessType.FIELD) +@Getter public class GroupResult { private int success; // number of successfully sent email private List failed; // failed emails with causes 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 index 35c43a3c8..efa69680b 100644 --- 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 @@ -1,17 +1,25 @@ package ru.javaops.masterjava.service.mail; import lombok.AllArgsConstructor; -import lombok.Data; +import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; -@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; + @AllArgsConstructor @NoArgsConstructor +@XmlAccessorType(XmlAccessType.FIELD) +@Getter public class MailResult { public static final String OK = "OK"; + @XmlAttribute private @NonNull String email; + @XmlValue private String result; public boolean isOk() { diff --git a/services/mail-service/src/main/resources/mail.conf b/services/mail-service/src/main/resources/mail.conf index 774fb9b7e..cf955dc3e 100644 --- a/services/mail-service/src/main/resources/mail.conf +++ b/services/mail-service/src/main/resources/mail.conf @@ -5,7 +5,7 @@ mail { password: password useSSL: true useTLS: false - debug: true + debug: false fromName: MasterJava } From 6f1a95e96aadd5ef7a36b63862ab487fe27f7b4d Mon Sep 17 00:00:00 2001 From: booktest Date: Sun, 10 Mar 2019 00:25:52 +0300 Subject: [PATCH 55/83] Lesson08 - Add mvn plugins --- persist/pom.xml | 24 +++++++++++++++++++ services/mail-service/pom.xml | 43 +++++++++++++++-------------------- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/persist/pom.xml b/persist/pom.xml index 9a116c786..7ac053bfa 100644 --- a/persist/pom.xml +++ b/persist/pom.xml @@ -31,6 +31,30 @@ + + + + org.liquibase + liquibase-maven-plugin + 3.5.3 + + ../sql/databaseChangeLog.sql + org.postgresql.Driver + jdbc:postgresql://localhost:5432/masterjava + postgres + root + + + + + + process-resources + + update + + + + diff --git a/services/mail-service/pom.xml b/services/mail-service/pom.xml index 819c187de..23022ac30 100644 --- a/services/mail-service/pom.xml +++ b/services/mail-service/pom.xml @@ -21,31 +21,24 @@ org.apache.maven.plugins - maven-war-plugin - 3.2.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 + + + + + + + + + + + From 1d0217bd9f91ddaf6717f42eddeab5d75419b1fa Mon Sep 17 00:00:00 2001 From: booktest Date: Sun, 10 Mar 2019 00:26:34 +0300 Subject: [PATCH 56/83] Lesson08 - small fix --- config_templates/wsdl/mailService.wsdl | 2 +- sql/databaseChangeLog.sql | 6 +++--- initDB.sql => sql/initDB.sql | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename initDB.sql => sql/initDB.sql (100%) diff --git a/config_templates/wsdl/mailService.wsdl b/config_templates/wsdl/mailService.wsdl index 6cbc96ffe..c1ef3bf2b 100644 --- a/config_templates/wsdl/mailService.wsdl +++ b/config_templates/wsdl/mailService.wsdl @@ -32,7 +32,7 @@ - + diff --git a/sql/databaseChangeLog.sql b/sql/databaseChangeLog.sql index 588c0f69b..44ef1cef8 100644 --- a/sql/databaseChangeLog.sql +++ b/sql/databaseChangeLog.sql @@ -1,6 +1,6 @@ --liquibase formatted sql ---changeset gkislin:1 +--changeset immmus:1 CREATE SEQUENCE common_seq START 100000; CREATE TABLE city ( @@ -11,7 +11,7 @@ CREATE TABLE city ( ALTER TABLE users ADD COLUMN city_ref TEXT REFERENCES city (ref) ON UPDATE CASCADE; ---changeset gkislin:2 +--changeset immmus:2 CREATE TABLE project ( id INTEGER PRIMARY KEY DEFAULT nextval('common_seq'), name TEXT UNIQUE NOT NULL, @@ -33,7 +33,7 @@ CREATE TABLE user_group ( CONSTRAINT users_group_idx UNIQUE (user_id, group_id) ); ---changeset gkislin:3 +--changeset immmus:3 CREATE TABLE mail_hist ( id SERIAL PRIMARY KEY, list_to TEXT NULL, diff --git a/initDB.sql b/sql/initDB.sql similarity index 100% rename from initDB.sql rename to sql/initDB.sql From ef5e666b76451293f5bcd87e6cbef7e4a945b5b1 Mon Sep 17 00:00:00 2001 From: booktest Date: Wed, 13 Mar 2019 13:31:33 +0300 Subject: [PATCH 57/83] Lesson09 - fix package --- .../java/ru/javaops/{ => masterjava}/web/FaultInfo.java | 2 +- .../javaops/{ => masterjava}/web/WebStateException.java | 2 +- .../java/ru/javaops/{ => masterjava}/web/WsClient.java | 2 +- .../ru/javaops/masterjava/service/mail/MailService.java | 2 +- .../ru/javaops/masterjava/service/mail/MailWSClient.java | 4 ++-- .../javaops/masterjava/service/mail/MailWSClientMain.java | 2 +- .../ru/javaops/masterjava/service/mail/MailSender.java | 8 +++++++- .../masterjava/service/mail/MailServiceExecutor.java | 4 ++-- .../javaops/masterjava/service/mail/MailServiceImpl.java | 2 +- .../masterjava/service/mail/MailServiceClient.java | 2 +- 10 files changed, 18 insertions(+), 12 deletions(-) rename services/common-ws/src/main/java/ru/javaops/{ => masterjava}/web/FaultInfo.java (92%) rename services/common-ws/src/main/java/ru/javaops/{ => masterjava}/web/WebStateException.java (95%) rename services/common-ws/src/main/java/ru/javaops/{ => masterjava}/web/WsClient.java (97%) diff --git a/services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/FaultInfo.java similarity index 92% rename from services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java rename to services/common-ws/src/main/java/ru/javaops/masterjava/web/FaultInfo.java index d3525c5b9..ed0c09b1f 100644 --- a/services/common-ws/src/main/java/ru/javaops/web/FaultInfo.java +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/FaultInfo.java @@ -1,4 +1,4 @@ -package ru.javaops.web; +package ru.javaops.masterjava.web; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/services/common-ws/src/main/java/ru/javaops/web/WebStateException.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/WebStateException.java similarity index 95% rename from services/common-ws/src/main/java/ru/javaops/web/WebStateException.java rename to services/common-ws/src/main/java/ru/javaops/masterjava/web/WebStateException.java index 6268f58b0..90f9fc44d 100644 --- a/services/common-ws/src/main/java/ru/javaops/web/WebStateException.java +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/WebStateException.java @@ -1,4 +1,4 @@ -package ru.javaops.web; +package ru.javaops.masterjava.web; import lombok.Getter; diff --git a/services/common-ws/src/main/java/ru/javaops/web/WsClient.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java similarity index 97% rename from services/common-ws/src/main/java/ru/javaops/web/WsClient.java rename to services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java index a2d62ffdb..f2b2e80d4 100644 --- a/services/common-ws/src/main/java/ru/javaops/web/WsClient.java +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java @@ -1,4 +1,4 @@ -package ru.javaops.web; +package ru.javaops.masterjava.web; import com.typesafe.config.Config; import ru.javaops.masterjava.ExceptionType; 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 index f7e9cb3ae..5d68fc938 100644 --- 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 @@ -1,6 +1,6 @@ package ru.javaops.masterjava.service.mail; -import ru.javaops.web.WebStateException; +import ru.javaops.masterjava.web.WebStateException; import javax.jws.WebMethod; import javax.jws.WebParam; 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 index e3d1e7598..c168d141b 100644 --- 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 @@ -5,8 +5,8 @@ import com.google.common.collect.Iterables; import com.google.common.io.Resources; import lombok.extern.slf4j.Slf4j; -import ru.javaops.web.WebStateException; -import ru.javaops.web.WsClient; +import ru.javaops.masterjava.web.WebStateException; +import ru.javaops.masterjava.web.WsClient; import javax.xml.namespace.QName; import java.util.Set; 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 index 6b3d0eeef..c63e51ad4 100644 --- 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 @@ -2,7 +2,7 @@ import com.google.common.collect.ImmutableSet; import lombok.extern.slf4j.Slf4j; -import ru.javaops.web.WebStateException; +import ru.javaops.masterjava.web.WebStateException; @Slf4j public class MailWSClientMain { 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 index 23d51ffeb..c968039ce 100644 --- 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 @@ -9,7 +9,13 @@ 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 ru.javaops.masterjava.web.WebStateException; + +import javax.mail.internet.MimeUtility; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Set; import java.util.Set; 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 index 1314b1335..f1be4d65f 100644 --- 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 @@ -3,8 +3,8 @@ import lombok.extern.slf4j.Slf4j; import one.util.streamex.StreamEx; import ru.javaops.masterjava.ExceptionType; -import ru.javaops.web.WebStateException; -import ru.javaops.web.WsClient; +import ru.javaops.masterjava.web.WebStateException; +import ru.javaops.masterjava.web.WsClient; import java.util.ArrayList; import java.util.List; 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 index 90ac47717..8f2ad11d6 100644 --- 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 @@ -1,6 +1,6 @@ package ru.javaops.masterjava.service.mail; -import ru.javaops.web.WebStateException; +import ru.javaops.masterjava.web.WebStateException; import javax.jws.WebService; import java.util.Set; 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 index 5ec260012..f61a8513b 100644 --- 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 @@ -1,7 +1,7 @@ package ru.javaops.masterjava.service.mail; import com.google.common.collect.ImmutableSet; -import ru.javaops.web.WebStateException; +import ru.javaops.masterjava.web.WebStateException; import javax.xml.namespace.QName; import javax.xml.ws.Service; From 642499f55069747bbcab4deca33572b214a0f35f Mon Sep 17 00:00:00 2001 From: booktest Date: Wed, 13 Mar 2019 13:32:13 +0300 Subject: [PATCH 58/83] Lesson09 - HW8 service attach --- .../ru/javaops/masterjava/ExceptionType.java | 2 +- config_templates/wsdl/common.xsd | 2 +- config_templates/wsdl/mailService.wsdl | 10 +++++++++ .../masterjava/service/mail/Attachment.java | 22 +++++++++++++++++++ .../masterjava/service/mail/MailService.java | 7 ++++-- .../masterjava/service/mail/MailWSClient.java | 10 +++++---- .../service/mail/MailWSClientMain.java | 12 ++++++++-- .../service/mail/MailServiceImpl.java | 10 +++++---- .../service/mail/MailServiceClient.java | 11 ++++++++-- 9 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Attachment.java diff --git a/common/src/main/java/ru/javaops/masterjava/ExceptionType.java b/common/src/main/java/ru/javaops/masterjava/ExceptionType.java index 90b6c3e05..f0152574c 100644 --- a/common/src/main/java/ru/javaops/masterjava/ExceptionType.java +++ b/common/src/main/java/ru/javaops/masterjava/ExceptionType.java @@ -21,7 +21,7 @@ public enum ExceptionType { EMAIL("Ошибка при отправке почты"), TEMPLATE("Ошибка в шаблонах"), ONE_C("Ошибка в системе 1C"), - ATTACH("Ошибка вложенного файла"), + ATTACHMENT("Ошибка вложенного файла"), LDAP("Ошибка соединения с LDAP"), SOAP("Ошибка веб-сервиса"), NETWORK("Сетевая Ошибка"); diff --git a/config_templates/wsdl/common.xsd b/config_templates/wsdl/common.xsd index e996681a5..03675feb1 100644 --- a/config_templates/wsdl/common.xsd +++ b/config_templates/wsdl/common.xsd @@ -26,7 +26,7 @@ - + diff --git a/config_templates/wsdl/mailService.wsdl b/config_templates/wsdl/mailService.wsdl index c1ef3bf2b..c7dc0901e 100644 --- a/config_templates/wsdl/mailService.wsdl +++ b/config_templates/wsdl/mailService.wsdl @@ -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 @@ + @@ -68,6 +71,13 @@ + + + + + + + diff --git a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Attachment.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Attachment.java new file mode 100644 index 000000000..d383528ee --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/Attachment.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 Attachment { + // 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/MailService.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailService.java index 5d68fc938..61267d9b5 100644 --- 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 @@ -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 @@ String sendToGroup( @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 = "attachments") List attachments) 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 = "attachments") List attachments) 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 index c168d141b..61855028a 100644 --- 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 @@ -9,6 +9,8 @@ import ru.javaops.masterjava.web.WsClient; import javax.xml.namespace.QName; +import javax.xml.ws.soap.MTOMFeature; +import java.util.List; import java.util.Set; @Slf4j @@ -23,16 +25,16 @@ public class MailWSClient { WS_CLIENT.init("mail", "/mail/mailService?wsdl"); } - 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 attachments) throws WebStateException { log.info("Send to group to '" + to + "' cc '" + cc + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); - String status = WS_CLIENT.getPort().sendToGroup(to, cc, subject, body); + String status = getPort().sendToGroup(to, cc, subject, body, attachments); log.info("Send to group with status: " + status); 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 attachments) throws WebStateException { log.info("Send bulk to '" + to + "' subject '" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); - GroupResult result = WS_CLIENT.getPort().sendBulk(to, subject, body); + GroupResult result = getPort().sendBulk(to, subject, body, attachments); log.info("Sent bulk with result: " + result); return result; } 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 index c63e51ad4..f5c7cc735 100644 --- 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 @@ -1,15 +1,23 @@ package ru.javaops.masterjava.service.mail; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import lombok.extern.slf4j.Slf4j; import ru.javaops.masterjava.web.WebStateException; +import javax.activation.DataHandler; +import java.io.File; +import java.net.MalformedURLException; + @Slf4j public class MailWSClientMain { - public static void main(String[] args) throws WebStateException { + public static void main(String[] args) throws WebStateException, MalformedURLException { String state = MailWSClient.sendToGroup( ImmutableSet.of(new Addressee("To ")), - ImmutableSet.of(new Addressee("Copy ")), "Subject", "Body"); + ImmutableSet.of(new Addressee("Copy ")), "Subject", "Body", + ImmutableList.of(new Attachment("version.html", + new DataHandler(new File("config_templates/version.html").toURI().toURL())) + )); System.out.println(state); } } \ 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 index 8f2ad11d6..54bf18862 100644 --- 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 @@ -3,18 +3,20 @@ import ru.javaops.masterjava.web.WebStateException; 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" ) public class MailServiceImpl implements MailService { - public String sendToGroup(Set to, Set cc, String subject, String body) throws WebStateException { - return MailSender.sendToGroup(to, cc, subject, body); + public String sendToGroup(Set to, Set cc, String subject, String body, List attachments) throws WebStateException { + return MailSender.sendToGroup(to, cc, subject, body, attachments); } @Override - public GroupResult sendBulk(Set to, String subject, String body) throws WebStateException { - return MailServiceExecutor.sendBulk(to, subject, body); + public GroupResult sendBulk(Set to, String subject, String body, List attachments) throws WebStateException { + return MailServiceExecutor.sendBulk(to, subject, body, attachments); } } \ No newline at end of file diff --git a/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java b/services/mail-service/src/test/java/ru/javaops/masterjava/service/mail/MailServiceClient.java index f61a8513b..6aaa54317 100644 --- 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 @@ -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.masterjava.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 { @@ -15,15 +19,18 @@ public static void main(String[] args) throws MalformedURLException, WebStateExc new URL("http://localhost:8080/mail/mailService?wsdl"), new QName("http://mail.javaops.ru/", "MailServiceImplService")); + List attachments = ImmutableList.of( + new Attachment("version.html", new DataHandler(new File("config_templates/version.html").toURI().toURL()))); + MailService mailService = service.getPort(MailService.class); String state = mailService.sendToGroup(ImmutableSet.of(new Addressee("masterjava@javaops.ru", null)), null, - "Group mail subject", "Group mail body"); + "Group mail subject", "Group mail body", attachments); System.out.println("Group mail state: " + state); GroupResult groupResult = mailService.sendBulk(ImmutableSet.of( new Addressee("Мастер Java "), - new Addressee("Bad Email ")), "Bulk mail subject", "Bulk mail body"); + new Addressee("Bad Email ")), "Bulk mail subject", "Bulk mail body", attachments); System.out.println("\nBulk mail groupResult:\n" + groupResult); } } From e2f775ee1e4dd6fdda6ec55b95a416f934879829 Mon Sep 17 00:00:00 2001 From: booktest Date: Wed, 13 Mar 2019 13:33:13 +0300 Subject: [PATCH 59/83] Lesson09 - HW8 MTOM --- services/common-ws/pom.xml | 6 ++++++ .../src/main/java/ru/javaops/masterjava/web/WsClient.java | 5 +++-- .../ru/javaops/masterjava/service/mail/MailWSClient.java | 4 ++++ .../ru/javaops/masterjava/service/mail/MailServiceImpl.java | 2 ++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/services/common-ws/pom.xml b/services/common-ws/pom.xml index 2db9fc35f..d37692e7c 100644 --- a/services/common-ws/pom.xml +++ b/services/common-ws/pom.xml @@ -61,6 +61,12 @@
+ + org.jvnet.mimepull + mimepull + 1.9.7 + + javax.servlet javax.servlet-api diff --git a/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java index f2b2e80d4..6ca32585a 100644 --- a/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java @@ -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 @@ public void init(String host, String endpointAddress) { } // 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); 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 index 61855028a..e4de96c59 100644 --- 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 @@ -39,6 +39,10 @@ public static GroupResult sendBulk(final Set to, final String subject 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/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 index 54bf18862..4737ed7a2 100644 --- 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 @@ -10,6 +10,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 { public String sendToGroup(Set to, Set cc, String subject, String body, List attachments) throws WebStateException { return MailSender.sendToGroup(to, cc, subject, body, attachments); From 23af58f5901040757a1ff5adbfb2c4cccea4b678 Mon Sep 17 00:00:00 2001 From: booktest Date: Wed, 13 Mar 2019 13:34:42 +0300 Subject: [PATCH 60/83] Lesson09 - HW8 webapp attach --- .../service/mail/util/Attachments.java | 47 +++++++++++++++++++ .../masterjava/webapp/SendServlet.java | 10 +++- .../main/webapp/WEB-INF/templates/users.html | 29 +++++++++--- 3 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java diff --git a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java new file mode 100644 index 000000000..f8dddeef3 --- /dev/null +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java @@ -0,0 +1,47 @@ +package ru.javaops.masterjava.service.mail.util; + +import lombok.AllArgsConstructor; +import ru.javaops.masterjava.service.mail.Attachment; + +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 Attachment getAttachment(String name, InputStream inputStream) { + return new Attachment(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 ""; + } + } +} diff --git a/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java index 250b94866..b6ac85969 100644 --- a/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java +++ b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java @@ -1,18 +1,23 @@ package ru.javaops.masterjava.webapp; +import com.google.common.collect.ImmutableList; import lombok.extern.slf4j.Slf4j; import ru.javaops.masterjava.service.mail.GroupResult; import ru.javaops.masterjava.service.mail.MailWSClient; +import ru.javaops.masterjava.service.mail.util.Attachments; 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; @WebServlet("/send") @Slf4j +@MultipartConfig public class SendServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { @@ -24,7 +29,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S String users = req.getParameter("users"); String subject = req.getParameter("subject"); String body = req.getParameter("body"); - GroupResult groupResult = MailWSClient.sendBulk(MailWSClient.split(users), subject, body); + Part filePart = req.getPart("attach"); + GroupResult groupResult = MailWSClient.sendBulk(MailWSClient.split(users), subject, body, + filePart == null ? null : + ImmutableList.of(Attachments.getAttachment(filePart.getSubmittedFileName(), filePart.getInputStream()))); result = groupResult.toString(); log.info("Processing finished with result: {}", result); } catch (Exception e) { diff --git a/web/webapp/src/main/webapp/WEB-INF/templates/users.html b/web/webapp/src/main/webapp/WEB-INF/templates/users.html index 758b36052..4d54f8e2d 100644 --- a/web/webapp/src/main/webapp/WEB-INF/templates/users.html +++ b/web/webapp/src/main/webapp/WEB-INF/templates/users.html @@ -39,6 +39,9 @@


+

+ +

@@ -50,14 +53,26 @@ var users = $("input:checkbox:checked").map(function () { return this.value; }).get(); + + // https://stackoverflow.com/a/5976031/548473 + var fd = new FormData(); + fd.append('users', users); + fd.append('subject', $("#subject").val()); + fd.append('body', $("#body").val()); + var attach = $('#attach')[0].files[0]; + if (attach) fd.append('attach', attach); + // https://stackoverflow.com/a/22213543/548473 - $.post("send", "users=" + users + "&subject=" + $("#subject").val() + "&body=" + $("#body").val()) - .done(function (result) { - $('#result').html(result); - }) - .fail(function (result) { - $('#result').html(result); - }); + $.post({ + url: 'send', + data: fd, + contentType: false, + processData: false + }).done(function (result) { + $('#result').html(result); + }).fail(function (result) { + $('#result').html(result); + }); } From 72d6dc4ea1c5cc70e27ac6068ade424b315c8ba6 Mon Sep 17 00:00:00 2001 From: booktest Date: Wed, 13 Mar 2019 13:35:36 +0300 Subject: [PATCH 61/83] Lesson09 - HW8 mail attach --- .../masterjava/service/mail/MailSender.java | 20 +++++++++++++------ .../service/mail/MailServiceExecutor.java | 4 ++-- 2 files changed, 16 insertions(+), 8 deletions(-) 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 index c968039ce..d26e06fd5 100644 --- 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 @@ -4,7 +4,6 @@ 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; @@ -17,18 +16,17 @@ import java.util.List; import java.util.Set; -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) throws WebStateException { - val state = sendToGroup(ImmutableSet.of(to), ImmutableSet.of(), subject, body); + static MailResult sendTo(Addressee to, String subject, String body, List attachments) throws WebStateException { + val state = sendToGroup(ImmutableSet.of(to), ImmutableSet.of(), subject, body, attachments); 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 attachments) throws WebStateException { log.info("Send mail to \'" + to + "\' cc \'" + cc + "\' subject \'" + subject + (log.isDebugEnabled() ? "\nbody=" + body : "")); String state = MailResult.OK; try { @@ -41,12 +39,15 @@ static String sendToGroup(Set to, Set cc, String subject, for (Addressee addressee : cc) { email.addCc(addressee.getEmail(), addressee.getName()); } + for (Attachment attach : attachments) { + 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(); } @@ -59,4 +60,11 @@ static String sendToGroup(Set to, Set cc, String subject, 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 index f1be4d65f..1746e1a5d 100644 --- 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 @@ -19,11 +19,11 @@ public class MailServiceExecutor { private static final ExecutorService mailExecutor = Executors.newFixedThreadPool(8); - public static GroupResult sendBulk(final Set addressees, final String subject, final String body) throws WebStateException { + public static GroupResult sendBulk(final Set addressees, final String subject, final String body, List attachments) throws WebStateException { 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, attachments))) .toList(); return new Callable() { From af27f6a48654833f5f01b31f4d07e716b690a2a2 Mon Sep 17 00:00:00 2001 From: booktest Date: Wed, 13 Mar 2019 13:36:35 +0300 Subject: [PATCH 62/83] Lesson09 - HW8 git revision in version.html --- config_templates/app.conf | 2 ++ config_templates/version.html | 8 ++++++-- parent/pom.xml | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/config_templates/app.conf b/config_templates/app.conf index d2aee4c5b..6db0d7d7e 100644 --- a/config_templates/app.conf +++ b/config_templates/app.conf @@ -3,4 +3,6 @@ app { projectName = ${project.name} version = ${project.version} configDir = "${masterjava.config}" + branch.name = ${scmBranch} + commit.hash = ${buildNumber} } \ No newline at end of file diff --git a/config_templates/version.html b/config_templates/version.html index dd8e39570..3bd740541 100644 --- a/config_templates/version.html +++ b/config_templates/version.html @@ -4,8 +4,12 @@ ${project.name} -${project.groupId}:${project.name}:${project.version}
-configDir=${masterjava.config}
+
+${project.groupId}:${project.artifactId}:${project.version}
+branch.name = ${scmBranch}
+commit.hash = ${buildNumber}
+configDir = ${masterjava.config}
+
Многопоточность. Maven. XML. Веб сервисы. diff --git a/parent/pom.xml b/parent/pom.xml index fe96ac976..1ae91b0d5 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -61,6 +61,23 @@ + + org.codehaus.mojo + buildnumber-maven-plugin + 1.4 + + + validate + + create + + + + + false + false + + @@ -99,4 +116,11 @@ + + scm:git:https://github.com/JavaWebinar/masterjava.git + scm:git:https://github.com/JavaWebinar/masterjava.git + HEAD + https://github.com/vitchurb/masterjava.git + + \ No newline at end of file From 5b1afa66567921bf9912d516ca4a83e1313ea6c8 Mon Sep 17 00:00:00 2001 From: booktest Date: Thu, 14 Mar 2019 10:02:08 +0300 Subject: [PATCH 63/83] Lesson09 - msg ctx authentication --- .../ru/javaops/masterjava/web/AuthUtil.java | 33 +++++++++++++++++++ .../ru/javaops/masterjava/web/WsClient.java | 13 ++++++++ .../masterjava/service/mail/MailWSClient.java | 12 ++++++- .../service/mail/MailServiceImpl.java | 21 ++++++++++++ 4 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 services/common-ws/src/main/java/ru/javaops/masterjava/web/AuthUtil.java diff --git a/services/common-ws/src/main/java/ru/javaops/masterjava/web/AuthUtil.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/AuthUtil.java new file mode 100644 index 000000000..f8fea01c8 --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/AuthUtil.java @@ -0,0 +1,33 @@ +package ru.javaops.masterjava.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/masterjava/web/WsClient.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java index 6ca32585a..fbd18964a 100644 --- a/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java @@ -40,6 +40,19 @@ public T getPort(WebServiceFeature... features) { 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(Throwable t, ExceptionType type) { return (t instanceof WebStateException) ? (WebStateException) t : new WebStateException(t, type); } 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 index e4de96c59..c4e6764a1 100644 --- 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 @@ -5,6 +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.masterjava.web.AuthUtil; import ru.javaops.masterjava.web.WebStateException; import ru.javaops.masterjava.web.WsClient; @@ -16,6 +18,11 @@ @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); + + public static String AUTH_HEADER = AuthUtil.encodeBasicAuthHeader(USER, PASSWORD); static { WS_CLIENT = new WsClient<>(Resources.getResource("wsdl/mailService.wsdl"), @@ -40,7 +47,10 @@ public static GroupResult sendBulk(final Set to, final String subject } private static MailService getPort() { - return WS_CLIENT.getPort(new MTOMFeature(1024)); + MailService port = WS_CLIENT.getPort(new MTOMFeature(1024)); + WsClient.setAuth(port, USER, PASSWORD); + WsClient.setHandler(port, LOGGING_HANDLER); + return port; } public static Set split(String addressees) { 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 index 4737ed7a2..ca1d9b77a 100644 --- 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 @@ -1,10 +1,16 @@ package ru.javaops.masterjava.service.mail; +import ru.javaops.masterjava.web.AuthUtil; import ru.javaops.masterjava.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/" @@ -13,7 +19,22 @@ //@StreamingAttachment(parseEagerly=true, memoryThreshold=40000L) @MTOM public class MailServiceImpl implements MailService { + + @Resource + private WebServiceContext wsContext; + public String sendToGroup(Set to, Set cc, String subject, String body, List attachments) 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, attachments); } From 8a3e2879145ec2fa94850b363c6b060360a0dd3c Mon Sep 17 00:00:00 2001 From: booktest Date: Thu, 14 Mar 2019 10:05:09 +0300 Subject: [PATCH 64/83] Lesson09 - Added logging handlers and refactor method getInputStream in Attachments --- .../ru/javaops/masterjava/web/WsClient.java | 3 + .../web/handler/SoapBaseHandler.java | 23 +++ .../web/handler/SoapLoggingHandlers.java | 143 ++++++++++++++++++ services/mail-api/pom.xml | 5 + .../masterjava/service/mail/MailWSClient.java | 1 + .../service/mail/util/Attachments.java | 9 +- .../service/mail/MailServiceImpl.java | 1 + .../src/main/resources/mailWsHandlers.xml | 8 + 8 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapBaseHandler.java create mode 100644 services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapLoggingHandlers.java create mode 100644 services/mail-service/src/main/resources/mailWsHandlers.xml diff --git a/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java index fbd18964a..e65de940a 100644 --- a/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java @@ -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 { diff --git a/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapBaseHandler.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapBaseHandler.java new file mode 100644 index 000000000..5cd1622b0 --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapBaseHandler.java @@ -0,0 +1,23 @@ +package ru.javaops.masterjava.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/masterjava/web/handler/SoapLoggingHandlers.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapLoggingHandlers.java new file mode 100644 index 000000000..7af6cc145 --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapLoggingHandlers.java @@ -0,0 +1,143 @@ +package ru.javaops.masterjava.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() { + super(Level.INFO); + } + + @Override + protected boolean isRequest(boolean isOutbound) { + return !isOutbound; + } + } +} diff --git a/services/mail-api/pom.xml b/services/mail-api/pom.xml index 3ff36485e..6d506ec1c 100644 --- a/services/mail-api/pom.xml +++ b/services/mail-api/pom.xml @@ -33,6 +33,11 @@ common-ws ${project.version} + + commons-io + commons-io + 2.6 + \ 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 index c4e6764a1..7f0437f73 100644 --- 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 @@ -9,6 +9,7 @@ import ru.javaops.masterjava.web.AuthUtil; import ru.javaops.masterjava.web.WebStateException; import ru.javaops.masterjava.web.WsClient; +import ru.javaops.masterjava.web.handler.SoapLoggingHandlers; import javax.xml.namespace.QName; import javax.xml.ws.soap.MTOMFeature; diff --git a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java index f8dddeef3..5b0b0d0f5 100644 --- a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java @@ -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.Attachment; import javax.activation.DataHandler; @@ -14,6 +15,7 @@ public static Attachment getAttachment(String name, InputStream inputStream) { return new Attachment(name, new DataHandler(new InputStreamDataSource(inputStream))); } + // http://stackoverflow.com/questions/2830561/how-to-convert-an-inputstream-to-a-datahandler // http://stackoverflow.com/a/10783565/548473 @AllArgsConstructor private static class InputStreamDataSource implements DataSource { @@ -21,12 +23,7 @@ private static class InputStreamDataSource implements DataSource { @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 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 index ca1d9b77a..ae4bab469 100644 --- 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 @@ -18,6 +18,7 @@ ) //@StreamingAttachment(parseEagerly=true, memoryThreshold=40000L) @MTOM +@HandlerChain(file = "mailWsHandlers.xml") public class MailServiceImpl implements MailService { @Resource 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..d1f3ed3fb --- /dev/null +++ b/services/mail-service/src/main/resources/mailWsHandlers.xml @@ -0,0 +1,8 @@ + + + + SoapLoggingHandler + ru.javaops.masterjava.web.handler.SoapLoggingHandlers$ServerHandler + + + \ No newline at end of file From 058915393aa1ccfcb9bdd83ebd53e8e0048959eb Mon Sep 17 00:00:00 2001 From: booktest Date: Thu, 14 Mar 2019 10:06:21 +0300 Subject: [PATCH 65/83] Lesson09 - prepare HW9 --- common/src/main/resources/hosts.conf | 8 +++++++- .../ru/javaops/masterjava/web/Statistics.java | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 services/common-ws/src/main/java/ru/javaops/masterjava/web/Statistics.java diff --git a/common/src/main/resources/hosts.conf b/common/src/main/resources/hosts.conf index b30de2893..a8ab5821a 100644 --- a/common/src/main/resources/hosts.conf +++ b/common/src/main/resources/hosts.conf @@ -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_settings/masterjava/config/hosts.conf") diff --git a/services/common-ws/src/main/java/ru/javaops/masterjava/web/Statistics.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/Statistics.java new file mode 100644 index 000000000..32d11f9bb --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/Statistics.java @@ -0,0 +1,18 @@ +package ru.javaops.masterjava.web; + +import lombok.extern.slf4j.Slf4j; + +@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 + + } +} From b58930b580fd1eb12f3fbb2962549bfab700f901 Mon Sep 17 00:00:00 2001 From: booktest Date: Sun, 17 Mar 2019 14:44:16 +0300 Subject: [PATCH 66/83] Lesson10 - HW9 Added handlers --- .../ru/javaops/masterjava/web/WsClient.java | 7 ++- .../web/handler/SoapBaseHandler.java | 1 + .../web/handler/SoapLoggingHandlers.java | 6 +++ .../handler/SoapServerSecurityHandler.java | 43 +++++++++++++++++++ .../web/handler/SoapStatisticHandler.java | 32 ++++++++++++++ .../masterjava/service/mail/MailWSClient.java | 2 - .../masterjava/service/mail/MailHandlers.java | 18 ++++++++ .../service/mail/MailServiceImpl.java | 16 +++---- 8 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapServerSecurityHandler.java create mode 100644 services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapStatisticHandler.java create mode 100644 services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailHandlers.java diff --git a/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java index e65de940a..c7a1140ee 100644 --- a/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java @@ -31,7 +31,12 @@ public WsClient(URL wsdlUrl, QName qname, Class serviceClass) { } public void init(String host, String endpointAddress) { - this.endpointAddress = HOSTS.getString(host) + 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) diff --git a/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapBaseHandler.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapBaseHandler.java index 5cd1622b0..75eb667a0 100644 --- a/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapBaseHandler.java +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapBaseHandler.java @@ -9,6 +9,7 @@ public abstract class SoapBaseHandler implements MessageHandler { + @Override public Set getHeaders() { return null; } diff --git a/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapLoggingHandlers.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapLoggingHandlers.java index 7af6cc145..7f53e1337 100644 --- a/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapLoggingHandlers.java +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapLoggingHandlers.java @@ -56,10 +56,12 @@ public void handleMessage(MessageHandlerContext mhc, boolean isRequest) { ERROR { private static final String REQUEST_MSG = "REQUEST_MSG"; + @Override public void handleFault(MessageHandlerContext context) { log.error("Fault SOAP request:\n" + getMessageText(((Message) context.get(REQUEST_MSG)))); } + @Override public void handleMessage(MessageHandlerContext context, boolean isRequest) { if (isRequest) { context.put(REQUEST_MSG, context.getMessage().copy()); @@ -67,20 +69,24 @@ public void handleMessage(MessageHandlerContext context, boolean isRequest) { } }, INFO { + @Override public void handleFault(MessageHandlerContext context) { ERROR.handleFault(context); } + @Override public void handleMessage(MessageHandlerContext context, boolean isRequest) { ERROR.handleMessage(context, isRequest); log.info((isRequest ? "SOAP request: " : "SOAP response: ") + context.getMessage().getPayloadLocalPart()); } }, DEBUG { + @Override public void handleFault(MessageHandlerContext context) { log.error("Fault SOAP message:\n" + getMessageText(context.getMessage().copy())); } + @Override public void handleMessage(MessageHandlerContext context, boolean isRequest) { log.info((isRequest ? "SOAP request:\n" : "SOAP response:\n") + getMessageText(context.getMessage().copy())); } diff --git a/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapServerSecurityHandler.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapServerSecurityHandler.java new file mode 100644 index 000000000..2fae09072 --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapServerSecurityHandler.java @@ -0,0 +1,43 @@ +package ru.javaops.masterjava.web.handler; + +import com.sun.xml.ws.api.handler.MessageHandlerContext; +import lombok.extern.slf4j.Slf4j; +import ru.javaops.masterjava.web.AuthUtil; + +import javax.xml.ws.handler.MessageContext; +import java.util.List; +import java.util.Map; + +import static ru.javaops.masterjava.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/masterjava/web/handler/SoapStatisticHandler.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapStatisticHandler.java new file mode 100644 index 000000000..07dd87a41 --- /dev/null +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapStatisticHandler.java @@ -0,0 +1,32 @@ +package ru.javaops.masterjava.web.handler; + +import com.sun.xml.ws.api.handler.MessageHandlerContext; +import ru.javaops.masterjava.web.Statistics; + +public class SoapStatisticHandler extends SoapBaseHandler { + + private static final String PAYLOAD = "PAYLOAD"; + private static final String START_TIME = "START_TIME"; + + @Override + 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; + } + + @Override + 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/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/MailWSClient.java index 7f0437f73..3335deb47 100644 --- 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 @@ -23,8 +23,6 @@ public class MailWSClient { public static final String PASSWORD = "password"; private static final SoapLoggingHandlers.ClientHandler LOGGING_HANDLER = new SoapLoggingHandlers.ClientHandler(Level.DEBUG); - public static String AUTH_HEADER = AuthUtil.encodeBasicAuthHeader(USER, PASSWORD); - static { WS_CLIENT = new WsClient<>(Resources.getResource("wsdl/mailService.wsdl"), new QName("http://mail.javaops.ru/", "MailServiceImplService"), 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..6dc9987dd --- /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.masterjava.web.handler.SoapLoggingHandlers; +import ru.javaops.masterjava.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/MailServiceImpl.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/MailServiceImpl.java index ae4bab469..f1c9510a2 100644 --- 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 @@ -1,16 +1,11 @@ package ru.javaops.masterjava.service.mail; -import ru.javaops.masterjava.web.AuthUtil; import ru.javaops.masterjava.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/" @@ -21,21 +16,24 @@ @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 attachments) 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, attachments); } From 6b79f81daed5cdb638d3a3f30f3b8ccf043cb796 Mon Sep 17 00:00:00 2001 From: booktest Date: Sun, 17 Mar 2019 14:46:56 +0300 Subject: [PATCH 67/83] Lesson10 - HW9 host config with default.conf --- common/src/main/resources/defaults.conf | 4 ++ common/src/main/resources/hosts.conf | 4 -- config_templates/hosts.conf | 7 +++ .../ru/javaops/masterjava/web/WsClient.java | 45 ++++++++++++++++++- .../web/handler/SoapLoggingHandlers.java | 4 +- .../masterjava/service/mail/MailWSClient.java | 15 +++---- .../src/main/resources/mailWsHandlers.xml | 12 ++++- 7 files changed, 71 insertions(+), 20 deletions(-) create mode 100644 common/src/main/resources/defaults.conf create mode 100644 config_templates/hosts.conf 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 index a8ab5821a..374724da8 100644 --- a/common/src/main/resources/hosts.conf +++ b/common/src/main/resources/hosts.conf @@ -1,10 +1,6 @@ hosts { mail { endpoint = "http://localhost:8080" - debug.client = DEBUG - debug.server = INFO - user = "user" - password = "password" } } include file("/apps_settings/masterjava/config/hosts.conf") diff --git a/config_templates/hosts.conf b/config_templates/hosts.conf new file mode 100644 index 000000000..70f227d63 --- /dev/null +++ b/config_templates/hosts.conf @@ -0,0 +1,7 @@ +hosts { + mail { + user = "user" + password = "password" + server.debugLevel = DEBUG + } +} \ No newline at end of file diff --git a/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java index c7a1140ee..2631d0067 100644 --- a/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/WsClient.java @@ -1,8 +1,10 @@ package ru.javaops.masterjava.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.masterjava.web.handler.SoapLoggingHandlers; import javax.xml.namespace.QName; import javax.xml.ws.Binding; @@ -19,7 +21,40 @@ public class WsClient { 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"); @@ -44,7 +79,13 @@ 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); + 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; } diff --git a/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapLoggingHandlers.java b/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapLoggingHandlers.java index 7f53e1337..2ed298f1b 100644 --- a/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapLoggingHandlers.java +++ b/services/common-ws/src/main/java/ru/javaops/masterjava/web/handler/SoapLoggingHandlers.java @@ -137,8 +137,8 @@ protected boolean isRequest(boolean isOutbound) { public static class ServerHandler extends SoapLoggingHandlers { - public ServerHandler() { - super(Level.INFO); + public ServerHandler(Level loggingLevel) { + super(loggingLevel); } @Override 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 index 3335deb47..fc52a08e7 100644 --- 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 @@ -5,11 +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.masterjava.web.AuthUtil; import ru.javaops.masterjava.web.WebStateException; import ru.javaops.masterjava.web.WsClient; -import ru.javaops.masterjava.web.handler.SoapLoggingHandlers; import javax.xml.namespace.QName; import javax.xml.ws.soap.MTOMFeature; @@ -19,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"), @@ -46,14 +40,15 @@ public static GroupResult sendBulk(final Set to, final String subject } 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(); + } } diff --git a/services/mail-service/src/main/resources/mailWsHandlers.xml b/services/mail-service/src/main/resources/mailWsHandlers.xml index d1f3ed3fb..ecaaf04a9 100644 --- a/services/mail-service/src/main/resources/mailWsHandlers.xml +++ b/services/mail-service/src/main/resources/mailWsHandlers.xml @@ -1,8 +1,16 @@ - SoapLoggingHandler - ru.javaops.masterjava.web.handler.SoapLoggingHandlers$ServerHandler + MailLoggingHandler + ru.javaops.masterjava.service.mail.MailHandlers$LoggingHandler + + + SoapStatisticHandler + ru.javaops.masterjava.web.handler.SoapStatisticHandler + + + MailSecurityHandler + ru.javaops.masterjava.service.mail.MailHandlers$SecurityHandler \ No newline at end of file From bf4f8ed3f78bdc787b0a94710f94966bc69c2900 Mon Sep 17 00:00:00 2001 From: booktest Date: Sun, 17 Mar 2019 17:43:30 +0300 Subject: [PATCH 68/83] Lesson10 - JAX RS --- services/mail-service/pom.xml | 40 +++++++++++++++++++ .../masterjava/service/mail/rest/MailRS.java | 32 +++++++++++++++ .../service/mail/rest/MailRestConfig.java | 17 ++++++++ web/common-web/pom.xml | 11 ++++- .../main/webapp/WEB-INF/templates/users.html | 8 +++- 5 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRS.java create mode 100644 services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRestConfig.java diff --git a/services/mail-service/pom.xml b/services/mail-service/pom.xml index 23022ac30..3b87e0781 100644 --- a/services/mail-service/pom.xml +++ b/services/mail-service/pom.xml @@ -16,6 +16,10 @@ war Mail Service + + 2.26 + + mail @@ -66,5 +70,41 @@ test-jar test + + + org.glassfish.jersey.containers + jersey-container-servlet + ${jersey.version} + + + + + org.glassfish.jersey.inject + jersey-hk2 + ${jersey.version} + + + org.javassist + javassist + + + + + org.glassfish.jersey.media + jersey-media-moxy + ${jersey.version} + + + org.glassfish.jersey.ext + jersey-bean-validation + ${jersey.version} + + + + org.apache.activemq + activemq-all + ${activemq.version} + provided + \ No newline at end of file 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..24cea6645 --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRS.java @@ -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.masterjava.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 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..7f8a6aabe --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/rest/MailRestConfig.java @@ -0,0 +1,17 @@ +package ru.javaops.masterjava.service.mail.rest; + +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"); + } +} \ No newline at end of file diff --git a/web/common-web/pom.xml b/web/common-web/pom.xml index 37ca1f642..d4ce0260e 100644 --- a/web/common-web/pom.xml +++ b/web/common-web/pom.xml @@ -31,13 +31,22 @@ org.thymeleaf thymeleaf - 3.0.8.RELEASE + 3.0.9.RELEASE org.slf4j slf4j-api + + org.javassist + javassist + + + org.javassist + javassist + 3.22.0-GA + \ 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 index 4d54f8e2d..6c1c29321 100644 --- a/web/webapp/src/main/webapp/WEB-INF/templates/users.html +++ b/web/webapp/src/main/webapp/WEB-INF/templates/users.html @@ -48,6 +48,12 @@


 
 

From 6cbeb5eb05e2efa79403771dd2f7a4d7625952fc Mon Sep 17 00:00:00 2001
From: booktest 
Date: Sun, 17 Mar 2019 17:44:06 +0300
Subject: [PATCH 69/83] Lesson10 - jersey logging

---
 parent-web/pom.xml                            |  5 ++
 parent/pom.xml                                | 15 ++++++
 .../src/main/resources/logback.xml            | 46 +++++++++++++++++++
 3 files changed, 66 insertions(+)
 create mode 100644 services/mail-service/src/main/resources/logback.xml

diff --git a/parent-web/pom.xml b/parent-web/pom.xml
index f6a907233..7af328083 100644
--- a/parent-web/pom.xml
+++ b/parent-web/pom.xml
@@ -15,6 +15,11 @@
     pom
     1.0-SNAPSHOT
     Parent Web
+
+    
+        true
+        5.15.2
+    
 
     
         
diff --git a/parent/pom.xml b/parent/pom.xml
index 1ae91b0d5..74116a925 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -101,6 +101,21 @@
             provided
         
+ + org.slf4j + jul-to-slf4j + ${slf4j.version} + + + + junit 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..b96668bb1 --- /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 From 195a4e3b55363ddbf84eab4ba20e23fde05113d4 Mon Sep 17 00:00:00 2001 From: booktest Date: Sun, 17 Mar 2019 17:44:53 +0300 Subject: [PATCH 70/83] Lesson10 - JMS To make the JNDI application work with JMS, configure Tomcat: Copy: - config_templates/context.xml in $ TOMCAT_HOME/conf - $ MAVEN_REPO (~/.m2)/org/apache/activemq/activemq-all/5.15.2/activemq-all.jar in $ TOMCAT_HOME / lib --- config_templates/context.xml | 28 +++++++ .../mail/listeners/JmsMailListener.java | 63 ++++++++++++++++ web/webapp/pom.xml | 7 ++ .../masterjava/webapp/JmsSendServlet.java | 73 +++++++++++++++++++ ...{SendServlet.java => SoapSendServlet.java} | 4 +- .../main/webapp/WEB-INF/templates/users.html | 49 +++++++++---- 6 files changed, 209 insertions(+), 15 deletions(-) create mode 100644 services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/listeners/JmsMailListener.java create mode 100644 web/webapp/src/main/java/ru/javaops/masterjava/webapp/JmsSendServlet.java rename web/webapp/src/main/java/ru/javaops/masterjava/webapp/{SendServlet.java => SoapSendServlet.java} (95%) diff --git a/config_templates/context.xml b/config_templates/context.xml index 1ee3c15ac..9c2471d00 100644 --- a/config_templates/context.xml +++ b/config_templates/context.xml @@ -45,5 +45,33 @@ username="postgres" password="root" url="jdbc:postgresql://localhost:5432/masterjava"/> + + + + + + + + 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..50b61901b --- /dev/null +++ b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/listeners/JmsMailListener.java @@ -0,0 +1,63 @@ +package ru.javaops.masterjava.service.mail.listeners; + +import lombok.extern.slf4j.Slf4j; + +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(); + QueueConnectionFactory connectionFactory = + (QueueConnectionFactory) initCtx.lookup("java:comp/env/jms/ConnectionFactory"); + 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(); + // TODO implement mail sending + if (m instanceof TextMessage) { + TextMessage tm = (TextMessage) m; + String text = tm.getText(); + log.info("Received TextMessage with text '{}'", text); + } + } + } 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/web/webapp/pom.xml b/web/webapp/pom.xml index e892e75e0..a2f5bf755 100644 --- a/web/webapp/pom.xml +++ b/web/webapp/pom.xml @@ -31,5 +31,12 @@ mail-api ${project.version} + + + org.apache.activemq + activemq-client + provided + ${activemq.version} +
\ 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..e92638597 --- /dev/null +++ b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/JmsSendServlet.java @@ -0,0 +1,73 @@ +package ru.javaops.masterjava.webapp; + +import lombok.extern.slf4j.Slf4j; + +import javax.jms.*; +import javax.naming.InitialContext; +import javax.servlet.ServletConfig; +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 java.lang.IllegalStateException; + +@WebServlet("/sendJms") +@Slf4j +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 { + String result; + try { + log.info("Start sending"); + req.setCharacterEncoding("UTF-8"); + resp.setCharacterEncoding("UTF-8"); + String users = req.getParameter("users"); + String subject = req.getParameter("subject"); + String body = req.getParameter("body"); + result = sendJms(users, subject, body); + log.info("Processing finished with result: {}", result); + } catch (Exception e) { + log.error("Processing failed", e); + result = e.toString(); + } + resp.getWriter().write(result); + } + + private synchronized String sendJms(String users, String subject, String body) throws JMSException { + TextMessage testMessage = session.createTextMessage(); + testMessage.setText(subject); + producer.send(testMessage); + return "Successfully sent JMS message"; + } +} \ No newline at end of file diff --git a/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SoapSendServlet.java similarity index 95% rename from web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java rename to web/webapp/src/main/java/ru/javaops/masterjava/webapp/SoapSendServlet.java index b6ac85969..b751d2c5c 100644 --- a/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SendServlet.java +++ b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SoapSendServlet.java @@ -15,10 +15,10 @@ import javax.servlet.http.Part; import java.io.IOException; -@WebServlet("/send") +@WebServlet("/sendSoap") @Slf4j @MultipartConfig -public class SendServlet extends HttpServlet { +public class SoapSendServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String result; diff --git a/web/webapp/src/main/webapp/WEB-INF/templates/users.html b/web/webapp/src/main/webapp/WEB-INF/templates/users.html index 6c1c29321..0e9f0a049 100644 --- a/web/webapp/src/main/webapp/WEB-INF/templates/users.html +++ b/web/webapp/src/main/webapp/WEB-INF/templates/users.html @@ -42,6 +42,11 @@

+

+ SOAP
+ REST
+ JMS
+

@@ -60,21 +65,39 @@ return this.value; }).get(); - // https://stackoverflow.com/a/5976031/548473 - var fd = new FormData(); - fd.append('users', users); - fd.append('subject', $("#subject").val()); - fd.append('body', $("#body").val()); - var attach = $('#attach')[0].files[0]; - if (attach) fd.append('attach', attach); + if (users.length === 0) { + $('#result').html("Addresses are not selected"); + return; + } + + var param; + if (transport === "SOAP") { + // https://stackoverflow.com/a/5976031/548473 + var data = new FormData(); + data.append('users', users); + data.append('subject', $("#subject").val()); + data.append('body', $("#body").val()); + var attach = $('#attach')[0].files[0]; + if (attach) data.append('attach', attach); + param = { + url: "sendSoap", + data: data, + contentType: false, + processData: false + }; + } else if (transport === "REST" || transport === "JMS") { + param = { + url: (transport === "REST" ? "/mail/rest/send" : "sendJms"), + data: "users=" + users + "&subject=" + $("#subject").val() + "&body=" + $("#body").val(), + contentType: "application/x-www-form-urlencoded" + }; + } // https://stackoverflow.com/a/22213543/548473 - $.post({ - url: 'send', - data: fd, - contentType: false, - processData: false - }).done(function (result) { + $.post(param).done(function (result) { + if (typeof result === "object") { + result = JSON.stringify(result) + } $('#result').html(result); }).fail(function (result) { $('#result').html(result.responseText); From 615b61cea07a931b28c03c67810bf590a7fab3b2 Mon Sep 17 00:00:00 2001 From: booktest Date: Sun, 17 Mar 2019 18:05:45 +0300 Subject: [PATCH 71/83] Lesson10 - tomcat auth --- .../src/main/resources/mailWsHandlers.xml | 4 --- .../src/main/webapp/WEB-INF/web.xml | 25 +++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 services/mail-service/src/main/webapp/WEB-INF/web.xml diff --git a/services/mail-service/src/main/resources/mailWsHandlers.xml b/services/mail-service/src/main/resources/mailWsHandlers.xml index ecaaf04a9..24d10d5f1 100644 --- a/services/mail-service/src/main/resources/mailWsHandlers.xml +++ b/services/mail-service/src/main/resources/mailWsHandlers.xml @@ -8,9 +8,5 @@ SoapStatisticHandler ru.javaops.masterjava.web.handler.SoapStatisticHandler - - MailSecurityHandler - ru.javaops.masterjava.service.mail.MailHandlers$SecurityHandler - \ 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 + + From 0a2b0303d93f325e644379745545c28a64f54bb4 Mon Sep 17 00:00:00 2001 From: booktest Date: Sun, 17 Mar 2019 18:07:28 +0300 Subject: [PATCH 72/83] #An example of configs to put in apps_settings --- config_templates/hosts.conf | 4 ++-- config_templates/mail.conf | 4 ++++ config_templates/persist.conf | 5 +++++ 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 config_templates/mail.conf create mode 100644 config_templates/persist.conf diff --git a/config_templates/hosts.conf b/config_templates/hosts.conf index 70f227d63..bdbb25465 100644 --- a/config_templates/hosts.conf +++ b/config_templates/hosts.conf @@ -1,7 +1,7 @@ hosts { mail { - user = "user" - password = "password" + user = "tomcat" + password = "tomcat" server.debugLevel = DEBUG } } \ No newline at end of file diff --git a/config_templates/mail.conf b/config_templates/mail.conf new file mode 100644 index 000000000..da36f087c --- /dev/null +++ b/config_templates/mail.conf @@ -0,0 +1,4 @@ +mail { + username: "immmus@yandex.ru" + password: "123456" +} diff --git a/config_templates/persist.conf b/config_templates/persist.conf new file mode 100644 index 000000000..c0a58f926 --- /dev/null +++ b/config_templates/persist.conf @@ -0,0 +1,5 @@ +db { +url = "jdbc:postgresql://localhost:5432/masterjava" +user = postgres +password = root +} \ No newline at end of file From 558954cdf70deacd08293b0a51dfec0dfa34d321 Mon Sep 17 00:00:00 2001 From: booktest Date: Wed, 20 Mar 2019 15:42:51 +0300 Subject: [PATCH 73/83] fixBug with context.xml --- config_templates/context.xml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/config_templates/context.xml b/config_templates/context.xml index 9c2471d00..05f8b05c4 100644 --- a/config_templates/context.xml +++ b/config_templates/context.xml @@ -60,18 +60,4 @@ factory="org.apache.activemq.jndi.JNDIReferenceFactory" physicalName="MAIL.QUEUE"/> - - - - From beb12fb36bc9d09ececec1154143040b3815ca04 Mon Sep 17 00:00:00 2001 From: booktest Date: Wed, 20 Mar 2019 17:31:24 +0300 Subject: [PATCH 74/83] Lesson11 - HW10 jersey attach --- .../service/mail/util/Attachments.java | 11 +++--- services/mail-service/pom.xml | 11 ++++++ .../masterjava/service/mail/rest/MailRS.java | 36 ++++++++++++++++--- .../service/mail/rest/MailRestConfig.java | 2 ++ .../main/webapp/WEB-INF/templates/users.html | 8 ++--- 5 files changed, 55 insertions(+), 13 deletions(-) diff --git a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java index 5b0b0d0f5..1b5065716 100644 --- a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java +++ b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java @@ -18,26 +18,29 @@ public static Attachment getAttachment(String name, InputStream inputStream) { // http://stackoverflow.com/questions/2830561/how-to-convert-an-inputstream-to-a-datahandler // http://stackoverflow.com/a/10783565/548473 @AllArgsConstructor - private static class InputStreamDataSource implements DataSource { + private static class InputStreamDataSource implements ProxyDataSource { private InputStream inputStream; @Override public InputStream getInputStream() throws IOException { return new CloseShieldInputStream(inputStream); } + } + + public interface ProxyDataSource extends DataSource { @Override - public OutputStream getOutputStream() throws IOException { + default OutputStream getOutputStream() { throw new UnsupportedOperationException("Not implemented"); } @Override - public String getContentType() { + default String getContentType() { return "application/octet-stream"; } @Override - public String getName() { + default String getName() { return ""; } } diff --git a/services/mail-service/pom.xml b/services/mail-service/pom.xml index 3b87e0781..36d5915e8 100644 --- a/services/mail-service/pom.xml +++ b/services/mail-service/pom.xml @@ -99,6 +99,17 @@ jersey-bean-validation ${jersey.version} + + org.glassfish.jersey.media + jersey-media-multipart + ${jersey.version} + + + org.jvnet.mimepull + mimepull + + + org.apache.activemq 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 index 24cea6645..78d79affa 100644 --- 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 @@ -1,15 +1,24 @@ 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.Attachment; import ru.javaops.masterjava.service.mail.GroupResult; import ru.javaops.masterjava.service.mail.MailServiceExecutor; import ru.javaops.masterjava.service.mail.MailWSClient; +import ru.javaops.masterjava.service.mail.util.Attachments; import ru.javaops.masterjava.web.WebStateException; +import javax.activation.DataHandler; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; -import java.util.Collections; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.List; @Path("/") public class MailRS { @@ -23,10 +32,27 @@ public String 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 { + @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 { - return MailServiceExecutor.sendBulk(MailWSClient.split(users), subject, body, Collections.emptyList()); + final List attachments; + if (attachBodyPart == null) { + attachments = ImmutableList.of(); + } else { + try { + String attachName = attachBodyPart.getContentDisposition().getFileName(); +// UTF-8 encoding workaround: https://java.net/jira/browse/JERSEY-3032 + String utf8name = new String(attachName.getBytes("ISO8859_1"), StandardCharsets.UTF_8); + BodyPartEntity bodyPartEntity = ((BodyPartEntity) attachBodyPart.getEntity()); + + attachments = ImmutableList.of(new Attachment(utf8name, new DataHandler((Attachments.ProxyDataSource) bodyPartEntity::getInputStream))); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + } + return MailServiceExecutor.sendBulk(MailWSClient.split(users), subject, body, attachments); } } \ 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 index 7f8a6aabe..0a1fa5cc5 100644 --- 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 @@ -1,5 +1,6 @@ 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; @@ -13,5 +14,6 @@ public MailRestConfig() { // 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/web/webapp/src/main/webapp/WEB-INF/templates/users.html b/web/webapp/src/main/webapp/WEB-INF/templates/users.html index 0e9f0a049..264339bf8 100644 --- a/web/webapp/src/main/webapp/WEB-INF/templates/users.html +++ b/web/webapp/src/main/webapp/WEB-INF/templates/users.html @@ -71,7 +71,7 @@ } var param; - if (transport === "SOAP") { + if (transport === "SOAP" || transport === "REST") { // https://stackoverflow.com/a/5976031/548473 var data = new FormData(); data.append('users', users); @@ -80,14 +80,14 @@ var attach = $('#attach')[0].files[0]; if (attach) data.append('attach', attach); param = { - url: "sendSoap", + url: (transport === "REST" ? "/mail/rest/send" : "sendSoap"), data: data, contentType: false, processData: false }; - } else if (transport === "REST" || transport === "JMS") { + } else if (transport === "JMS") { param = { - url: (transport === "REST" ? "/mail/rest/send" : "sendJms"), + url: "sendJms", data: "users=" + users + "&subject=" + $("#subject").val() + "&body=" + $("#body").val(), contentType: "application/x-www-form-urlencoded" }; From 1b5bd2df41322fa5e4a0f199fe2c0a57ac23ee8c Mon Sep 17 00:00:00 2001 From: booktest Date: Wed, 20 Mar 2019 22:38:30 +0300 Subject: [PATCH 75/83] Lesson11 - HW10 JMS attach --- .../masterjava/service/mail/MailWSClient.java | 8 --- .../service/mail/util/Attachments.java | 47 ------------- .../service/mail/util/MailUtils.java | 70 +++++++++++++++++++ .../service/mail/MailServiceExecutor.java | 17 +++++ .../mail/listeners/JmsMailListener.java | 18 +++-- .../masterjava/service/mail/rest/MailRS.java | 7 +- .../masterjava/webapp/JmsSendServlet.java | 29 +++++--- .../masterjava/webapp/SoapSendServlet.java | 9 ++- .../main/webapp/WEB-INF/templates/users.html | 48 +++++-------- 9 files changed, 144 insertions(+), 109 deletions(-) delete mode 100644 services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java create mode 100644 services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/MailUtils.java 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 index fc52a08e7..f7998aa32 100644 --- 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 @@ -1,8 +1,5 @@ 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.WebStateException; @@ -43,11 +40,6 @@ 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)); - } - public static WsClient.HostConfig getHostConfig() { return WS_CLIENT.getHostConfig(); } diff --git a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java b/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java deleted file mode 100644 index 1b5065716..000000000 --- a/services/mail-api/src/main/java/ru/javaops/masterjava/service/mail/util/Attachments.java +++ /dev/null @@ -1,47 +0,0 @@ -package ru.javaops.masterjava.service.mail.util; - -import lombok.AllArgsConstructor; -import org.apache.commons.io.input.CloseShieldInputStream; -import ru.javaops.masterjava.service.mail.Attachment; - -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 Attachment getAttachment(String name, InputStream inputStream) { - return new Attachment(name, new DataHandler(new InputStreamDataSource(inputStream))); - } - - // http://stackoverflow.com/questions/2830561/how-to-convert-an-inputstream-to-a-datahandler - // http://stackoverflow.com/a/10783565/548473 - @AllArgsConstructor - private static class InputStreamDataSource implements ProxyDataSource { - private InputStream inputStream; - - @Override - public InputStream getInputStream() throws IOException { - return new CloseShieldInputStream(inputStream); - } - } - - public interface ProxyDataSource extends DataSource { - - @Override - default OutputStream getOutputStream() { - 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/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..a0783dbcf --- /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.IOUtils; +import org.apache.commons.io.input.CloseShieldInputStream; +import ru.javaops.masterjava.service.mail.Addressee; +import ru.javaops.masterjava.service.mail.Attachment; + +import javax.activation.DataHandler; +import javax.activation.DataSource; +import java.io.*; +import java.util.Set; + +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 @NotNull String users; + private String subject; + private @NotNull String body; + private String attachName; + private byte[] attachData; + } + + public static MailObject getMailObject(String users, String subject, String body, String name, InputStream is) { + try { + return new MailObject(users, subject, body, name, is == null ? null : IOUtils.toByteArray(is)); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + public static Attachment getAttachment(String name, byte[] attachData) { + return new Attachment(name, new DataHandler((ProxyDataSource) () -> new ByteArrayInputStream(attachData))); + } + + public static Attachment getAttachment(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 Attachment(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-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 index 1746e1a5d..d30171ce0 100644 --- 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 @@ -1,8 +1,11 @@ package ru.javaops.masterjava.service.mail; +import com.google.common.collect.ImmutableList; import lombok.extern.slf4j.Slf4j; import one.util.streamex.StreamEx; import ru.javaops.masterjava.ExceptionType; +import ru.javaops.masterjava.service.mail.util.MailUtils; +import ru.javaops.masterjava.service.mail.util.MailUtils.MailObject; import ru.javaops.masterjava.web.WebStateException; import ru.javaops.masterjava.web.WsClient; @@ -69,4 +72,18 @@ private void cancel(String cause, Throwable t) throws WebStateException { } }.call(); } + + 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(), + ImmutableList.of(MailUtils.getAttachment(mailObject.getAttachName(), mailObject.getAttachData()))); + } 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/listeners/JmsMailListener.java b/services/mail-service/src/main/java/ru/javaops/masterjava/service/mail/listeners/JmsMailListener.java index 50b61901b..7fe377c8c 100644 --- 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 @@ -1,6 +1,9 @@ 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; import javax.jms.*; import javax.naming.InitialContext; @@ -18,8 +21,9 @@ public class JmsMailListener implements ServletContextListener { public void contextInitialized(ServletContextEvent sce) { try { InitialContext initCtx = new InitialContext(); - QueueConnectionFactory connectionFactory = - (QueueConnectionFactory) initCtx.lookup("java:comp/env/jms/ConnectionFactory"); + 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"); @@ -30,11 +34,11 @@ public void contextInitialized(ServletContextEvent sce) { try { while (!Thread.interrupted()) { Message m = receiver.receive(); - // TODO implement mail sending - if (m instanceof TextMessage) { - TextMessage tm = (TextMessage) m; - String text = tm.getText(); - log.info("Received TextMessage with text '{}'", text); + if (m instanceof ObjectMessage) { + ObjectMessage om = (ObjectMessage) m; + MailUtils.MailObject mailObject = (MailUtils.MailObject) om.getObject(); + log.info("Received MailObject {}", mailObject); + MailServiceExecutor.sendAsync(mailObject); } } } catch (Exception e) { 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 index 78d79affa..6e3a7f47b 100644 --- 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 @@ -9,8 +9,7 @@ import ru.javaops.masterjava.service.mail.Attachment; import ru.javaops.masterjava.service.mail.GroupResult; import ru.javaops.masterjava.service.mail.MailServiceExecutor; -import ru.javaops.masterjava.service.mail.MailWSClient; -import ru.javaops.masterjava.service.mail.util.Attachments; +import ru.javaops.masterjava.service.mail.util.MailUtils; import ru.javaops.masterjava.web.WebStateException; import javax.activation.DataHandler; @@ -48,11 +47,11 @@ public GroupResult send(@NotBlank @FormDataParam("users") String users, String utf8name = new String(attachName.getBytes("ISO8859_1"), StandardCharsets.UTF_8); BodyPartEntity bodyPartEntity = ((BodyPartEntity) attachBodyPart.getEntity()); - attachments = ImmutableList.of(new Attachment(utf8name, new DataHandler((Attachments.ProxyDataSource) bodyPartEntity::getInputStream))); + attachments = ImmutableList.of(new Attachment(utf8name, new DataHandler((MailUtils.ProxyDataSource) bodyPartEntity::getInputStream))); } catch (UnsupportedEncodingException e) { throw new IllegalStateException(e); } } - return MailServiceExecutor.sendBulk(MailWSClient.split(users), subject, body, attachments); + return MailServiceExecutor.sendBulk(MailUtils.split(users), subject, body, attachments); } } \ 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 index e92638597..f9d28be23 100644 --- a/web/webapp/src/main/java/ru/javaops/masterjava/webapp/JmsSendServlet.java +++ b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/JmsSendServlet.java @@ -1,20 +1,25 @@ package ru.javaops.masterjava.webapp; import lombok.extern.slf4j.Slf4j; +import ru.javaops.masterjava.service.mail.util.MailUtils; +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 javax.servlet.http.Part; import java.io.IOException; import java.lang.IllegalStateException; @WebServlet("/sendJms") @Slf4j +@MultipartConfig public class JmsSendServlet extends HttpServlet { private Connection connection; private Session session; @@ -46,16 +51,22 @@ public void destroy() { } @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { String result; try { log.info("Start sending"); req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); - String users = req.getParameter("users"); - String subject = req.getParameter("subject"); - String body = req.getParameter("body"); - result = sendJms(users, subject, body); + Part filePart = req.getPart("attach"); + + MailObject mailObject = MailUtils.getMailObject( + req.getParameter("users"), + req.getParameter("subject"), + req.getParameter("body"), + filePart == null ? null : filePart.getSubmittedFileName(), + filePart == null ? null : filePart.getInputStream()); + + result = sendJms(mailObject); log.info("Processing finished with result: {}", result); } catch (Exception e) { log.error("Processing failed", e); @@ -64,10 +75,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S resp.getWriter().write(result); } - private synchronized String sendJms(String users, String subject, String body) throws JMSException { - TextMessage testMessage = session.createTextMessage(); - testMessage.setText(subject); - producer.send(testMessage); + 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 index b751d2c5c..b254ab9c7 100644 --- a/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SoapSendServlet.java +++ b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/SoapSendServlet.java @@ -4,9 +4,8 @@ import lombok.extern.slf4j.Slf4j; import ru.javaops.masterjava.service.mail.GroupResult; import ru.javaops.masterjava.service.mail.MailWSClient; -import ru.javaops.masterjava.service.mail.util.Attachments; +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; @@ -20,7 +19,7 @@ @MultipartConfig public class SoapSendServlet extends HttpServlet { @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { String result; try { log.info("Start sending"); @@ -30,9 +29,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S String subject = req.getParameter("subject"); String body = req.getParameter("body"); Part filePart = req.getPart("attach"); - GroupResult groupResult = MailWSClient.sendBulk(MailWSClient.split(users), subject, body, + GroupResult groupResult = MailWSClient.sendBulk(MailUtils.split(users), subject, body, filePart == null ? null : - ImmutableList.of(Attachments.getAttachment(filePart.getSubmittedFileName(), filePart.getInputStream()))); + ImmutableList.of(MailUtils.getAttachment(filePart.getSubmittedFileName(), filePart.getInputStream()))); result = groupResult.toString(); log.info("Processing finished with result: {}", result); } catch (Exception e) { diff --git a/web/webapp/src/main/webapp/WEB-INF/templates/users.html b/web/webapp/src/main/webapp/WEB-INF/templates/users.html index 264339bf8..38ef3bad6 100644 --- a/web/webapp/src/main/webapp/WEB-INF/templates/users.html +++ b/web/webapp/src/main/webapp/WEB-INF/templates/users.html @@ -43,9 +43,9 @@

- SOAP
- REST
- JMS
+ SOAP
+ REST
+ JMS

@@ -53,10 +53,10 @@


 
 
+
+
+
+
 
-
+
@@ -34,13 +34,13 @@

- +


- +

SOAP
@@ -55,46 +55,7 @@


 
\ No newline at end of file diff --git a/web/webapp/src/main/webapp/resources/sending.js b/web/webapp/src/main/webapp/resources/sending.js new file mode 100644 index 000000000..e106b1f06 --- /dev/null +++ b/web/webapp/src/main/webapp/resources/sending.js @@ -0,0 +1,42 @@ +let url = "sendSoap"; //default + +function setUrl(value) { + url = value; +} + +function send() { + $('#result').html("Sending ..."); + let users = $("input:checkbox:checked").map(function () { + return this.value; + }).get(); + + if (users.length === 0) { + $('#result').html("Addresses are not selected"); + return; + } + + // https://stackoverflow.com/a/5976031/548473 + let data = new FormData(); + data.append('users', users); + data.append('subject', $("#subject").val()); + data.append('body', $("#body").val()); + let attach = $('#attach')[0].files; + + if (attach) $.each(attach, (k, v) => data.append("attachment" + k, v)); + + +// https://stackoverflow.com/a/22213543/548473 + $.post({ + url: url, + data: data, + contentType: false, + processData: false + }).done(function (result) { + if (typeof result === "object") { + result = JSON.stringify(result) + } + $('#result').html(result); + }).fail(function (result) { + $('#result').html(result.responseText); + }); +} \ No newline at end of file