diff --git a/README.md b/README.md index 86806493..e001b4da 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,120 @@ -# Курс JavaSE + Web - -## Вступительное заниятие - - [Необходимое ПО](intro.md#Необходимое-ПО) - - [Рекомендуемые книги](intro.md#Рекомендуемые-книги) - - [Ресуры в сети](intro.md#Ресуры-в-сети) - - [Вступительное задание](intro.md#Вступительное-задание) - -## Темы курса -### Занятие 1 - - [Разработка ПО](lesson1.md#Разработка-ПО) - - [Обзор языка Java](lesson1.md#Обзор-языка-java) - - [Системы управления версиями. Git](lesson1.md#Системы-управления-версиями-git) - - [Настройка окружения](lesson1.md#Настройка-окружения) - - [Насторойка проекта. Ветка HW1. Debug](lesson1.md#Насторойка-проекта-Ветка-hw1-debug) +# Курс BaseJava (обновленный и переработанный) + +## Разработка Web приложения "База данных резюме" + - используем: Java 8, IntelliJ IDEA, + GitHub/Git, Сервлеты, JSP, JSTL, Tomcat, JUnit, PostgreSQL, GSON, JAXB + - хранение резюме + - в памяти на основе массива, отсортированного массива, списка и ассоциированного массива (Map) + - в файловой системе (File API и Java 7 NIO File API) + - в стандартной и кастомной сериализации Java + - в формате JSON (Google Gson) + - в формате XML (JAXB) + - в реляционной базе PostgreSQL + - деплой веб приложения + - в контейнер сервлетов Tomcat + - в облачный сервис Heroku + +Приложение будет разрабатываться начиная со первого занятия, основываясь на базовых темах курса: +**объектная модель, коллекции, система ввода-вывода, работа с файлами, сериализация, работа с XML, JSON, SQL, персистентность в базу данных (PostgreSQL), сервлеты, HTML/JSP/JSTL, веб-контейнер Tomcat, модульные тесты JUnit, java.util.Logging, система контроля версий Git.** + +> Любое знание стоит воспринимать как подобие семантического дерева: убедитесь в том, что понимаете фундаментальные принципы, то есть ствол и крупные ветки, прежде чем лезть в мелкие листья-детали. Иначе последним не на чем будет держаться. + +[*— Илон Маск](https://ru.wikipedia.org/wiki/Маск,_Илон) + +# Изучайте [первое открытое занятие](lesson/lesson1.md). +### Внизу урока есть первое домашнее задание, по которому можно оценить свой уровень готовности к проекту. + +## Программа +### [Регистрация](http://javaops.ru/reg/basejava) +### Открытое занятие 1 + - [Презентация проекта](lesson/lesson1.md#-Вебинар-ПРЕЗЕНТАЦИЯ-обучения) + - [Разработка ПО](lesson/lesson1.md#-1-Разработка-ПО) + - [Обзор языка Java](lesson/lesson1.md#-3-Обзор-языка-java) + - [Системы управления версиями. Git](lesson/lesson1.md#-4-Системы-управления-версиями-git) + - [ПЕРВОЕ ДОМАШНЕЕ ЗАДАНИЕ](lesson/lesson1.md#Домашнее-задание-hw1) ### Занятие 2 - - [Принципы ООП](lesson2.md#Принципы-ООП) - - [Структура памяти: куча, стек, регистры, константы](lesson2.md#Структура-памяти-куча-стек-регистры-константы) - - [Типы данных. Пакеты](lesson2.md#Типы-данных-Пакеты) + - [Принципы ООП](lesson/lesson2.md#Принципы-ООП) + - [Структура памяти: куча, стек, регистры, константы](lesson/lesson2.md#Структура-памяти-куча-стек-регистры-константы) + - [Типы данных. Пакеты](lesson/lesson2.md#Типы-данных-Пакеты) ### Занятие 3 - - [Объектная модель в Java](lesson3.md#Объектная-модель-в-java) - - [Сложность алгоритмов](lesson3.md#Сложность-алгоритмов) - + - [Объектная модель в Java](lesson/lesson3.md#Объектная-модель-в-java) + - [Сложность алгоритмов](lesson/lesson3.md#Сложность-алгоритмов) + - [Паттерн проектирования Шаблонный метод](https://github.com/JavaOPs/JavaSE-Web/blob/master/lesson/lesson3.md#Паттерн-проектирования-Шаблонный-метод) + ### Занятие 4 - - [Работа со строками](lesson4.md#Работа-со-строками) - - [Исключения](lesson4.md#Исключения) - - [Reflection. Аннотации. Модульное тестирование](lesson4.md#reflection-Аннотации-Модульное-тестирование) + - [Работа со строками](lesson/lesson4.md#Работа-со-строками) + - [Исключения](lesson/lesson4.md#Исключения) + - [Reflection. Аннотации. Модульное тестирование](lesson/lesson4.md#reflection-Аннотации-Модульное-тестирование) ### Занятие 5 - - [Контейнеры/коллекции](lesson5.md#Контейнерыколлекции) + - [Контейнеры/коллекции](lesson/lesson5.md#Контейнерыколлекции) ### Занятие 6 - - [Iterator / Iterable. Вложенные, внутренние, локальные и анонимные классы](lesson6.md#iterator--iterable-Вложенные-внутренние-локальные-и-анонимные-классы) - - [Новое в Java 8](lesson6.md#Новое-в-java-8) + - [Iterator / Iterable. Вложенные, внутренние, локальные и анонимные классы](lesson/lesson6.md#iterator--iterable-Вложенные-внутренние-локальные-и-анонимные-классы) + - [Новое в Java 8](lesson/lesson6.md#Новое-в-java-8) ### Занятие 7 - - [Параметризация. Стирание типов](lesson7.md#Параметризация-Стирание-типов) - - [Логирование](lesson7.md#Логирование) - - [Синглетон, Enum](lesson7.md#Синглетон-enum) + - [Параметризация. Стирание типов](lesson/lesson7.md#Параметризация-Стирание-типов) + - [Логирование](lesson/lesson7.md#Логирование) + - [Синглетон, Enum](lesson/lesson7.md#Синглетон-enum) ### Занятие 8 - - [Работа с датами и временем](lesson8.md#Работа-с-датами-и-временем) - - [Работа с файлами и ресурсами](lesson8.md#Работа-с-файлами-и-ресурсами) + - [Работа с датами и временем](lesson/lesson8.md#Работа-с-датами-и-временем) + - [Работа с файлами и ресурсами](lesson/lesson8.md#Работа-с-файлами-и-ресурсами) ### Занятие 9 - - [Ввод/вывод](lesson9.md#Вводвывод) - - [Сериализация](lesson9.md#Сериализация) - - [NIO](lesson9.md#nio) - - [Основы Java 8 Stream API](lesson9.md#Основы-java-8-stream-api) + - [Ввод/вывод](lesson/lesson9.md#Вводвывод) + - [Сериализация](lesson/lesson9.md#Сериализация) + - [NIO](lesson/lesson9.md#nio) + - [Основы Java 8 Stream API](lesson/lesson9.md#Основы-java-8-stream-api) ### Занятие 10 - - [Формат XML. Работа с XML в Java](lesson10.md#Формат-xml-Работа-с-xml-в-java) - - [JSON](lesson10.md#json) - - [DataInputStream / DataOutputStream](lesson10.md#datainputstream--dataoutputstream) + - [Формат XML. Работа с XML в Java](lesson/lesson10.md#Формат-xml-Работа-с-xml-в-java) + - [JSON](lesson/lesson10.md#json) + - [DataInputStream / DataOutputStream](lesson/lesson10.md#datainputstream--dataoutputstream) ### Занятие 11 - - [Многопоточность. Параллельное выполнение.](lesson11.md#Многопоточность-Параллельное-выполнение) - - [Потоки. Синхронизация](lesson11.md#Потоки-Синхронизация) - - [Ленивая инициализация, JMM](lesson11.md#Ленивая-инициализация-jmm) + - [Многопоточность. Параллельное выполнение.](lesson/lesson11.md#Многопоточность-Параллельное-выполнение) + - [Потоки. Синхронизация](lesson/lesson11.md#Потоки-Синхронизация) + - [Ленивая инициализация, JMM](lesson/lesson11.md#Ленивая-инициализация-jmm) ### Занятие 12 - - [java.util.concurrent](lesson12.md#javautilconcurrent) + - [java.util.concurrent](lesson/lesson12.md#javautilconcurrent) ### Занятие 13 - - [Базы данных. Реляционные СУБД. PostgreSQL](lesson13.md#Базы-данных-Реляционные-СУБД-postgresql) - - [Конфигурирование данных в Java проекте](lesson13.md#Конфигурирование-данных-в-java-проекте) - - [Подключение DB в проект](lesson13.md#Подключение-db-в-проект) + - [Базы данных. Реляционные СУБД. PostgreSQL](lesson/lesson13.md#Базы-данных-Реляционные-СУБД-postgresql) + - [Конфигурирование данных в Java проекте](lesson/lesson13.md#Конфигурирование-данных-в-java-проекте) + - [Подключение DB в проект](lesson/lesson13.md#Подключение-db-в-проект) ### Занятие 14 - - [JOIN](lesson14.md#join) - - [Транзакции](lesson14.md#Транзакции) - - [Установка/запуск Tomcat](lesson14.md#Установказапуск-tomcat) + - [JOIN](lesson/lesson14.md#join) + - [Транзакции](lesson/lesson14.md#Транзакции) + - [Установка/запуск Tomcat](lesson/lesson14.md#Установказапуск-tomcat) ### Занятие 15 - - [HTML, Tomcat](lesson15.md#html-tomcat) - - [Сервлеты](lesson15.md#Сервлеты) + - [HTML, Tomcat](lesson/lesson15.md#html-tomcat) + - [Сервлеты](lesson/lesson15.md#Сервлеты) ### Занятие 16 - - [JSP](lesson16.md#jsp) - - [JSTL](lesson16.md#jstl) + - [JSP](lesson/lesson16.md#jsp) + - [JSTL](lesson/lesson16.md#jstl) ### Занятие 17 - - [Деплой в Heroku](lesson17.md#Деплой-в-heroku) - - [Classloader](lesson17.md#classloader) - - [Обзор Java Enterprise](lesson17.md#Обзор-java-enterprise) - + - [Деплой в Heroku](lesson/lesson17.md#Деплой-в-heroku) + - [Classloader](lesson/lesson17.md#classloader) + - [Обзор Java Enterprise](lesson/lesson17.md#Обзор-java-enterprise) + +## Рекомендуемые книги +- YAKOV FAIN: Программирование на Java для начинающих +- Книги по Java: от новичка до профессионала +- Джошуа Блох: Java. Эффективное программирование, 2-е издание +- Гамма, Хелм, Джонсон: Приемы объектно-ориентированного проектирования. Паттерны проектирования +- Редмонд Э.: Семь баз данных за семь недель. Введение в современные базы данных и идеологию NoSQL. + +## Ресуры в сети +- [Руководство по Java Core](http://proselyte.net/tutorials/java-core/) +- [Java. Базовый курс](https://stepik.org/course/Java-Базовый-курс-187) +- intuit: Программирование на Java +- Основы программирования на Java: учебное пособие diff --git a/config/init_db.sql b/config/init_db.sql new file mode 100644 index 00000000..19d8073a --- /dev/null +++ b/config/init_db.sql @@ -0,0 +1,47 @@ +create table resume ( + uuid char(36) not null + constraint resume_pkey + primary key, + full_name text NOT NULL +); + +alter table resume + owner to postgres; + +create table contact +( + id serial not null + constraint contact_pkey + primary key, + type text not null, + value text not null, + resume_uuid char(36) not null + constraint contact_resume_uuid_fk + references resume + on delete cascade +); + +alter table contact + owner to postgres; + +create unique index contact_uuid_type_index + on contact (resume_uuid, type); + +create table section +( + id serial not null + constraint section_pk + primary key, + type text not null, + value text not null, + resume_uuid char(36) not null + constraint section_resume_uuid_fk + references resume + on delete cascade +); + +alter table section owner to postgres; + +create unique index section_uuid_type_index + on section (resume_uuid, type); + diff --git a/config/init_db_gkislin.sql b/config/init_db_gkislin.sql new file mode 100644 index 00000000..e5d0242a --- /dev/null +++ b/config/init_db_gkislin.sql @@ -0,0 +1,13 @@ +CREATE TABLE resume ( + uuid CHAR(36) PRIMARY KEY NOT NULL, + full_name TEXT NOT NULL +); + +CREATE TABLE contact ( + id SERIAL, + resume_uuid CHAR(36) NOT NULL REFERENCES resume (uuid) ON DELETE CASCADE, + type TEXT NOT NULL, + value TEXT NOT NULL +); +CREATE UNIQUE INDEX contact_uuid_type_index + ON contact (resume_uuid, type); diff --git a/config/populate.sql b/config/populate.sql new file mode 100644 index 00000000..6d8c9404 --- /dev/null +++ b/config/populate.sql @@ -0,0 +1,4 @@ +INSERT INTO resume (uuid, full_name) VALUES +('7de882da-02f2-4d16-8daa-60660aaf4071', 'Name1'), +('a97b3ac3-3817-4c3f-8a5f-178497311f1d', 'Name2'), +('dd0a70d1-5ed3-479a-b452-d5e04f21ca73', 'Name3'); \ No newline at end of file diff --git a/config/resumes.properties b/config/resumes.properties new file mode 100644 index 00000000..abeec497 --- /dev/null +++ b/config/resumes.properties @@ -0,0 +1,7 @@ +storage.dir=storage +db.url=jdbc:postgresql://ec2-79-125-6-250.eu-west-1.compute.amazonaws.com:5432/d76l4kfs5it914 +db.user=tqcuhhawowqmzx +db.password=a5d3358e731c4e8bc1b34ec5494213f866b088123429223e1c120d3309d986a5 +#db.url=jdbc:postgresql://localhost:5432/resumes +#db.user=postgres +#db.password=postgres diff --git a/intro.md b/intro.md deleted file mode 100644 index c929bead..00000000 --- a/intro.md +++ /dev/null @@ -1,38 +0,0 @@ - -# Вступительное занятие - -## Необходимое ПО -- JDK8 -- Git -- Аккаунт GitHub -- IntelliJ IDEA - -> Выбирать Ultimate, 30 days trial (нам понадобится Git, JavaScript, Tomcat, JSP). Учебный ключ выдается на первом занятии. - -## Рекомендуемые книги -- YAKOV FAIN: Программирование на Java для начинающих -- Книги по Java: от новичка до профессионала -- Джошуа Блох: Java. Эффективное программирование, 2-е издание -- Гамма, Хелм, Джонсон: Приемы объектно-ориентированного проектирования. Паттерны проектирования -- Редмонд Э.: Семь баз данных за семь недель. Введение в современные базы данных и идеологию NoSQL. - -## Ресуры в сети -- intuit: Программирование на Java -- Основы программирования на Java: учебное пособие - -## Вступительное задание -- Установите ПО: Git, Java 8, IntelliJ IDEA -- Создайте локальную копию нашего проекта: `git clone https://github.com/JavaWebinar/JavaSE-Web.git` -> в результате у вас должен появится каталог `JavaSE-Web` - -- В IntelliJ IDEA создайте новый проект, выбрав каталог `JavaSE-Web` - -![image](https://cloud.githubusercontent.com/assets/18701152/14917746/38c4a20a-0e29-11e6-8985-c57911da57c4.png) - -![image](https://cloud.githubusercontent.com/assets/18701152/14917800/71887238-0e29-11e6-9830-e557901892b4.png) - -![image](https://cloud.githubusercontent.com/assets/18701152/14917769/5025e29c-0e29-11e6-9c7b-70b82966ccbe.png) - -- Реализуйте класс `ArrayStorage`: хранение резюме на основе массива (методы `clear, get, save, delete, getAll, size`). -- Протестируйте вашу реализацию, запустив `MainTestArrayStorage.main()`: в IDEA слева на полях зеленая стрелка. -- Протестируйте вашу реализацию интерактивно с помощью `MainArray.main()`. diff --git a/lesson/lesson1.md b/lesson/lesson1.md new file mode 100644 index 00000000..04a2fab9 --- /dev/null +++ b/lesson/lesson1.md @@ -0,0 +1,104 @@ +# Первое занятие + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) [Вебинар-презентация обучения на проекте BaseJava](https://drive.google.com/file/d/0B_4NpoQW1xfpNzdqT2hOcUJ6TGs) +#### [Итоговый пример приложения, разрабатываемого в рамках данного курса (на примере резюме Григория Кислина)](https://javawebinar.github.io/) + +### Подготовка рабочего окружения +- Установите [JDK8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) (выбрать Accept License Agreement) +- Установите систему управления версиями [Git](http://git-scm.com/downloads) (опции по умолчанию) +- Создайте аккаунт на [GitHub](https://github.com/). Для удобной навигации по файлам на GitHub можно установить расширение для браузера [Octotree](https://habrahabr.ru/post/223527/) +- Установите [IntelliJ IDEA](http://www.jetbrains.com/idea/download/index.html). **Пока нет базы данных и веб, можно работать с версией Community**. На версию Ultimate дается 30 дней пробного бесплатного использования (trial). На проектах каждый участник проектов получает единоразовый личный купон на бесплатную версию Ultimate на 6 месяцев. + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. [Разработка ПО](https://drive.google.com/open?id=0B_4NpoQW1xfpVjZUTEpvVUN1TTA) +- [Мифический человеко-месяц](https://ru.wikipedia.org/wiki/Мифический_человеко-месяц) (wiki) +- [Размеры проектов в количестве строк кода [eng]](https://medium.freecodecamp.com/the-biggest-codebases-in-history-a128bb3eea73) +- [Соглашения по оформлению кода [eng]](https://google.github.io/styleguide/javaguide.html) +- [Методологии разработки ПО](https://dou.ua/forums/topic/14015/) +- [Ещё раз про семь основных методологий разработки](https://habrahabr.ru/company/edison/blog/269789/) + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. [Обзор инструментов и технологий](https://drive.google.com/file/d/0B_4NpoQW1xfpTXJYU2xZbjN2d2M) +- [Bash — шпаргалка для начинающих](https://tproger.ru/translations/bash-cheatsheet) +- [Интерактивный курс по SQL](http://www.sql-ex.ru/) +- [Типичный тест SQL на собеседованиях](https://habrahabr.ru/post/181033/) +- **Обновление!!**[Что и почему используют Java-разработчики: опрос RebelLabs](https://jug.ru/2017/12/rebellabs-report/) + - [Java Tools and Technologies Landscape Report 2016](https://zeroturnaround.com/rebellabs/java-tools-and-technologies-landscape-2016/) + - [Java Tools and Technologies Landscape for 2014](http://zeroturnaround.com/rebellabs/java-tools-and-technologies-landscape-for-2014) +- **Дополнительно:** + - [Автоматизированная сборка проекта в Java](http://spring-projects.ru/guides/maven/) + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. [Обзор языка Java](https://drive.google.com/open?id=0B_4NpoQW1xfpTU5SSElhUjlGNnc) +- [Java](http://ru.wikipedia.org/wiki/Java), [JVM](http://ru.wikipedia.org/wiki/Виртуальная_машина_Java), [JIT-компиляция](http://ru.wikipedia.org/wiki/JIT) (wiki) +- [Что такое Java? История создания](http://www.intuit.ru/studies/courses/16/16/lecture/27105) +- [Programming languages TIOBE Index](http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html) + +![jvm](https://cloud.githubusercontent.com/assets/18701152/15219296/e6c67e86-186b-11e6-986f-651a87deec6c.png) + +- [ME](http://ru.wikipedia.org/wiki/Java_Platform,_Micro_Edition), [SE](https://ru.wikipedia.org/wiki/Java_Platform,_Standard_Edition), [EE](http://ru.wikipedia.org/wiki/Java_Platform,_Enterprise_Edition) (wiki) +- [Java Microbenchmark JMH](http://openjdk.java.net/projects/code-tools/jmh/) (используем на курсе [MasterJava](https://github.com/JavaWebinar/masterjava#Занятие-2)) +- [Oracle Java8 Home](http://docs.oracle.com/javase/8/docs/index.html) +- **Дополнительно:** + - [Java version and vendor data analyzed 2017](https://plumbr.io/blog/java/java-version-and-vendor-data-analyzed-2017-edition) + - [Most popular Java application servers: 2017 edition](https://plumbr.io/blog/java/most-popular-java-application-servers-2017-edition) + - [Понимаем основы Java garbage collection](https://ggenikus.github.io/blog/2014/05/04/gc) + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. [Системы управления версиями. Git](https://drive.google.com/file/d/0B9Ye2auQ_NsFSUNrdVc0bDZuX2s) + +![image](https://cloud.githubusercontent.com/assets/18701152/15219746/9295a2fe-186d-11e6-876b-c61cc9be71e4.png) + + - [Система управления версиями](https://ru.wikipedia.org/wiki/Система_управления_версиями) (wiki) + - [Сравнение разных VCS](https://biz30.timedoctor.com/ru/cистема-контроля-версий/) + - [Git обучалка](https://githowto.com/ru) + - [Интерактивная Git обучалка (в настройках выберите русский язык)](http://learngitbranching.js.org) + - [Официальная книга про Git](https://git-scm.com/book/ru/v2) + - [Working with remote repositories](https://illustrated-git.readthedocs.org/en/latest/#working-with-remote-repositories) + - [Базовый курс по обучению Git](https://www.youtube.com/playlist?list=PLIU76b8Cjem5B3sufBJ_KFTpKkMEvaTQR) (youtube) + - [Git. Быстрый старт](https://www.youtube.com/watch?v=4-NX17Ip-xQ&list=PLmRNNqEA7JoM77hOJkPrLOfJQGizCLR3P) (youtube) + +### Настройка проекта +- Создайте на GitHub репозиторий с названием `basejava` +- Откройте консоль (терминал) у себя на компьютере +- Наберите и запустите: `git` (по умолчанию при установке git заносится в PATH. Если он не находится, [занесите](https://www.java.com/ru/download/help/path.xml) git в переменную окружения PATH и перезапустите консоль) +- Создайте локальную копию проекта: `git clone https://github.com/JavaOps/basejava.git` +- Перейдите в каталог проекта: `cd basejava` +- Настройте git в локальном проекте на свой проект в GitHub: + - `git remote -v` + - `git remote set-url origin url_на_твой_basejava-репозиторий.git` - настройка pull + - `git remote set-url --push origin url_на_твой_basejava-репозиторий.git` - настройка push + - `git push -u origin master` + +## Домашнее задание HW1 +- Создайте в IntelliJ IDEA новый проект, выбрав каталог `basejava`, который вы клонировали ранее к себе на компьютер: + +![newproject](https://user-images.githubusercontent.com/29703461/38273513-d1f7ce52-3794-11e8-829c-305212c25be7.png) + +![next](https://user-images.githubusercontent.com/29703461/38273546-e712a6fe-3794-11e8-9850-29287b46a8a0.png) + +![next1](https://user-images.githubusercontent.com/29703461/38273584-00e07dc2-3795-11e8-9006-3109f949cf33.png) + +![finish](https://user-images.githubusercontent.com/29703461/38275669-3e621614-379b-11e8-8b3a-8e0a3ad4c65c.png) + +- Реализуйте класс `ru.javaops.webapp.storage.ArrayStorage`, организовав хранение резюме на основе массива с методами `save, get, delete, size, clear, getAll` +- При этом храните все резюме в начале `storage` (без дырок в виде `null`), чтобы не перебирать каждый раз все 10000 элементов +``` +Схема хранения резюме в storage (от 0 до size - 1 элементов null нет): +r1, r2, r3,..., rn, null, null,..., null +<----- size -----> +<------- storage.length (10000) -------> +``` +- Посмотрите на класс `java.util.Arrays`. В нем есть полезные методы, которые помогут вам написать более простой и понятный код +- Протестируйте вашу реализацию с помощью классов `ru.javaops.webapp.MainArray.main()` и `ru.javaops.webapp.MainTestArrayStorage.main()` +- Изучите дополнительные материалы по IntelliJ IDEA: + - [Idea Wiki](https://github.com/JavaOPs/topjava/wiki/IDEA) ([поставьте кодировку UTF-8](https://github.com/JavaOPs/topjava/wiki/IDEA#Поставить-кодировку-utf-8), [поменяйте шрифт по умолчанию на DejaVu](https://github.com/JavaOPs/topjava/wiki/IDEA#Поменять-фонт-по-умолчанию-dejavu)) + - [Руководство пользователя IntelliJ IDEA. Отладчик](http://info.javarush.ru/idea_help/2014/01/22/Руководство-пользователя-IntelliJ-IDEA-Отладчик-.html) + - [Эффективная работа с кодом в IntelliJ IDEA](https://www.youtube.com/watch?v=tpv5n2jWHlw) (youtube) + - [Эффективная работа в IDEA](https://www.youtube.com/watch?v=_rj7dx6c5R8) (youtube) + +### Вопросы по HW1 + > Не могу запустить программу, да и рядом с классами появился какой-то значок + ![badsrc](https://user-images.githubusercontent.com/29703461/38277015-9cd9155e-379f-11e8-9cd4-a9182a005e9a.png) + - Проблема в том, что IDEA неправильно "воспринимает" папку `src`. Для ее решения необходимо нажать `ПКМ на папке src -> выбрать Mark Directory as -> Sources Root` + + > Что такое `null`? + +[Что такое null в Java?](http://qaru.site/questions/1960/what-is-null-in-java) (оригинал: [What is null in Java? +](https://stackoverflow.com/questions/2707322/what-is-null-in-java)) diff --git a/lesson10.md b/lesson/lesson10.md similarity index 100% rename from lesson10.md rename to lesson/lesson10.md diff --git a/lesson11.md b/lesson/lesson11.md similarity index 100% rename from lesson11.md rename to lesson/lesson11.md diff --git a/lesson12.md b/lesson/lesson12.md similarity index 63% rename from lesson12.md rename to lesson/lesson12.md index c3a99007..47156cdd 100644 --- a/lesson12.md +++ b/lesson/lesson12.md @@ -9,10 +9,27 @@ - Справочник по синхронизаторам java.util.concurrent.* - Использование ThreadLocal переменных +> Замечания по видео: + + ThreadLocal DATE_FORMAT = new ThreadLocal() { + @Override + protected SimpleDateFormat initialValue() { + return new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); + }; + }; + +можно написать через лямбду: + + ThreadLocal.withInitial(() -> new SimpleDateFormat("dd.MM.yyyy HH:mm:ss")); + +А лучше использовать потокобезопасный `DateTimeFormatter` Java 8 Time API: + + DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss"); + ## Разбор домашнего задания 10го урока ## Домашнее задание: - Установить PostgreSQL - Посомтреть на реляционные базы данных и SQL: - Введение в базы данных - - Основы SQL \ No newline at end of file + - Основы SQL diff --git a/lesson13.md b/lesson/lesson13.md similarity index 100% rename from lesson13.md rename to lesson/lesson13.md diff --git a/lesson14.md b/lesson/lesson14.md similarity index 100% rename from lesson14.md rename to lesson/lesson14.md diff --git a/lesson15.md b/lesson/lesson15.md similarity index 100% rename from lesson15.md rename to lesson/lesson15.md diff --git a/lesson16.md b/lesson/lesson16.md similarity index 100% rename from lesson16.md rename to lesson/lesson16.md diff --git a/lesson17.md b/lesson/lesson17.md similarity index 100% rename from lesson17.md rename to lesson/lesson17.md diff --git a/lesson2.md b/lesson/lesson2.md similarity index 87% rename from lesson2.md rename to lesson/lesson2.md index e5f33609..a6888a56 100644 --- a/lesson2.md +++ b/lesson/lesson2.md @@ -5,6 +5,7 @@ - Методология процедурно-ориентированного и объектно-ориентированного программирования - Объекты (cостояние, поведение, уникальность) - Классы. Инкапсуляция. Наследование. Полиморфизм. +- Основы Объектно-Ориентированного Программирования (ООП) - Типы отношений между классами Наследование, агрегация, композиция, ассоциация. - Достоинства/Недостатки ООП. Библиотеки vs фреймворки. - Дополнительно: @@ -30,8 +31,11 @@ - Модификаторы доступа. Область Видимости. ## Домашнее задание + +> Правка к видео: ru.javaops.webapp.storage.ArrayStorage.delete() - вместо `storage[i] = null` нужно `storage[size-1] = null` + - Прочитать Соглашения по именованию. -- Реализовать `ArrayStorage.update` -- Сделать проверки: в `update/delete/get` - резюме есть в storage, в `save`- нет в storage: `System.out.println("Resume ...")`. +- Реализовать `ru.javaops.webapp.storage.ArrayStorage.update` +- Сделать проверки: в `update/delete/get` - резюме есть в storage, в `save`- нет в storage: `System.out.println("ru.javaops.webapp.model.Resume ...")`. - Сделать в save проверку на переполнениеe: `System.out.println("...")`. -- Избавится от дублирования в коде `ArrayStorage` +- Избавится от дублирования в коде `ru.javaops.webapp.storage.ArrayStorage` diff --git a/lesson3.md b/lesson/lesson3.md similarity index 72% rename from lesson3.md rename to lesson/lesson3.md index e86bcc05..af719e49 100644 --- a/lesson3.md +++ b/lesson/lesson3.md @@ -8,14 +8,17 @@ - Object. Контракт equals/hashCode - Интерфейсы - Полиморфизм, abstract +- [Java Core. Вопросы к собеседованию](http://info.javarush.ru/translation/2014/02/12/Java-Core-Вопросы-к-собеседованию-ч-1.html) ## Сложность алгоритмов - Алгоритмы и структуры данных для начинающих: сложность алгоритмов - Time complexity - Временная сложность алгоритма - Вычислительная сложность + +## Паттерн проектирования Шаблонный метод - Шаблонный метод ## Домашнее задание -- Закончить реализацию `AbstractArrayStorage`, `ArrayStorage`, `SortedArrayStorage` (`SortedArrayStorage` хранит элементы отсортированными, сортировать весь массив не надо). -- Сделать проверку `ArrayStorage.update` +- Закончить реализацию `AbstractArrayStorage`, `ru.javaops.webapp.storage.ArrayStorage`, `SortedArrayStorage` (`SortedArrayStorage` хранит элементы отсортированными, сортировать весь массив не надо). +- Сделать проверку `ru.javaops.webapp.storage.ArrayStorage.update` diff --git a/lesson4.md b/lesson/lesson4.md similarity index 93% rename from lesson4.md rename to lesson/lesson4.md index 4207304b..c365e5d8 100644 --- a/lesson4.md +++ b/lesson/lesson4.md @@ -25,4 +25,4 @@ ## Домашнее задание Реализовать `AbstractStorageTest` и тесты `ArrayStorageTest` и `SortedArrayStorageTest`. -В `MainReflection` сделать вызов метода `toString` через отражение. +В `ru.javaops.webapp.MainReflection` сделать вызов метода `toString` через отражение. diff --git a/lesson5.md b/lesson/lesson5.md similarity index 62% rename from lesson5.md rename to lesson/lesson5.md index fdc77b03..169a361c 100644 --- a/lesson5.md +++ b/lesson/lesson5.md @@ -8,6 +8,8 @@ - Структуры данных в картинках - Инициализация полей в Java - Java собеседование по коллекциям +- [Часто задаваемые на собеседованиях вопросы по классам коллекциям в Java](http://info.javarush.ru/translation/2013/10/08/Часто-задаваемые-на-собеседованиях-вопросы-по-классам-коллекциям-в-Java-Часть-2-.html#1) +- [Собеседование по Java — коллекции](http://javastudy.ru/interview/collections/) ## Домашнее задание -Выделить общий класс `AbstractStorage` и реализовать подклассы `ListStorage` и `MapStorage`. Выбор реализации List и Map за вами. \ No newline at end of file +Выделить общий класс `AbstractStorage` и реализовать подклассы `ListStorage` и `MapStorage`. Выбор реализации List и Map за вами. diff --git a/lesson6.md b/lesson/lesson6.md similarity index 82% rename from lesson6.md rename to lesson/lesson6.md index 3295fbb7..a995d525 100644 --- a/lesson6.md +++ b/lesson/lesson6.md @@ -15,6 +15,6 @@ ## Домашнее задание - Сделать рефакторинг тестов: `saveOverflow` должно быть только для Array реализаций. -- Рефакторинг: в конструктор Resume добавить второй параметр `fullName` -- Сделать рефакторинг всех реализаций `Storage`: заменить метод `Resume[] getAll()` на `List getAllSorted()` +- Рефакторинг: в конструктор ru.javaops.webapp.model.Resume добавить второй параметр `fullName` +- Сделать рефакторинг всех реализаций `Storage`: заменить метод `ru.javaops.webapp.model.Resume[] getAll()` на `List getAllSorted()` - Реализовать до конца `MapUuidStorage`. Подумать что еще может быть search key в реализации на основе Map. diff --git a/lesson7.md b/lesson/lesson7.md similarity index 94% rename from lesson7.md rename to lesson/lesson7.md index 30494c95..cf77911e 100644 --- a/lesson7.md +++ b/lesson/lesson7.md @@ -23,7 +23,7 @@ Доменный объект Cделать объектную модель резюме (диаграмма и классы). Образец резюме (делаем упрощенно) - - Делать только классы, включаемые в Resume. Resume - главный класс. В него все включается (композиция - строгий вид агрегации). + - Делать только классы, включаемые в ru.javaops.webapp.model.Resume. ru.javaops.webapp.model.Resume - главный класс. В него все включается (композиция - строгий вид агрегации). - Схожие по структуре и функциональности сущности делаем одним классом. - Модель упрощаем для хранения только необходимой информации для вывода/ редактирования резюме. - В модели резюме должны быть представлены контакты и следующие разделы: diff --git a/lesson8.md b/lesson/lesson8.md similarity index 100% rename from lesson8.md rename to lesson/lesson8.md diff --git a/lesson9.md b/lesson/lesson9.md similarity index 100% rename from lesson9.md rename to lesson/lesson9.md diff --git a/lesson1.md b/lesson1.md deleted file mode 100644 index 0b1fc5c0..00000000 --- a/lesson1.md +++ /dev/null @@ -1,60 +0,0 @@ - -# Первое занятие - -## Разработка ПО. -- Книга: Мифический человеко-месяц -- Методологии разработки ПО -- Ещё раз про семь основных методологий разработки - -## Обзор инструментов и технологий. -- Обзор популярности инструментов и технологий Java за 2014г. -- Дополнительно: - - Автоматизированные сборки в Java - -## Обзор языка Java -- Java, JVM, JIT-компиляция - -![jvm](https://cloud.githubusercontent.com/assets/18701152/15219296/e6c67e86-186b-11e6-986f-651a87deec6c.png) - -- Programming languages TIOBE Index -- ME, SE (русский), EE (русский) -- Oracle Java8 Home -- Дополнительно: - - Java version and vendor data analyzed - - Most Popular Java EE Servers - - Что такое Java? История создания - - Понимаем основы Java garbage collection - -## Системы управления версиями. Git. -- Система управления версиями. -- VCS/DVSC. - -![image](https://cloud.githubusercontent.com/assets/18701152/15219746/9295a2fe-186d-11e6-876b-c61cc9be71e4.png) - -- Популярность разный VSC -- Книга по Git - -## Настройка окружения -- Idea Wiki (поставить кодировку UTF-8, поменять фонт по умолчанию на DejaVu) -- git занести в переменная окружения PATH, перезапустить cmd -- Создайте локальную копию нашего проекта: `git clone https://github.com/School-IT-Programm/resume-storage.git` -- Перейти в каталог проекта: `cd resume-storage` -- `git remote -v` -- `git remote set-url origin https://github.com/School-IT-Programm/resume-storage.git` - настройка pull -- `git remote set-url --push origin https://github.com/[YouGitHub/YourRepo].git` - настройка push -- `git push -u origin master` - -## Насторойка проекта. Ветка HW1. Debug -- Отладчик IntelliJ IDEA -- Эффективная работа с кодом в IntelliJ IDEA - -## Домашнее задание -- Модифицировать класс `ArrayStorage`: хранить все резюме в начале storage (без дырок null), чтобы не перебирать каждый раз все 10000 элементов. -``` -Хранеие резюме в storage (от 0 до size-1 элементов null нет): - -r1, r2, r3,..., rn, null, null,..., null -<---- size -----> -<---- storage.length ---------------> -``` -- Посмотреть на класс `Arrays`. Там есть полезные вещи, которые могут упростить код `ArrayStorage`. diff --git a/src/ArrayStorage.java b/src/ArrayStorage.java deleted file mode 100644 index 7aff0388..00000000 --- a/src/ArrayStorage.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Array based storage for Resumes - */ -public class ArrayStorage { - Resume[] storage = new Resume[10000]; - - void clear() { - } - - void save(Resume r) { - } - - Resume get(String uuid) { - return null; - } - - void delete(String uuid) { - } - - /** - * @return array, contains only Resumes in storage (without null) - */ - Resume[] getAll() { - return new Resume[0]; - } - - int size() { - return 0; - } -} diff --git a/src/MainTestArrayStorage.java b/src/MainTestArrayStorage.java deleted file mode 100644 index 17ba8c03..00000000 --- a/src/MainTestArrayStorage.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Test for com.urise.webapp.storage.ArrayStorage - */ -public class MainTestArrayStorage { - static final ArrayStorage ARRAY_STORAGE = new ArrayStorage(); - - public static void main(String[] args) { - Resume r1 = new Resume(); - r1.uuid = "uuid1"; - Resume r2 = new Resume(); - r2.uuid = "uuid2"; - Resume r3 = new Resume(); - r3.uuid = "uuid3"; - - ARRAY_STORAGE.save(r1); - ARRAY_STORAGE.save(r2); - ARRAY_STORAGE.save(r3); - - System.out.println("Get r1: " + ARRAY_STORAGE.get(r1.uuid)); - System.out.println("Size: " + ARRAY_STORAGE.size()); - - System.out.println("Get dummy: " + ARRAY_STORAGE.get("dummy")); - - printAll(); - ARRAY_STORAGE.delete(r1.uuid); - printAll(); - ARRAY_STORAGE.clear(); - printAll(); - - System.out.println("Size: " + ARRAY_STORAGE.size()); - } - - static void printAll() { - System.out.println("\nGet All"); - for (Resume r : ARRAY_STORAGE.getAll()) { - System.out.println(r); - } - } -} diff --git a/src/Resume.java b/src/Resume.java deleted file mode 100644 index 937c6a75..00000000 --- a/src/Resume.java +++ /dev/null @@ -1,13 +0,0 @@ -/** - * com.urise.webapp.model.Resume class - */ -public class Resume { - - // Unique identifier - String uuid; - - @Override - public String toString() { - return uuid; - } -} diff --git a/src/ru/javaops/webapp/Config.java b/src/ru/javaops/webapp/Config.java new file mode 100644 index 00000000..8c75b7b0 --- /dev/null +++ b/src/ru/javaops/webapp/Config.java @@ -0,0 +1,48 @@ +package ru.javaops.webapp; + +import ru.javaops.webapp.storage.FileStorage; +import ru.javaops.webapp.storage.IStorage; +import ru.javaops.webapp.storage.SqlStorage; + +import java.io.*; +import java.util.Properties; + +public class Config { + private final static String PROPS = "/resumes.properties"; + private static final Config INSTANCE = new Config(); + + private final File storageDir; + private final IStorage storage; + + public static Config getInstance() { + return INSTANCE; + } + + private Config() { + try (InputStream is = Config.class.getResourceAsStream(PROPS)) { + Properties prop = new Properties(); + prop.load(is); + storageDir = new File(prop.getProperty("storage.dir")); + storage = new SqlStorage(prop.getProperty("db.url"), prop.getProperty("db.user"), prop.getProperty("db.password")); + } catch (IOException e) { + throw new IllegalStateException("Invalid config file " + PROPS); + } + } + + public File getStorageDir() { + return storageDir; + } + + public IStorage getStorage() { + return storage; + } + +// private static File getHomeDir(){ +// String prop = System.getProperty("homeDir"); +// File homeDir = new File(prop == null? "." : prop); +// if (!homeDir.isDirectory()){ +// throw new IllegalStateException(homeDir + " is not directory."); +// } +// return homeDir; +// } +} diff --git a/src/MainArray.java b/src/ru/javaops/webapp/MainArray.java similarity index 67% rename from src/MainArray.java rename to src/ru/javaops/webapp/MainArray.java index 20f1f141..6823ffce 100644 --- a/src/MainArray.java +++ b/src/ru/javaops/webapp/MainArray.java @@ -1,12 +1,19 @@ +package ru.javaops.webapp; + +import ru.javaops.webapp.model.Resume; +import ru.javaops.webapp.storage.*; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.util.List; -/** - * Test for com.urise.webapp.storage.ArrayStorage - */ public class MainArray { - private final static ArrayStorage ARRAY_STORAGE = new ArrayStorage(); + //private final static IStorage ARRAY_STORAGE = new ArrayStorage(); + private final static IStorage ARRAY_STORAGE = new SortedArrayStorage(); + //private final static IStorage ARRAY_STORAGE = new ListStorage(); + //private final static IStorage ARRAY_STORAGE = new MapUuidStorage(); + //private final static IStorage ARRAY_STORAGE = new MapFullNameStorage(); public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); @@ -14,14 +21,19 @@ public static void main(String[] args) throws IOException { while (true) { System.out.print("Введите одну из команд - (list | save uuid | delete uuid | get uuid | clear | exit): "); String[] params = reader.readLine().trim().toLowerCase().split(" "); - if (params.length < 1 || params.length > 2) { + if (params.length < 1 || params.length > 3) { System.out.println("Неверная команда."); continue; } String uuid = null; + String fullName = null; if (params.length == 2) { uuid = params[1].intern(); } + if (params.length == 3) { + uuid = params[1].intern(); + fullName = params[2].intern(); + } switch (params[0]) { case "list": printAll(); @@ -30,8 +42,7 @@ public static void main(String[] args) throws IOException { System.out.println(ARRAY_STORAGE.size()); break; case "save": - r = new Resume(); - r.uuid = uuid; + r = new Resume(uuid, fullName); ARRAY_STORAGE.save(r); printAll(); break; @@ -55,14 +66,14 @@ public static void main(String[] args) throws IOException { } } - static void printAll() { - Resume[] all = ARRAY_STORAGE.getAll(); + private static void printAll() { + List all = ARRAY_STORAGE.getAllSorted(); System.out.println("----------------------------"); - if (all.length == 0) { + if (all.size() == 0) { System.out.println("Empty"); } else { for (Resume r : all) { - System.out.println(r); + System.out.println(r.getUuid() + " " + r.getFullName()); } } System.out.println("----------------------------"); diff --git a/src/ru/javaops/webapp/MainConcurrancy.java b/src/ru/javaops/webapp/MainConcurrancy.java new file mode 100644 index 00000000..50b7fbd9 --- /dev/null +++ b/src/ru/javaops/webapp/MainConcurrancy.java @@ -0,0 +1,157 @@ +package ru.javaops.webapp; + +import ru.javaops.webapp.util.LazySingleton; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class MainConcurrancy { + private static int counter; + //private static final Object LOCK = new Object(); + private static final Lock lock = new ReentrantLock(); + private final static AtomicInteger atomicCounter = new AtomicInteger(); + private static final int THREADS_COUNT = 10000; + + private static final ThreadLocal threadLocal = new ThreadLocal(){ + @Override + protected SimpleDateFormat initialValue() { + return new SimpleDateFormat(); + } + }; + + public static void main(String[] args) throws InterruptedException { + + Thread thread0 = new Thread() { + public void run() { + System.out.println(getName() + ", " + getState()); + } + }; + thread0.start(); + + Thread thread1 = new Thread(new Runnable() { + @Override + public void run() { + Thread currentThred = Thread.currentThread(); + System.out.println(currentThred.getName() + ", " + currentThred.getState()); + +// throw new IllegalStateException(); + } + }); + thread1.start(); + + + new Thread(() -> { + Thread currentThread = Thread.currentThread(); + System.out.println(currentThread.getName() + ", " + currentThread.getState()); + }).start(); + + System.out.println(thread0.getName() + ", " + thread0.getState()); + System.out.println(thread1.getName() + ", " + thread1.getState()); + + //Thread.currentThread().sleep(500); + //Thread.currentThread().join(); + //System.out.println(counter); + + //final Object lock2 = new Object(); + final MainConcurrancy mainConcurancy = new MainConcurrancy(); + //CountDownLatch latch = new CountDownLatch(THREADS_COUNT); + //Создать кол-во потоков равное кол-ву ядер на ПК/Сервере +// ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + ExecutorService executorService = Executors.newCachedThreadPool(); +// CompletionService completionService = new ExecutorCompletionService(executorService); +// List threads = new ArrayList<>(THREADS_COUNT); + for (int i = 0; i < THREADS_COUNT; i++) { + Future future = executorService.submit(() -> { + { + for (int j = 0; j < 100; j++) { + mainConcurancy.inc(); + //System.out.println(threadLocal.get().format(new Date())); + } + //latch.countDown(); + return 5; + } + }); +// completionService.poll(); + /*System.out.println(future.isDone()); + try { + System.out.println(future.get()); + } catch (ExecutionException e) { + e.printStackTrace(); + }*/ + /*Thread thread = new Thread(new Runnable() { + @Override + public void run() { + for (int j = 0; j < 100; j++) { + mainConcurancy.inc(); + } + latch.countDown(); + } + }); + thread.start();*/ + //thread.join(); +// threads.add(thread); + } + + //System.out.println(counter); + System.out.println(atomicCounter.get()); + +// threads.forEach(thread -> { +// try { +// thread.join(); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// }); + //latch.await(10, TimeUnit.SECONDS); + executorService.shutdown(); + //Thread.sleep(500); + LazySingleton.getInstance(); + //System.out.println(counter); + System.out.println(atomicCounter.get()); + + String lock1 = "lock1"; + String lock2 = "lock2"; +// doDeadLock(lock1, lock2); +// doDeadLock(lock2, lock1); + } + + private static void doDeadLock(String lock1, String lock2) { + new Thread(() -> { + synchronized (lock1){ + System.out.println("Holding lock1"); + System.out.println("Waiting lock2"); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + synchronized (lock2){ + System.out.println("Holding lock2"); + } + } + }).start(); + } + + private void inc() { + double sin = Math.sin(123); + + atomicCounter.incrementAndGet(); + +// lock.lock(); +// try { +// counter++; +// } finally { +// lock.unlock(); +// } + + /*synchronized (this) { + counter++; + }*/ + } +} diff --git a/src/ru/javaops/webapp/MainDate.java b/src/ru/javaops/webapp/MainDate.java new file mode 100644 index 00000000..c9a54b4f --- /dev/null +++ b/src/ru/javaops/webapp/MainDate.java @@ -0,0 +1,32 @@ +package ru.javaops.webapp; + +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Calendar; +import java.util.Date; + +public class MainDate { + public static void main(String[] args) { + long start = System.currentTimeMillis(); + Date date = new Date(); + System.out.println(date); + System.out.println(System.currentTimeMillis() - start); + Calendar cal = Calendar.getInstance(); + System.out.println(cal.getTime()); + + LocalDate localDate = LocalDate.now(); + LocalTime localTime = LocalTime.now(); + LocalDateTime localDateTime = LocalDateTime.now(); + System.out.println(localDateTime); + System.out.println(localDate + " - " + localTime); + + SimpleDateFormat sdf = new SimpleDateFormat("yy/MM/dd"); + System.out.println(sdf.format(date)); + + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd"); + System.out.println(dtf.format(localDateTime)); + } +} diff --git a/src/ru/javaops/webapp/MainDeadLockExample.java b/src/ru/javaops/webapp/MainDeadLockExample.java new file mode 100644 index 00000000..ed83474f --- /dev/null +++ b/src/ru/javaops/webapp/MainDeadLockExample.java @@ -0,0 +1,73 @@ +package ru.javaops.webapp; + +public class MainDeadLockExample { + + public static void main(String[] args) { + Account accountA = new Account(1, "AAA", 100000.); + Account accountB = new Account(2, "BBB", 50000.); + + for (int i = 1; i < 100000; i++) { + Thread thread01 = new Thread(() -> { + doTransfer(accountA, accountB, 1); + System.out.println(accountA.getAmount() + " " + accountB.getAmount()); + }); + thread01.start(); + + Thread thread02 = new Thread(() -> { + doTransfer(accountB, accountA, 1); + System.out.println(accountA.getAmount() + " " + accountB.getAmount()); + }); + thread02.start(); + } + } + + private static void doTransfer(Account fromAcc, Account toAcc, double amount) { + synchronized (fromAcc) { + if (fromAcc.getAmount() < amount) { + System.out.println("Error, not enough amount at account ID: " + fromAcc.getId()); + } else { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + synchronized (toAcc) { + fromAcc.credit(amount); + toAcc.debit(amount); + } + } + } + } + + private static class Account { + private final int id; + private final String owner; + private double amount; + + Account(int id, String owner, double amount) { + this.id = id; + this.owner = owner; + this.amount = amount; + } + + public int getId() { + return id; + } + + public String getOwner() { + return owner; + } + + public double getAmount() { + return amount; + } + + private void debit(double amount) { + this.amount += amount; + } + + private void credit(double amount) { + this.amount -= amount; + } + } +} diff --git a/src/ru/javaops/webapp/MainFile.java b/src/ru/javaops/webapp/MainFile.java new file mode 100644 index 00000000..69bc2407 --- /dev/null +++ b/src/ru/javaops/webapp/MainFile.java @@ -0,0 +1,43 @@ +package ru.javaops.webapp; + +import ru.javaops.webapp.exception.StorageException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + + +public class MainFile { + public static void main(String[] args) { + File filePath = new File("./.gitignore"); + try { + System.out.println(filePath.getCanonicalPath()); + } catch (IOException e) { + throw new RuntimeException("Error", e); + } + + try (FileInputStream fis = new FileInputStream(filePath)) { + System.out.println(fis.read()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + File dir = new File("./src/ru/javaops"); // /webapp + getDirectoryFileNames(dir, ""); + } + + private static void getDirectoryFileNames(File dir, String space) { + File[] files = dir.listFiles(); + if (files == null) { + throw new StorageException(dir.getName() + " is not directory, or IO Error"); + } + for (File file : files) { + if (file.isFile()) { + System.out.println(space + file.getName()); + } else { + System.out.println(space + "[" + file.getName() + "]"); + getDirectoryFileNames(file, space + " "); + } + } + } +} diff --git a/src/ru/javaops/webapp/MainReflection.java b/src/ru/javaops/webapp/MainReflection.java new file mode 100644 index 00000000..857db452 --- /dev/null +++ b/src/ru/javaops/webapp/MainReflection.java @@ -0,0 +1,29 @@ +package ru.javaops.webapp; + +import ru.javaops.webapp.model.Resume; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class MainReflection { + @SuppressWarnings("unchecked") + public static void main(String[] args) throws IllegalAccessException { + Resume r = new Resume("dummy"); + Field field = r.getClass().getDeclaredFields()[0]; + field.setAccessible(true); + System.out.println(field.getName()); + System.out.println(field.get(r)); + field.set(r, "new_uuid"); + System.out.println(r); + + Class resumeClass = Resume.class; + try { + Method method = resumeClass.getDeclaredMethod("toString", (Class[]) null); + System.out.println(method.invoke(r, (Object[]) null)); + } catch (NoSuchMethodException | InvocationTargetException e) { + System.out.println(e.getMessage()); + } + + } +} diff --git a/src/ru/javaops/webapp/MainStreams.java b/src/ru/javaops/webapp/MainStreams.java new file mode 100644 index 00000000..3d4dfc34 --- /dev/null +++ b/src/ru/javaops/webapp/MainStreams.java @@ -0,0 +1,25 @@ +package ru.javaops.webapp; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class MainStreams { + public static void main(String[] args) { + System.out.println(minValue(new int[]{9, 8})); + System.out.println(oddOrEven(Arrays.asList(1, 2, 3, 3, 2, 3))); + } + + private static int minValue(int[] values) { + return Arrays.stream(values) + .distinct() + .sorted() + .reduce((s1, s2) -> s1 * 10 + s2) + .orElse(0); + } + + private static List oddOrEven(List integers) { + int sum = integers.stream().mapToInt(Integer::intValue).sum(); + return integers.stream().filter(s -> s % 2 != sum % 2).collect(Collectors.toList()); + } +} diff --git a/src/ru/javaops/webapp/MainTestArrayStorage.java b/src/ru/javaops/webapp/MainTestArrayStorage.java new file mode 100644 index 00000000..267e8b5f --- /dev/null +++ b/src/ru/javaops/webapp/MainTestArrayStorage.java @@ -0,0 +1,51 @@ +package ru.javaops.webapp; + +import ru.javaops.webapp.model.Resume; +import ru.javaops.webapp.storage.IStorage; +import ru.javaops.webapp.storage.SortedArrayStorage; + +public class MainTestArrayStorage { + private static final IStorage ARRAY_STORAGE = new SortedArrayStorage(); + //private static final IStorage ARRAY_STORAGE = new ArrayStorage(); + + public static void main(String[] args) { + final Resume r1 = new Resume("uuid3", ""); + final Resume r2 = new Resume("uuid1", ""); + final Resume r3 = new Resume("uuid2", ""); + + + ARRAY_STORAGE.save(r1); + ARRAY_STORAGE.save(r2); + ARRAY_STORAGE.save(r3); + + System.out.println("Get r1: " + ARRAY_STORAGE.get(r1.getUuid())); + System.out.println("Size: " + ARRAY_STORAGE.size()); + + System.out.println("Get dummy: " + ARRAY_STORAGE.get("dummy")); + + //Test update method + printAll(); + ARRAY_STORAGE.update(r3); + + printAll(); + ARRAY_STORAGE.delete(r1.getUuid()); + printAll(); + ARRAY_STORAGE.clear(); + printAll(); + + System.out.println("Size: " + ARRAY_STORAGE.size()); + + //Test storage overflow + for (int i = 0; i < 100001; i++){ + Resume resume = new Resume("uuid" + i, "uuid" + i); + ARRAY_STORAGE.save(resume); + } + } + + private static void printAll() { + System.out.println("\nGet All"); + for (Resume r : ARRAY_STORAGE.getAllSorted()) { + System.out.println(r); + } + } +} diff --git a/src/ru/javaops/webapp/TestSingleton.java b/src/ru/javaops/webapp/TestSingleton.java new file mode 100644 index 00000000..8737543f --- /dev/null +++ b/src/ru/javaops/webapp/TestSingleton.java @@ -0,0 +1,25 @@ +package ru.javaops.webapp; + +public class TestSingleton { + private static TestSingleton instance; + + public static TestSingleton getInstance() { + if (instance == null){ + instance = new TestSingleton(); + } + return instance; + } + + private TestSingleton() { + } + + public static void main(String[] args) { + TestSingleton.getInstance().toString(); + Singleton instance = Singleton.valueOf("SECOND"); + System.out.println(instance.ordinal()); + } + + public enum Singleton { + INSTANCE, SECOND + } +} diff --git a/src/ru/javaops/webapp/exception/ExistStorageException.java b/src/ru/javaops/webapp/exception/ExistStorageException.java new file mode 100644 index 00000000..cbbb4c03 --- /dev/null +++ b/src/ru/javaops/webapp/exception/ExistStorageException.java @@ -0,0 +1,7 @@ +package ru.javaops.webapp.exception; + +public class ExistStorageException extends StorageException { + public ExistStorageException(String uuid) { + super("Error: resume with uuid: " + uuid + " already exist!", uuid); + } +} diff --git a/src/ru/javaops/webapp/exception/NotExistStorageException.java b/src/ru/javaops/webapp/exception/NotExistStorageException.java new file mode 100644 index 00000000..5c3cae41 --- /dev/null +++ b/src/ru/javaops/webapp/exception/NotExistStorageException.java @@ -0,0 +1,7 @@ +package ru.javaops.webapp.exception; + +public class NotExistStorageException extends StorageException { + public NotExistStorageException(String uuid) { + super("Error: resume with uuid: " + uuid + " not found!", uuid); + } +} diff --git a/src/ru/javaops/webapp/exception/StorageException.java b/src/ru/javaops/webapp/exception/StorageException.java new file mode 100644 index 00000000..6636d33d --- /dev/null +++ b/src/ru/javaops/webapp/exception/StorageException.java @@ -0,0 +1,31 @@ +package ru.javaops.webapp.exception; + +public class StorageException extends RuntimeException { + private final String uuid; + + public StorageException(String uuid){ + this.uuid = uuid; + } + + public StorageException(Exception e) { + this(e.getMessage(), e); + } + + public StorageException(String message, String uuid) { + super(message); + this.uuid = uuid; + } + + public StorageException(String message, Exception e) { + this(message, null, e); + } + + public StorageException(String message, String uuid, Exception e) { + super(message, e); + this.uuid = uuid; + } + + public String getUuid() { + return uuid; + } +} diff --git a/src/ru/javaops/webapp/model/ContactType.java b/src/ru/javaops/webapp/model/ContactType.java new file mode 100644 index 00000000..6d7a5627 --- /dev/null +++ b/src/ru/javaops/webapp/model/ContactType.java @@ -0,0 +1,74 @@ +package ru.javaops.webapp.model; + +public enum ContactType { + PHONE("Тел."), + MOBILE_PHONE("Мобильный тел."), + EMAIL("E-mail"){ + @Override + public String toHtml0(String value) { + return getTitle() + ": " + toLink("mailto:" + value, value); + } + }, + TELEGRAM("Telegram"){ + @Override + public String toHtml0(String value) { + return getTitle() + ": " + toLink("tg://resolve?domain=" + value, value); + } + }, + SKYPE("Skype"){ + @Override + public String toHtml0(String value) { + return getTitle() + ": " + toLink("skype:" + value, value); + } + }, + LINKEDIN("LinkedIn"){ + @Override + public String toHtml0(String value) { + return getTitle() + ": " + toLink("https://linkedin.com/" + value, value); + } + }, + GITHUB("GitHub"){ + @Override + public String toHtml0(String value) { + return getTitle() + ": " + toLink("https://github.com/" + value, value); + } + }, + STACKOVERFLOW("Stackoverflow"){ + @Override + public String toHtml0(String value) { + return getTitle() + ": " + toLink("https://stackoverflow.com/" + value, value); + } + }, + HOME_PAGE("Домашняя страница"){ + @Override + public String toHtml0(String value) { + return getTitle() + ": " + toLink("https://" + value, value); + } + }; + + private String title; + + ContactType(String title){ + this.title = title; + } + + public String getTitle() { + return title; + } + + protected String toHtml0(String value) { + return title + ": " + value; + } + + public String toHtml(String value) { + return (value == null) ? "" : toHtml0(value); + } + + public String toLink(String href) { + return toLink(href, title); + } + + public static String toLink(String href, String title) { + return "" + title + ""; + } +} diff --git a/src/ru/javaops/webapp/model/ListSection.java b/src/ru/javaops/webapp/model/ListSection.java new file mode 100644 index 00000000..486aa90d --- /dev/null +++ b/src/ru/javaops/webapp/model/ListSection.java @@ -0,0 +1,50 @@ +package ru.javaops.webapp.model; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class ListSection extends Section { + private static final long serialVersionUID = 1L; + + public static final ListSection EMPTY = new ListSection(""); + + private List items; + + public ListSection() { + } + + public ListSection(String... items){ + this(Arrays.asList(items)); + } + + public ListSection(List items) { + Objects.requireNonNull(items, "items can't be NULL"); + this.items = items; + } + + public List getItems() { + return items; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ListSection that = (ListSection) o; + return Objects.equals(items, that.items); + } + + @Override + public int hashCode() { + return Objects.hash(items); + } + + @Override + public String toString() { + return items.toString(); +// return "ListSection{" + +// "items=" + items + +// '}'; + } +} diff --git a/src/ru/javaops/webapp/model/Organisation.java b/src/ru/javaops/webapp/model/Organisation.java new file mode 100644 index 00000000..9b6462da --- /dev/null +++ b/src/ru/javaops/webapp/model/Organisation.java @@ -0,0 +1,153 @@ +package ru.javaops.webapp.model; + +import ru.javaops.webapp.util.LocalDateAdapter; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.io.Serializable; +import java.time.LocalDate; +import java.time.Month; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import static ru.javaops.webapp.util.DateUtil.NOW; +import static ru.javaops.webapp.util.DateUtil.of; + +@XmlAccessorType(XmlAccessType.FIELD) +public class Organisation implements Serializable { + private static final long serialVersionUID = 1L; + + public static final Organisation EMPTY = new Organisation("", "", Position.EMPTY); + + private String name; + private String url; + private List positions; + + public Organisation() { + } + + public Organisation(String name, String url, Position... positions){ + this(name, url, Arrays.asList(positions)); + } + + public Organisation(String name, String url, List positions){ + Objects.requireNonNull(name, "name can't be NULL"); + this.name = name; + this.url = url == null ? "" : url; + this.positions = positions; + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + public List getPositions() { + return positions; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Organisation that = (Organisation) o; + return Objects.equals(name, that.name) && + Objects.equals(url, that.url) && + Objects.equals(positions, that.positions); + } + + @Override + public int hashCode() { + return Objects.hash(name, url, positions); + } + + @Override + public String toString() { + return "Organisation: " + name + " (" + url + ", " + positions + ")"; +// return "Organisation{" + +// "name='" + name + '\'' + +// ", url='" + url + '\'' + +// ", positions=" + positions + +// '}'; + } + + @XmlAccessorType(XmlAccessType.FIELD) + public static class Position implements Serializable { + private static final long serialVersionUID = 1L; + + public static final Position EMPTY = new Position(); + + @XmlJavaTypeAdapter(LocalDateAdapter.class) + private LocalDate startDate; + @XmlJavaTypeAdapter(LocalDateAdapter.class) + private LocalDate endDate; + private String head; + private String description; + + public Position() { + } + + public Position(int startYear, Month startMonth, String head, String description) { + this(of(startYear, startMonth), NOW, head, description); + } + + public Position(int startYear, Month startMonth, int endYear, Month endMonth, String head, String description) { + this(of(startYear, startMonth), of(endYear, endMonth), head, description); + } + + public Position(LocalDate startDate, LocalDate endDate, String head, String description) { + Objects.requireNonNull(startDate, "startDate can't be NULL"); + Objects.requireNonNull(head, "head can't be NULL"); + this.startDate = startDate; + this.endDate = endDate; + this.head = head; + this.description = description == null ? "" : description; + } + + public LocalDate getStartDate() { + return startDate; + } + + public LocalDate getEndDate() { + return endDate; + } + + public String getHead() { + return head; + } + + public String getDescription() { + return description; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Position position = (Position) o; + return Objects.equals(startDate, position.startDate) && + Objects.equals(endDate, position.endDate) && + Objects.equals(head, position.head) && + Objects.equals(description, position.description); + } + + @Override + public int hashCode() { + return Objects.hash(startDate, endDate, head, description); + } + + @Override + public String toString() { + return "Position: " + + "startDate=" + startDate + + ", endDate=" + endDate + + ", head='" + head + '\'' + + ", description='" + description + '\''; + } + } +} diff --git a/src/ru/javaops/webapp/model/OrganisationSection.java b/src/ru/javaops/webapp/model/OrganisationSection.java new file mode 100644 index 00000000..a7c5efe1 --- /dev/null +++ b/src/ru/javaops/webapp/model/OrganisationSection.java @@ -0,0 +1,48 @@ +package ru.javaops.webapp.model; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class OrganisationSection extends Section { + private static final long serialVersionUID = 1L; + + private List organisations; + + public OrganisationSection() { + } + + public OrganisationSection(Organisation... organisations){ + this(Arrays.asList(organisations)); + } + + public OrganisationSection(List organisations) { + Objects.requireNonNull(organisations, "organisations can't be NULL"); + this.organisations = organisations; + } + + public List getOrganisations() { + return organisations; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OrganisationSection that = (OrganisationSection) o; + return Objects.equals(organisations, that.organisations); + } + + @Override + public int hashCode() { + return Objects.hash(organisations); + } + + @Override + public String toString() { + return organisations.toString(); +// return "OrganisationSection{" + +// "organisations=" + organisations + +// '}'; + } +} diff --git a/src/ru/javaops/webapp/model/Resume.java b/src/ru/javaops/webapp/model/Resume.java new file mode 100644 index 00000000..52fdd11d --- /dev/null +++ b/src/ru/javaops/webapp/model/Resume.java @@ -0,0 +1,110 @@ +package ru.javaops.webapp.model; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.*; + +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class Resume implements Comparable, Serializable { + private static final long serialVersionUID = 1L; + + public static final Resume EMPTY = new Resume(); + + static { + EMPTY.setSection(SectionType.OBJECTIVE, TextSection.EMPTY); + EMPTY.setSection(SectionType.PERSONAL, TextSection.EMPTY); + EMPTY.setSection(SectionType.ACHIEVEMENT, ListSection.EMPTY); + EMPTY.setSection(SectionType.QUALIFICATIONS, ListSection.EMPTY); + EMPTY.setSection(SectionType.EXPERIENCE, new OrganisationSection(Organisation.EMPTY)); + EMPTY.setSection(SectionType.EDUCATION, new OrganisationSection(Organisation.EMPTY)); + } + + // Unique identifier + private String uuid; + + private String fullName; + + private final Map contacts = new EnumMap<>(ContactType.class); + private final Map sections = new EnumMap<>(SectionType.class); + + public Resume() { + } + + public Resume(String fullName){ + this((UUID.randomUUID().toString()), fullName); + } + + public Resume(String uuid, String fullName){ + Objects.requireNonNull(uuid, "UUID can't be NULL"); + Objects.requireNonNull(fullName, "fullName can't be NULL"); + this.uuid = uuid; + this.fullName = fullName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Resume resume = (Resume) o; + return Objects.equals(uuid, resume.uuid) && + Objects.equals(fullName, resume.fullName) && + Objects.equals(contacts, resume.contacts) && + Objects.equals(sections, resume.sections); + } + + @Override + public int hashCode() { + return Objects.hash(uuid, fullName, contacts, sections); + } + + @Override + public int compareTo(Resume o) { + int comp = this.fullName.compareTo(o.fullName); + return comp != 0 ? comp : this.uuid.compareTo(o.uuid); + } + + public String getUuid() { + return uuid; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName){ + this.fullName = fullName; + } + + public String getContact(ContactType type) { + return contacts.get(type); + } + + public Section getSection(SectionType type) { + return sections.get(type); + } + + public void setContact(ContactType type, String value) { + this.contacts.put(type, value); + } + + public void setSection(SectionType type, Section value) { + this.sections.put(type, value); + } + + public Map getContacts() { + return contacts; + } + + public Map getSections() { + return sections; + } + + @Override + public String toString() { + return uuid + " (" + fullName + ")"; + } + +} diff --git a/src/ru/javaops/webapp/model/Section.java b/src/ru/javaops/webapp/model/Section.java new file mode 100644 index 00000000..3c00573a --- /dev/null +++ b/src/ru/javaops/webapp/model/Section.java @@ -0,0 +1,9 @@ +package ru.javaops.webapp.model; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import java.io.Serializable; + +@XmlAccessorType(XmlAccessType.FIELD) +public abstract class Section implements Serializable { +} diff --git a/src/ru/javaops/webapp/model/SectionType.java b/src/ru/javaops/webapp/model/SectionType.java new file mode 100644 index 00000000..9e790ee3 --- /dev/null +++ b/src/ru/javaops/webapp/model/SectionType.java @@ -0,0 +1,66 @@ +package ru.javaops.webapp.model; + +public enum SectionType { + OBJECTIVE("Позиция"){ + @Override + public String toHtml0(String value) { + return value; + } + }, + PERSONAL("Личные качества"){ + @Override + public String toHtml0(String value) { + return value; + } + }, + ACHIEVEMENT("Достижения"){ + @Override + public String toHtml0(String value) { + return value; + } + }, + QUALIFICATIONS("Квалификация"){ + @Override + public String toHtml0(String value) { + return value; + } + }, + EXPERIENCE("Опыт работы"){ + @Override + public String toHtml0(String value) { + return value; + } + }, + EDUCATION("Образование"){ + @Override + public String toHtml0(String value) { + return value; + } + }; + + private String title; + + SectionType(String title){ + this.title = title; + } + + public String getTitle() { + return title; + } + + public String toHtml(String value) { + return (value == null) ? "" : toHtml0(value); + } + + public String toHtml(TextSection value) { + return (value == null) ? "" : toHtml0(value.getText()); + } + + public String toHtml(OrganisationSection value) { + return (value == null) ? "" : toHtml0(value.toString()); + } + + protected String toHtml0(String value) { + return title + ": " + value; + } +} diff --git a/src/ru/javaops/webapp/model/TextSection.java b/src/ru/javaops/webapp/model/TextSection.java new file mode 100644 index 00000000..d227f59d --- /dev/null +++ b/src/ru/javaops/webapp/model/TextSection.java @@ -0,0 +1,41 @@ +package ru.javaops.webapp.model; + +import java.util.Objects; + +public class TextSection extends Section { + private static final long serialVersionUID = 1L; + + public static final TextSection EMPTY = new TextSection(""); + + private String text; + + public TextSection() { + } + + public TextSection(String text) { + Objects.requireNonNull(text, "text can't be NULL"); + this.text = text; + } + + public String getText() { + return text; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TextSection that = (TextSection) o; + return Objects.equals(text, that.text); + } + + @Override + public int hashCode() { + return Objects.hash(text); + } + + @Override + public String toString() { + return text; + } +} diff --git a/src/ru/javaops/webapp/sql/ExceptionUtil.java b/src/ru/javaops/webapp/sql/ExceptionUtil.java new file mode 100644 index 00000000..facc37a7 --- /dev/null +++ b/src/ru/javaops/webapp/sql/ExceptionUtil.java @@ -0,0 +1,23 @@ +package ru.javaops.webapp.sql; + +import org.postgresql.util.PSQLException; +import ru.javaops.webapp.exception.ExistStorageException; +import ru.javaops.webapp.exception.StorageException; + +import java.sql.SQLException; + +public class ExceptionUtil { + private ExceptionUtil() { + } + + public static StorageException convertException(SQLException e) { + if (e instanceof PSQLException) { + +// http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html + if (e.getSQLState().equals("23505")) { + return new ExistStorageException(null); + } + } + return new StorageException(e); + } +} diff --git a/src/ru/javaops/webapp/sql/IConnectionFactory.java b/src/ru/javaops/webapp/sql/IConnectionFactory.java new file mode 100644 index 00000000..45d77f34 --- /dev/null +++ b/src/ru/javaops/webapp/sql/IConnectionFactory.java @@ -0,0 +1,8 @@ +package ru.javaops.webapp.sql; + +import java.sql.Connection; +import java.sql.SQLException; + +public interface IConnectionFactory { + Connection getConnection() throws SQLException; +} diff --git a/src/ru/javaops/webapp/sql/ISqlExecutor.java b/src/ru/javaops/webapp/sql/ISqlExecutor.java new file mode 100644 index 00000000..1fade9d9 --- /dev/null +++ b/src/ru/javaops/webapp/sql/ISqlExecutor.java @@ -0,0 +1,8 @@ +package ru.javaops.webapp.sql; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public interface ISqlExecutor { + T execute(PreparedStatement st) throws SQLException; +} diff --git a/src/ru/javaops/webapp/sql/ISqlTransaction.java b/src/ru/javaops/webapp/sql/ISqlTransaction.java new file mode 100644 index 00000000..9919fb09 --- /dev/null +++ b/src/ru/javaops/webapp/sql/ISqlTransaction.java @@ -0,0 +1,8 @@ +package ru.javaops.webapp.sql; + +import java.sql.Connection; +import java.sql.SQLException; + +public interface ISqlTransaction { + T execute(Connection conn) throws SQLException; +} diff --git a/src/ru/javaops/webapp/sql/SqlHelper.java b/src/ru/javaops/webapp/sql/SqlHelper.java new file mode 100644 index 00000000..d5f49e3d --- /dev/null +++ b/src/ru/javaops/webapp/sql/SqlHelper.java @@ -0,0 +1,45 @@ +package ru.javaops.webapp.sql; + + +import ru.javaops.webapp.exception.StorageException; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public class SqlHelper { + private final IConnectionFactory connectionFactory; + + public SqlHelper(IConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + public void execute(String sql) { + execute(sql, PreparedStatement::execute); + } + + public T execute(String sql, ISqlExecutor executor) { + try (Connection conn = connectionFactory.getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + return executor.execute(ps); + } catch (SQLException e) { + throw ExceptionUtil.convertException(e); + } + } + + public T transactionalExecute(ISqlTransaction executor) { + try (Connection conn = connectionFactory.getConnection()) { + try { + conn.setAutoCommit(false); + T res = executor.execute(conn); + conn.commit(); + return res; + } catch (SQLException e) { + conn.rollback(); + throw ExceptionUtil.convertException(e); + } + } catch (SQLException e) { + throw new StorageException(e); + } + } +} diff --git a/src/ru/javaops/webapp/storage/AbstractArrayStorage.java b/src/ru/javaops/webapp/storage/AbstractArrayStorage.java new file mode 100644 index 00000000..26e778fd --- /dev/null +++ b/src/ru/javaops/webapp/storage/AbstractArrayStorage.java @@ -0,0 +1,67 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.exception.StorageException; +import ru.javaops.webapp.model.Resume; + +import java.util.*; + + +public abstract class AbstractArrayStorage extends AbstractStorage { + protected static final int STORAGE_LIMIT = 100000; + + protected final Resume[] storage = new Resume[STORAGE_LIMIT]; + protected int size = 0; + + @Override + protected abstract Integer getSearchKey(String uuid); + + protected abstract void putResume(int index, Resume resume); + + protected abstract void removeResume(int index); + + public int size() { + return size; + } + + public void clear() { + Arrays.fill(storage, 0, size, null); + size = 0; + } + + @Override + protected void doSave(Resume resume, Integer index) { + if (size >= STORAGE_LIMIT) { + throw new StorageException("Error: resume storage is full!", resume.getUuid()); + } else { + putResume(index, resume); + size++; + } + } + + @Override + protected boolean isExist(Integer index) { + return index >= 0; + } + + @Override + public List doCopyAll() { + return Arrays.asList(Arrays.copyOfRange(storage, 0, size)); + } + + @Override + protected Resume doGet(Integer index) { + return storage[index]; + } + + @Override + protected void doUpdate(Resume resume, Integer index) { + storage[index] = resume; + } + + @Override + protected void doDelete(Integer index) { + size--; + removeResume(index); + storage[size] = null; + } +} diff --git a/src/ru/javaops/webapp/storage/AbstractStorage.java b/src/ru/javaops/webapp/storage/AbstractStorage.java new file mode 100644 index 00000000..2effdd3f --- /dev/null +++ b/src/ru/javaops/webapp/storage/AbstractStorage.java @@ -0,0 +1,79 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.exception.ExistStorageException; +import ru.javaops.webapp.exception.NotExistStorageException; +import ru.javaops.webapp.model.Resume; + +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; + + +public abstract class AbstractStorage implements IStorage{ + + private static final Logger LOG = Logger.getLogger(AbstractStorage.class.getName()); + + protected abstract void doSave(Resume resume, SK searchKey); + + protected abstract SK getSearchKey(String uuid); + + protected abstract Resume doGet(SK searchKey); + + protected abstract void doUpdate(Resume resume, SK searchKey); + + protected abstract void doDelete(SK searchKey); + + protected abstract boolean isExist(SK searchKey); + + protected abstract List doCopyAll(); + + public void save(Resume resume) { + LOG.info("Save " + resume); + SK searchKey = getNotExistSearchKey(resume.getUuid()); + doSave(resume, searchKey); + } + + public Resume get(String uuid) { + LOG.info("Get " + uuid); + SK searchKey = getExistSearchKey(uuid); + return doGet(searchKey); + } + + public void update(Resume resume) { + LOG.info("Update " + resume); + SK searchKey = getExistSearchKey(resume.getUuid()); + doUpdate(resume, searchKey); + } + + public void delete(String uuid) { + LOG.info("Delete " + uuid); + SK searchKey = getExistSearchKey(uuid); + doDelete(searchKey); + } + + protected SK getExistSearchKey(String uuid){ + SK searchKey = getSearchKey(uuid); + if (!isExist(searchKey)){ + LOG.warning("Resume " + uuid + " not exist."); + throw new NotExistStorageException(uuid); + } + return searchKey; + } + + @Override + public List getAllSorted() { + LOG.info("getAllSorted"); + List list = doCopyAll(); + Collections.sort(list); + return list; + } + + protected SK getNotExistSearchKey(String uuid){ + SK searchKey = getSearchKey(uuid); + if (isExist(searchKey)){ + LOG.warning("Resume " + uuid + " is exist."); + throw new ExistStorageException(uuid); + } + return searchKey; + } +} diff --git a/src/ru/javaops/webapp/storage/ArrayStorage.java b/src/ru/javaops/webapp/storage/ArrayStorage.java new file mode 100644 index 00000000..d1b7e311 --- /dev/null +++ b/src/ru/javaops/webapp/storage/ArrayStorage.java @@ -0,0 +1,26 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.model.Resume; + +public class ArrayStorage extends AbstractArrayStorage { + + @Override + protected Integer getSearchKey(String uuid) { + for (int i = 0; i < size; i++) { + if (storage[i].getUuid().equals(uuid)) { + return i; + } + } + return -1; + } + + @Override + protected void putResume(int index, Resume resume) { + storage[size] = resume; + } + + @Override + protected void removeResume(int index) { + storage[index] = storage[size]; + } +} diff --git a/src/ru/javaops/webapp/storage/FileStorage.java b/src/ru/javaops/webapp/storage/FileStorage.java new file mode 100644 index 00000000..c0f320f7 --- /dev/null +++ b/src/ru/javaops/webapp/storage/FileStorage.java @@ -0,0 +1,106 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.exception.StorageException; +import ru.javaops.webapp.model.Resume; +import ru.javaops.webapp.storage.serialize.ISerializeStrategy; +import ru.javaops.webapp.storage.serialize.StreamSerializer; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class FileStorage extends AbstractStorage { + private File directory; + private ISerializeStrategy streamSerializer; + + protected FileStorage(File directory, ISerializeStrategy streamSerializer) { + Objects.requireNonNull(directory, "directory must not be null"); + if (!directory.isDirectory()) { + throw new IllegalArgumentException(directory.getAbsolutePath() + " is not directory"); + } + if (!directory.canRead() || !directory.canWrite()) { + throw new IllegalArgumentException(directory.getAbsolutePath() + " is not readable/writable"); + } + this.directory = directory; + this.streamSerializer = streamSerializer; + } + + @Override + protected void doSave(Resume resume, File file) { + try { + if (file.createNewFile()) { + doUpdate(resume, file); + } + } catch (IOException e) { + throw new StorageException("File create error " + file.getAbsolutePath(), file.getName(), e); + } + } + + @Override + protected File getSearchKey(String uuid) { + return new File(directory, uuid); + } + + @Override + protected Resume doGet(File file) { + try { + return streamSerializer.doRead(new BufferedInputStream(new FileInputStream(file))); + } catch (IOException e) { + throw new StorageException("File read error", file.getName(), e); + } + } + + @Override + protected void doUpdate(Resume resume, File file) { + try { + streamSerializer.doWrite(resume, new BufferedOutputStream(new FileOutputStream(file))); + } catch (IOException e) { + throw new StorageException("File write error", file.getName(), e); + } + } + + @Override + protected void doDelete(File file) { + if (!file.delete()) { + throw new StorageException("File delete error", file.getName()); + } + } + + @Override + protected boolean isExist(File file) { + return file.exists(); + } + + @Override + protected List doCopyAll() { + List resumes = new ArrayList<>(); + File[] files = getFiles(); + for (File file : files) { + resumes.add(doGet(file)); + } + return resumes; + } + + @Override + public void clear() { + File[] files = getFiles(); + for (File file : files) { + doDelete(file); + } + } + + @Override + public int size() { + File[] files = getFiles(); + return files.length; + } + + private File[] getFiles() { + File[] files = directory.listFiles(); + if (files == null) { + throw new StorageException("Directory error " + directory.getName()); + } + return files; + } +} diff --git a/src/ru/javaops/webapp/storage/IStorage.java b/src/ru/javaops/webapp/storage/IStorage.java new file mode 100644 index 00000000..d8c0049b --- /dev/null +++ b/src/ru/javaops/webapp/storage/IStorage.java @@ -0,0 +1,22 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.model.Resume; + +import java.util.List; + +public interface IStorage { + + void clear(); + + Resume get(String uuid); + + void update(Resume resume); + + void save(Resume resume); + + void delete(String uuid); + + List getAllSorted(); + + int size(); +} diff --git a/src/ru/javaops/webapp/storage/ListStorage.java b/src/ru/javaops/webapp/storage/ListStorage.java new file mode 100644 index 00000000..5c32886e --- /dev/null +++ b/src/ru/javaops/webapp/storage/ListStorage.java @@ -0,0 +1,61 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.model.Resume; + +import java.util.ArrayList; +import java.util.List; + +public class ListStorage extends AbstractStorage { + + private final List storage = new ArrayList<>(); + + @Override + protected void doSave(Resume resume, Integer index) { + storage.add(resume); + } + + @Override + protected Integer getSearchKey(String uuid) { + for (int i = 0; i < storage.size(); i++){ + if (storage.get(i).getUuid().equals(uuid)){ + return i; + } + } + return null; + } + + @Override + protected Resume doGet(Integer index) { + return storage.get(index); + } + + @Override + protected void doUpdate(Resume resume, Integer index) { + storage.set(index, resume); + } + + @Override + protected void doDelete(Integer index) { + storage.remove(index.intValue()); + } + + @Override + protected boolean isExist(Integer index) { + return (index != null); + } + + @Override + public List doCopyAll() { + return new ArrayList<>(storage); + } + + @Override + public void clear() { + storage.clear(); + } + + @Override + public int size() { + return storage.size(); + } +} diff --git a/src/ru/javaops/webapp/storage/MapResumeStorage.java b/src/ru/javaops/webapp/storage/MapResumeStorage.java new file mode 100644 index 00000000..adfafe22 --- /dev/null +++ b/src/ru/javaops/webapp/storage/MapResumeStorage.java @@ -0,0 +1,56 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.model.Resume; + +import java.util.*; + +public class MapResumeStorage extends AbstractStorage { + + private Map storage = new HashMap<>(); + + @Override + protected void doSave(Resume newResume, Resume resume) { + storage.put(newResume.getUuid(), newResume); + } + + @Override + protected Resume getSearchKey(String uuid) { + return storage.get(uuid); + } + + @Override + protected Resume doGet(Resume resume) { + return resume; + } + + @Override + protected void doUpdate(Resume newResume, Resume resume) { + storage.put(newResume.getUuid(), newResume); + } + + @Override + protected void doDelete(Resume resume) { + storage.remove(resume.getUuid()); + } + + @Override + protected boolean isExist(Resume resume) { + return resume != null; + } + + @Override + public List doCopyAll() { + return new ArrayList<>(storage.values()); + } + + + @Override + public void clear() { + storage.clear(); + } + + @Override + public int size() { + return storage.size(); + } +} diff --git a/src/ru/javaops/webapp/storage/MapUuidStorage.java b/src/ru/javaops/webapp/storage/MapUuidStorage.java new file mode 100644 index 00000000..89ac85ab --- /dev/null +++ b/src/ru/javaops/webapp/storage/MapUuidStorage.java @@ -0,0 +1,56 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.model.Resume; + +import java.util.*; + + +public class MapUuidStorage extends AbstractStorage { + + private Map storage = new HashMap<>(); + + @Override + protected void doSave(Resume resume, String uuid) { + storage.put(uuid, resume); + } + + @Override + protected String getSearchKey(String uuid) { + return uuid; + } + + @Override + protected Resume doGet(String uuid) { + return storage.get(uuid); + } + + @Override + protected void doUpdate(Resume resume, String uuid) { + storage.replace(uuid, resume); + } + + @Override + protected void doDelete(String uuid) { + storage.remove(uuid); + } + + @Override + protected boolean isExist(String uuid) { + return storage.containsKey(uuid); + } + + @Override + public List doCopyAll() { + return new ArrayList<>(storage.values()); + } + + @Override + public void clear() { + storage.clear(); + } + + @Override + public int size() { + return storage.size(); + } +} diff --git a/src/ru/javaops/webapp/storage/PathStorage.java b/src/ru/javaops/webapp/storage/PathStorage.java new file mode 100644 index 00000000..22c13af0 --- /dev/null +++ b/src/ru/javaops/webapp/storage/PathStorage.java @@ -0,0 +1,106 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.exception.StorageException; +import ru.javaops.webapp.model.Resume; +import ru.javaops.webapp.storage.serialize.ISerializeStrategy; +import ru.javaops.webapp.storage.serialize.StreamSerializer; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class PathStorage extends AbstractStorage { + private Path directory; + private ISerializeStrategy streamSerializer; + + protected PathStorage(String dir, ISerializeStrategy streamSerializer) { + directory = Paths.get(dir); + this.streamSerializer = streamSerializer; + Objects.requireNonNull(directory, "directory must not be null"); + if (!Files.isDirectory(directory)) { + throw new IllegalArgumentException(dir + " is not directory"); + } + if (!Files.isReadable(directory) || !Files.isWritable(directory)) { + throw new IllegalArgumentException(dir + " is not readable/writable"); + } + } + + @Override + protected void doSave(Resume resume, Path path) { + try { + Files.createFile(path); + doUpdate(resume, path); + } catch (IOException e) { + throw new StorageException("File create error " + path.toString(), getFileName(path), e); + } + } + + @Override + protected Path getSearchKey(String uuid) { + return directory.resolve(uuid); + } + + @Override + protected Resume doGet(Path path) { + try { + return streamSerializer.doRead(new BufferedInputStream(Files.newInputStream(path))); + } catch (IOException e) { + throw new StorageException("File read error", getFileName(path), e); + } + } + + @Override + protected void doUpdate(Resume resume, Path path) { + try { + streamSerializer.doWrite(resume, new BufferedOutputStream(Files.newOutputStream(path))); + } catch (IOException e) { + throw new StorageException("File write error", getFileName(path), e); + } + } + + @Override + protected void doDelete(Path path) { + try { + Files.delete(path); + } catch (IOException e) { + throw new StorageException("File delete error", getFileName(path), e); + } + } + + @Override + protected boolean isExist(Path path) { + return Files.exists(path); + } + + @Override + protected List doCopyAll() { + return getFilesList().map(this::doGet).collect(Collectors.toList()); + } + + @Override + public void clear() { + getFilesList().forEach(this::doDelete); + } + + @Override + public int size() { + return (int) getFilesList().count(); + } + + private String getFileName(Path path) { + return path.getFileName().toString(); + } + + private Stream getFilesList() { + try { + return Files.list(directory); + } catch (IOException e) { + throw new StorageException("Files get error", e); + } + } +} diff --git a/src/ru/javaops/webapp/storage/SortedArrayStorage.java b/src/ru/javaops/webapp/storage/SortedArrayStorage.java new file mode 100644 index 00000000..23620f45 --- /dev/null +++ b/src/ru/javaops/webapp/storage/SortedArrayStorage.java @@ -0,0 +1,30 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.model.Resume; + +import java.util.Arrays; +import java.util.Comparator; + +public class SortedArrayStorage extends AbstractArrayStorage{ + + private static final Comparator RESUME_COMPARATOR = Comparator.comparing(Resume::getUuid); + + @Override + protected Integer getSearchKey(String uuid) { + Resume searchResume = new Resume(uuid, ""); + return Arrays.binarySearch(storage, 0, size, searchResume, RESUME_COMPARATOR); + } + + @Override + protected void putResume(int index, Resume resume) { + index = ~index; + System.arraycopy(storage, index, storage, index + 1, size - index); + storage[index] = resume; + } + + @Override + protected void removeResume(int index) { + System.arraycopy(storage, index + 1, storage, index, size - index); + } + +} diff --git a/src/ru/javaops/webapp/storage/SqlStorage.java b/src/ru/javaops/webapp/storage/SqlStorage.java new file mode 100644 index 00000000..521165c2 --- /dev/null +++ b/src/ru/javaops/webapp/storage/SqlStorage.java @@ -0,0 +1,192 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.exception.NotExistStorageException; +import ru.javaops.webapp.model.*; +import ru.javaops.webapp.sql.SqlHelper; +import ru.javaops.webapp.util.JsonParser; + +import java.sql.*; +import java.util.*; + +public class SqlStorage implements IStorage { + private final SqlHelper sqlHelper; + + public SqlStorage(String dbUrl, String dbUser, String dbPassword) { + try { + Class.forName("org.postgresql.Driver"); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + sqlHelper = new SqlHelper(() -> DriverManager.getConnection(dbUrl, dbUser, dbPassword)); + } + + @Override + public void clear() { + sqlHelper.execute("DELETE FROM resume"); + } + + @Override + public Resume get(String uuid) { + return sqlHelper.transactionalExecute(conn -> { + try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM resume r WHERE r.uuid = ?")) { + ps.setString(1, uuid); + ResultSet rs = ps.executeQuery(); + if (!rs.next()) { + throw new NotExistStorageException(uuid); + } + Resume resume = new Resume(uuid, rs.getString("full_name")); + try (PreparedStatement ps2 = conn.prepareStatement("SELECT * FROM resume r LEFT JOIN contact c on r.uuid = c.resume_uuid WHERE c.resume_uuid = ?")) { + ps2.setString(1, uuid); + ResultSet rs2 = ps2.executeQuery(); + if (rs2.next()) { + addContacts(rs2, resume); + } + } + try (PreparedStatement ps1 = conn.prepareStatement("SELECT * FROM resume r LEFT JOIN section s on r.uuid = s.resume_uuid WHERE s.resume_uuid = ?")) { + ps1.setString(1, uuid); + ResultSet rs1 = ps1.executeQuery(); + if (rs1.next()) { + addSections(rs1, resume); + } + } + return resume; + } + }); + } + + @Override + public void update(Resume resume) { + sqlHelper.transactionalExecute(conn -> { + try (PreparedStatement ps = conn.prepareStatement("UPDATE resume SET full_name = ? WHERE uuid = ?")) { + ps.setString(1, resume.getFullName()); + ps.setString(2, resume.getUuid()); + if (ps.executeUpdate() == 0) { + throw new NotExistStorageException(resume.getUuid()); + } + } + deleteContacts(resume, conn); + deleteSections(resume, conn); + insertContacts(resume, conn); + insertSections(resume, conn); + return null; + }); + + } + + @Override + public void save(Resume resume) { + sqlHelper.transactionalExecute(conn -> { + try (PreparedStatement ps = conn.prepareStatement("INSERT INTO resume (uuid, full_name) VALUES (?,?)")) { + ps.setString(1, resume.getUuid()); + ps.setString(2, resume.getFullName()); + ps.execute(); + } + insertContacts(resume, conn); + insertSections(resume, conn); + return null; + }); + } + + @Override + public void delete(String uuid) { + sqlHelper.execute("DELETE FROM resume r WHERE r.uuid = ?", ps -> { + ps.setString(1, uuid); + if (ps.executeUpdate() == 0) { + throw new NotExistStorageException(uuid); + } + return null; + }); + } + + @Override + public List getAllSorted() { + return sqlHelper.transactionalExecute(conn -> { + try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM resume ORDER BY full_name, uuid ASC ")) { + ResultSet rs = ps.executeQuery(); + List resumes = new ArrayList<>(); + while (rs.next()) { + String uuid = rs.getString("uuid").replaceAll("\\s", ""); + resumes.add(get(uuid)); + } + return resumes; + } + }); + } + + @Override + public int size() { + return sqlHelper.execute("SELECT COUNT(*) FROM resume", (ps) -> { + ResultSet rs = ps.executeQuery(); + return rs.next() ? rs.getInt(1) : 0; + }); + } + + private void addContact(ResultSet rs, Resume resume) throws SQLException { + String value = rs.getString("value"); + if (rs.getString("value") != null) { + resume.setContact(ContactType.valueOf(rs.getString("type")), value); + } + } + + private void addSection(ResultSet rs, Resume resume) throws SQLException { + String value = rs.getString("value"); + if (value != null) { + SectionType type = SectionType.valueOf(rs.getString("type")); + resume.setSection(type, JsonParser.read(value, Section.class)); + } + } + + private void insertContacts(Resume resume, Connection conn) throws SQLException { + try (PreparedStatement ps = conn.prepareStatement("INSERT INTO contact (resume_uuid, type, value) VALUES (?,?,?)")) { + for (Map.Entry e : resume.getContacts().entrySet()) { + ps.setString(1, resume.getUuid()); + ps.setString(2, e.getKey().name()); + ps.setString(3, e.getValue()); + ps.addBatch(); + } + ps.executeBatch(); + } + } + + private void insertSections(Resume resume, Connection conn) throws SQLException { + List list; + try (PreparedStatement ps = conn.prepareStatement("INSERT INTO section (resume_uuid, type, value) VALUES (?,?,?)")) { + for (Map.Entry e : resume.getSections().entrySet()) { + ps.setString(1, resume.getUuid()); + ps.setString(2, e.getKey().name()); + Section section = e.getValue(); + ps.setString(3, JsonParser.write(section, Section.class)); + ps.addBatch(); + } + ps.executeBatch(); + } + } + + private void addContacts(ResultSet rs, Resume resume) throws SQLException { + do { + addContact(rs, resume); + } while (rs.next()); + } + + private void addSections(ResultSet rs, Resume resume) throws SQLException { + do { + addSection(rs, resume); + } while (rs.next()); + } + + private void deleteContacts(Resume resume, Connection conn) throws SQLException { + deleteAttributes(resume, conn, "DELETE FROM contact c WHERE c.resume_uuid = ?"); + } + + private void deleteSections(Resume resume, Connection conn) throws SQLException { + deleteAttributes(resume, conn, "DELETE FROM section s WHERE s.resume_uuid = ?"); + } + + private void deleteAttributes(Resume resume, Connection conn, String sql) throws SQLException { + try (PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setString(1, resume.getUuid()); + ps.execute(); + } + } + +} diff --git a/src/ru/javaops/webapp/storage/serialize/DataStreamSerializer.java b/src/ru/javaops/webapp/storage/serialize/DataStreamSerializer.java new file mode 100644 index 00000000..8d5b7890 --- /dev/null +++ b/src/ru/javaops/webapp/storage/serialize/DataStreamSerializer.java @@ -0,0 +1,131 @@ +package ru.javaops.webapp.storage.serialize; + +import ru.javaops.webapp.model.*; + +import java.io.*; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.*; + +public class DataStreamSerializer implements ISerializeStrategy { + @Override + public void doWrite(Resume r, OutputStream os) throws IOException { + try (DataOutputStream dos = new DataOutputStream(os)) { + dos.writeUTF(r.getUuid()); + dos.writeUTF(r.getFullName()); + Map contacts = r.getContacts(); + writeCollection(dos, contacts.entrySet(), entry -> { + dos.writeUTF(entry.getKey().name()); + dos.writeUTF(entry.getValue()); + }); + Map sections = r.getSections(); + writeCollection(dos, sections.entrySet(), entry -> { + SectionType sType = entry.getKey(); + Section section = entry.getValue(); + dos.writeUTF(sType.name()); + switch (sType) { + case PERSONAL: + case OBJECTIVE: + dos.writeUTF(section.toString()); + break; + case ACHIEVEMENT: + case QUALIFICATIONS: + writeCollection(dos, ((ListSection) section).getItems(), dos::writeUTF); + break; + case EXPERIENCE: + case EDUCATION: + writeCollection(dos, ((OrganisationSection) section).getOrganisations(), organisation -> { + dos.writeUTF(organisation.getName()); + dos.writeUTF(organisation.getUrl()); + writeCollection(dos, organisation.getPositions(), position -> { + dos.writeUTF(position.getStartDate().toString()); + dos.writeUTF(position.getEndDate().toString()); + dos.writeUTF(position.getHead()); + dos.writeUTF(position.getDescription()); + }); + }); + break; + } + }); + } + } + + private interface ItemWriter { + void accept(T t) throws IOException; + } + + private void writeCollection(DataOutputStream dos, Collection collection, ItemWriter writer) throws IOException { + dos.writeInt(collection.size()); + for (T item : collection){ + writer.accept(item); + } + } + + @Override + public Resume doRead(InputStream is) throws IOException { + try (DataInputStream dis = new DataInputStream(is)) { + String uuid = dis.readUTF(); + String fullName = dis.readUTF(); + Resume resume = new Resume(uuid, fullName); + readCollection(dis, () -> resume.setContact(ContactType.valueOf(dis.readUTF()), dis.readUTF())); + readCollection(dis, () -> { + SectionType currentSection = SectionType.valueOf(dis.readUTF()); + resume.setSection(currentSection, readSection(dis, currentSection)); + }); + return resume; + } + } + + private interface ItemReader { + void read() throws IOException; + } + + private void readCollection(DataInputStream dis, ItemReader reader) throws IOException { + int size = dis.readInt(); + for (int i = 0; i < size; i++) { + reader.read(); + } + } + + private Section readSection(DataInputStream dis, SectionType currentSection) throws IOException { + switch (currentSection) { + case PERSONAL: + case OBJECTIVE: + return new TextSection(dis.readUTF()); + case ACHIEVEMENT: + case QUALIFICATIONS: + return new ListSection(readList(dis, dis::readUTF)); + case EXPERIENCE: + case EDUCATION: + return new OrganisationSection( + readList(dis, () -> new Organisation(dis.readUTF(), dis.readUTF(), + readList(dis, () -> new Organisation.Position( + parseLocalDate(dis.readUTF()), + parseLocalDate(dis.readUTF()), + dis.readUTF(), dis.readUTF()) + ) + ) + )); + default: + throw new IllegalStateException(); + } + } + + private interface ElemReader{ + T read() throws IOException; + } + + private List readList(DataInputStream dis, ElemReader reader) throws IOException { + int size = dis.readInt(); + List list = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + list.add(reader.read()); + } + return list; + } + + private LocalDate parseLocalDate(String string){ + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + return LocalDate.parse(string, dtf); + } +} diff --git a/src/ru/javaops/webapp/storage/serialize/ISerializeStrategy.java b/src/ru/javaops/webapp/storage/serialize/ISerializeStrategy.java new file mode 100644 index 00000000..6c3cb724 --- /dev/null +++ b/src/ru/javaops/webapp/storage/serialize/ISerializeStrategy.java @@ -0,0 +1,13 @@ +package ru.javaops.webapp.storage.serialize; + +import ru.javaops.webapp.model.Resume; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public interface ISerializeStrategy { + void doWrite(Resume resume, OutputStream outputStream) throws IOException; + + Resume doRead(InputStream inputStream) throws IOException; +} diff --git a/src/ru/javaops/webapp/storage/serialize/JsonStreamSerializer.java b/src/ru/javaops/webapp/storage/serialize/JsonStreamSerializer.java new file mode 100644 index 00000000..35822a27 --- /dev/null +++ b/src/ru/javaops/webapp/storage/serialize/JsonStreamSerializer.java @@ -0,0 +1,23 @@ +package ru.javaops.webapp.storage.serialize; + +import ru.javaops.webapp.model.Resume; +import ru.javaops.webapp.util.JsonParser; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +public class JsonStreamSerializer implements ISerializeStrategy { + @Override + public void doWrite(Resume r, OutputStream os) throws IOException { + try (Writer writer = new OutputStreamWriter(os, StandardCharsets.UTF_8)) { + JsonParser.write(r, writer); + } + } + + @Override + public Resume doRead(InputStream is) throws IOException { + try (Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + return JsonParser.read(reader, Resume.class); + } + } +} diff --git a/src/ru/javaops/webapp/storage/serialize/StreamSerializer.java b/src/ru/javaops/webapp/storage/serialize/StreamSerializer.java new file mode 100644 index 00000000..fb9fd728 --- /dev/null +++ b/src/ru/javaops/webapp/storage/serialize/StreamSerializer.java @@ -0,0 +1,26 @@ +package ru.javaops.webapp.storage.serialize; + +import ru.javaops.webapp.exception.StorageException; +import ru.javaops.webapp.model.Resume; + +import java.io.*; + +public class StreamSerializer implements ISerializeStrategy { + @Override + public void doWrite(Resume resume, OutputStream outputStream) { + try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream)) { + objectOutputStream.writeObject(resume); + } catch (IOException e) { + throw new StorageException("Resume write error", resume.getUuid(), e); + } + } + + @Override + public Resume doRead(InputStream inputStream) { + try (ObjectInputStream objectInputStream = new ObjectInputStream(inputStream)) { + return (Resume) objectInputStream.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new StorageException("Resume read error", e); + } + } +} diff --git a/src/ru/javaops/webapp/storage/serialize/XmlStreamSerializer.java b/src/ru/javaops/webapp/storage/serialize/XmlStreamSerializer.java new file mode 100644 index 00000000..97f59641 --- /dev/null +++ b/src/ru/javaops/webapp/storage/serialize/XmlStreamSerializer.java @@ -0,0 +1,31 @@ +package ru.javaops.webapp.storage.serialize; + +import ru.javaops.webapp.model.*; +import ru.javaops.webapp.util.XmlParser; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +public class XmlStreamSerializer implements ISerializeStrategy { + private XmlParser xmlParser; + + public XmlStreamSerializer() { + xmlParser = new XmlParser( + Resume.class, Organisation.class, OrganisationSection.class, + TextSection.class, ListSection.class, Organisation.Position.class); + } + + @Override + public void doWrite(Resume r, OutputStream os) throws IOException { + try (Writer w = new OutputStreamWriter(os, StandardCharsets.UTF_8)) { + xmlParser.marshall(r, w); + } + } + + @Override + public Resume doRead(InputStream is) throws IOException { + try (Reader r = new InputStreamReader(is, StandardCharsets.UTF_8)) { + return xmlParser.unmarshall(r); + } + } +} diff --git a/src/ru/javaops/webapp/util/DateUtil.java b/src/ru/javaops/webapp/util/DateUtil.java new file mode 100644 index 00000000..899326f8 --- /dev/null +++ b/src/ru/javaops/webapp/util/DateUtil.java @@ -0,0 +1,23 @@ +package ru.javaops.webapp.util; + +import java.time.LocalDate; +import java.time.Month; +import java.time.format.DateTimeFormatter; + +public class DateUtil { + public static LocalDate of(int year, Month month){ + return LocalDate.of(year, month, 1); + } + + public static final LocalDate NOW = LocalDate.of(3000, 1, 1); + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("MM/yyyy"); + + public static String format(LocalDate date) { + if (date == null) return ""; + return date.equals(NOW) ? "Сейчас" : date.format(DATE_FORMATTER); + } + + public static LocalDate parse(String date) { + return LocalDate.parse(date); + } +} diff --git a/src/ru/javaops/webapp/util/HtmlUtil.java b/src/ru/javaops/webapp/util/HtmlUtil.java new file mode 100644 index 00000000..b85ff0f1 --- /dev/null +++ b/src/ru/javaops/webapp/util/HtmlUtil.java @@ -0,0 +1,15 @@ +package ru.javaops.webapp.util; + +import ru.javaops.webapp.model.Organisation; + +public class HtmlUtil { + + public static boolean isEmpty(String str) { + return str == null || str.trim().length() == 0; + } + + public static String formatDates(Organisation.Position position) { + return DateUtil.format(position.getStartDate()) + " - " + DateUtil.format(position.getEndDate()); + } + +} diff --git a/src/ru/javaops/webapp/util/JsonParser.java b/src/ru/javaops/webapp/util/JsonParser.java new file mode 100644 index 00000000..22bf1e2b --- /dev/null +++ b/src/ru/javaops/webapp/util/JsonParser.java @@ -0,0 +1,30 @@ +package ru.javaops.webapp.util; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import ru.javaops.webapp.model.Section; + +import java.io.Reader; +import java.io.Writer; + +public class JsonParser { + private static Gson GSON = new GsonBuilder() + .registerTypeAdapter(Section.class, new JsonSectionAdapter()) + .create(); + + public static T read(Reader reader, Class clazz) { + return GSON.fromJson(reader, clazz); + } + + public static T read(String content, Class clazz) { + return GSON.fromJson(content, clazz); + } + + public static void write(T object, Writer writer) { + GSON.toJson(object, writer); + } + + public static String write(T object, Class clazz) { + return GSON.toJson(object, clazz); + } +} diff --git a/src/ru/javaops/webapp/util/JsonSectionAdapter.java b/src/ru/javaops/webapp/util/JsonSectionAdapter.java new file mode 100644 index 00000000..92ddd6aa --- /dev/null +++ b/src/ru/javaops/webapp/util/JsonSectionAdapter.java @@ -0,0 +1,34 @@ +package ru.javaops.webapp.util; + +import com.google.gson.*; + +import java.lang.reflect.Type; + +public class JsonSectionAdapter implements JsonSerializer, JsonDeserializer { + private static final String CLASSNAME = "CLASSNAME"; + private static final String INSTANCE = "INSTANCE"; + + @Override + public T deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME); + String className = prim.getAsString(); + + try { + Class clazz = Class.forName(className); + return context.deserialize(jsonObject.get(INSTANCE), clazz); + } catch (ClassNotFoundException e) { + throw new JsonParseException(e.getMessage()); + } + } + + + @Override + public JsonElement serialize(T section, Type type, JsonSerializationContext context) { + JsonObject retValue = new JsonObject(); + retValue.addProperty(CLASSNAME, section.getClass().getName()); + JsonElement elem = context.serialize(section); + retValue.add(INSTANCE, elem); + return retValue; + } +} diff --git a/src/ru/javaops/webapp/util/LazySingleton.java b/src/ru/javaops/webapp/util/LazySingleton.java new file mode 100644 index 00000000..a42d9038 --- /dev/null +++ b/src/ru/javaops/webapp/util/LazySingleton.java @@ -0,0 +1,24 @@ +package ru.javaops.webapp.util; + +public class LazySingleton { + volatile private static LazySingleton INSTANCE; + + private LazySingleton() { + } + + private static class LazySingletonHolder { + private static final LazySingleton INSTANCE = new LazySingleton(); + } + + public static LazySingleton getInstance() { + return LazySingletonHolder.INSTANCE; +// if (INSTANCE == null) { +// synchronized (LazySingleton.class) { +// if (INSTANCE == null) { +// INSTANCE = new LazySingleton(); +// } +// } +// } +// return INSTANCE; + } +} diff --git a/src/ru/javaops/webapp/util/LocalDateAdapter.java b/src/ru/javaops/webapp/util/LocalDateAdapter.java new file mode 100644 index 00000000..0f8af698 --- /dev/null +++ b/src/ru/javaops/webapp/util/LocalDateAdapter.java @@ -0,0 +1,16 @@ +package ru.javaops.webapp.util; + +import javax.xml.bind.annotation.adapters.XmlAdapter; +import java.time.LocalDate; + +public class LocalDateAdapter extends XmlAdapter { + @Override + public LocalDate unmarshal(String str) throws Exception { + return LocalDate.parse(str); + } + + @Override + public String marshal(LocalDate ld) throws Exception { + return ld.toString(); + } +} diff --git a/src/ru/javaops/webapp/util/XmlParser.java b/src/ru/javaops/webapp/util/XmlParser.java new file mode 100644 index 00000000..e42de7b3 --- /dev/null +++ b/src/ru/javaops/webapp/util/XmlParser.java @@ -0,0 +1,44 @@ +package ru.javaops.webapp.util; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import java.io.Reader; +import java.io.Writer; + +public class XmlParser { + private final Marshaller marshaller; + private final Unmarshaller unmarshaller; + + public XmlParser(Class... classesToBeBound) { + try { + JAXBContext ctx = JAXBContext.newInstance(classesToBeBound); + + marshaller = ctx.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); +// marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true); + + unmarshaller = ctx.createUnmarshaller(); + } catch (JAXBException e) { + throw new IllegalStateException(e); + } + } + + public T unmarshall(Reader reader) { + try { + return (T) unmarshaller.unmarshal(reader); + } catch (JAXBException e) { + throw new IllegalStateException(e); + } + } + + public void marshall(Object instance, Writer writer) { + try { + marshaller.marshal(instance, writer); + } catch (JAXBException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/src/ru/javaops/webapp/web/ResumeServlet.java b/src/ru/javaops/webapp/web/ResumeServlet.java new file mode 100644 index 00000000..befcc2b6 --- /dev/null +++ b/src/ru/javaops/webapp/web/ResumeServlet.java @@ -0,0 +1,166 @@ +package ru.javaops.webapp.web; + +import ru.javaops.webapp.Config; +import ru.javaops.webapp.model.*; +import ru.javaops.webapp.storage.IStorage; +import ru.javaops.webapp.util.DateUtil; +import ru.javaops.webapp.util.HtmlUtil; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class ResumeServlet extends HttpServlet { + + private IStorage storage; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + storage = Config.getInstance().getStorage(); + } + + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws javax.servlet.ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + String uuid = request.getParameter("uuid"); + String fullName = request.getParameter("fullName").trim(); + Resume r; + if (uuid.equals("") && fullName.trim().equals("")) { + response.sendRedirect("resume"); + return; + } else if (uuid.equals("") && !fullName.equals("")) { + r = new Resume(fullName); + storage.save(r); + } else { + r = storage.get(uuid); + r.setFullName(fullName); + } + for (ContactType type : ContactType.values()) { + String value = request.getParameter(type.name()); + if (value != null && value.trim().length() != 0) { + r.setContact(type, value); + } else { + r.getContacts().remove(type); + } + } + for (SectionType type : SectionType.values()) { + String value = request.getParameter(type.name()); + String[] values = request.getParameterValues(type.name()); + //if (value != null && value.trim().length() != 0) { + if (HtmlUtil.isEmpty(value) && values.length < 2) { + r.getSections().remove(type); + } else { + switch (type) { + case PERSONAL: + case OBJECTIVE: { + r.setSection(type, new TextSection(value)); + break; + } + case ACHIEVEMENT: + case QUALIFICATIONS: { + r.setSection(type, new ListSection(value.split("\\n"))); + break; + } + case EXPERIENCE: + case EDUCATION: { + List organisations = new ArrayList<>(); + String[] urls = request.getParameterValues(type.name() + "url"); + for (int i = 0; i < values.length; i++) { + String name = values[i]; + if (!HtmlUtil.isEmpty(name)) { + List positions = new ArrayList<>(); + String prefix = type.name() + i; + String[] startDates = request.getParameterValues(prefix + "startDate"); + String[] endDates = request.getParameterValues(prefix + "endDate"); + String[] heads = request.getParameterValues(prefix + "head"); + String[] descriptions = request.getParameterValues(prefix + "description"); + for (int j = 0; j < heads.length; j++) { + if (!HtmlUtil.isEmpty(heads[j])) { + positions.add(new Organisation.Position(DateUtil.parse(startDates[j]), DateUtil.parse(endDates[j]), heads[j], descriptions[j])); + } + } + organisations.add(new Organisation(name, urls[i], positions)); + } + } + r.setSection(type, new OrganisationSection(organisations)); + break; + } + } + } + } + storage.update(r); + response.sendRedirect("resume"); + } + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws javax.servlet.ServletException, IOException { + String uuid = request.getParameter("uuid"); + String action = request.getParameter("action"); + if (action == null) { + request.setAttribute("resumes", storage.getAllSorted()); + request.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(request, response); + return; + } + Resume r; + switch (action) { + case "add": + r = Resume.EMPTY; + break; +// request.getRequestDispatcher(("/WEB-INF/jsp/add.jsp")).forward(request, response); +// return; + case "delete": + storage.delete(uuid); + response.sendRedirect("resume"); + return; + case "view": + r = storage.get(uuid); + break; + case "edit": + r = storage.get(uuid); + for (SectionType type : SectionType.values()) { + Section section = r.getSection(type); + switch (type) { + case OBJECTIVE: + case PERSONAL: + if (section == null) { + section = TextSection.EMPTY; + } + break; + case ACHIEVEMENT: + case QUALIFICATIONS: + if (section == null) { + section = ListSection.EMPTY; + } + break; + case EXPERIENCE: + case EDUCATION: + OrganisationSection orgSection = (OrganisationSection) section; + List emptyFirstOrganisations = new ArrayList<>(); + emptyFirstOrganisations.add(Organisation.EMPTY); + if (orgSection != null) { + for (Organisation organisation : orgSection.getOrganisations()) { + List emptyFirstPositions = new ArrayList<>(); + emptyFirstPositions.add(Organisation.Position.EMPTY); + emptyFirstPositions.addAll(organisation.getPositions()); + emptyFirstOrganisations.add(new Organisation(organisation.getName(), organisation.getUrl(), emptyFirstPositions)); + } + } + section = new OrganisationSection(emptyFirstOrganisations); + break; + } + r.setSection(type, section); + } + break; + default: + throw new IllegalArgumentException("Action " + action + " is illegal"); + } + request.setAttribute("resume", r); + request.getRequestDispatcher( + ("view".equals(action) ? "/WEB-INF/jsp/view.jsp" : "/WEB-INF/jsp/edit.jsp") + ).forward(request, response); + } +} diff --git a/test/ru/javaops/webapp/storage/AbstractArrayStorageTest.java b/test/ru/javaops/webapp/storage/AbstractArrayStorageTest.java new file mode 100644 index 00000000..6b16a3f0 --- /dev/null +++ b/test/ru/javaops/webapp/storage/AbstractArrayStorageTest.java @@ -0,0 +1,26 @@ +package ru.javaops.webapp.storage; + +import org.junit.Test; +import ru.javaops.webapp.exception.StorageException; +import ru.javaops.webapp.model.Resume; + +import static org.junit.Assert.fail; +import static ru.javaops.webapp.storage.AbstractArrayStorage.STORAGE_LIMIT; + +public abstract class AbstractArrayStorageTest extends AbstractStorageTest { + public AbstractArrayStorageTest(IStorage storage) { + super(storage); + } + + @Test(expected = StorageException.class) + public void storageOverflow() { + try { + for (int i = storage.size(); i < STORAGE_LIMIT; i++) { + storage.save(new Resume("Name" + i)); + } + } catch (Exception e) { + fail("Storage is not full filled! " + "Exception: " + e.getMessage()); + } + storage.save(new Resume("dummy")); + } +} diff --git a/test/ru/javaops/webapp/storage/AbstractStorageTest.java b/test/ru/javaops/webapp/storage/AbstractStorageTest.java new file mode 100644 index 00000000..42e313d7 --- /dev/null +++ b/test/ru/javaops/webapp/storage/AbstractStorageTest.java @@ -0,0 +1,113 @@ +package ru.javaops.webapp.storage; + +import org.junit.*; +import ru.javaops.webapp.Config; +import ru.javaops.webapp.exception.ExistStorageException; +import ru.javaops.webapp.exception.NotExistStorageException; +import ru.javaops.webapp.model.*; +import ru.javaops.webapp.storage.serialize.StreamSerializer; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + + +public abstract class AbstractStorageTest { + protected final static File STORAGE_DIR = Config.getInstance().getStorageDir(); //new File("storage"); + protected final static StreamSerializer STREAM_SERIALIZER = new StreamSerializer(); + + protected final IStorage storage; + + private static final String UUID_3 = "uuid3"; + private static final Resume resume1 = ResumeTestData.getResume1(); + private static final Resume resume2 = ResumeTestData.getResume2(); + private static final Resume resume3 = new Resume(UUID_3, "Name3"); + + public AbstractStorageTest(IStorage storage) { + this.storage = storage; + } + + @Before + public void setUp() { + storage.clear(); + storage.save(resume1); + storage.save(resume2); + storage.save(resume3); + } + + @Test + public void size() { + compareSize(3); + } + + @Test + public void get() { + compareResume(resume1); + } + + @Test(expected = NotExistStorageException.class) + public void getNotExist() { + storage.get("dummy"); + } + + @Test + public void getAllSorted() { + List listStorage = storage.getAllSorted(); //Arrays.asList(resumes); + Assert.assertEquals(Arrays.asList(resume1, resume2, resume3), listStorage); + } + + @Test + public void update() { + Resume resume = storage.get(UUID_3); + resume3.setContact(ContactType.PHONE, "+79898989898"); + resume3.setContact(ContactType.MOBILE_PHONE, "+7555444554"); + resume3.setContact(ContactType.EMAIL, "name33@mail.ru"); + storage.update(resume); + compareSize(3); + compareResume(resume); + } + + @Test(expected = NotExistStorageException.class) + public void updateNotExist() { + storage.update(new Resume("dummy")); + } + + @Test + public void save() { + Resume resume = new Resume("dummy"); + storage.save(resume); + compareSize(4); + compareResume(resume); + } + + @Test(expected = ExistStorageException.class) + public void saveExistStorageException() { + storage.save(resume1); + } + + @Test(expected = NotExistStorageException.class) + public void delete() { + storage.delete(UUID_3); + compareSize(2); + storage.get(UUID_3); + } + + @Test(expected = NotExistStorageException.class) + public void deleteNotExistStorageException() { + storage.delete("dummy"); + } + + @Test + public void clear() { + storage.clear(); + compareSize(0); + } + + protected void compareSize(int size){ + Assert.assertEquals(size, storage.size()); + } + + protected void compareResume(Resume resume){ + Assert.assertEquals(resume, storage.get(resume.getUuid())); + } +} \ No newline at end of file diff --git a/test/ru/javaops/webapp/storage/ArrayStorageTest.java b/test/ru/javaops/webapp/storage/ArrayStorageTest.java new file mode 100644 index 00000000..bc6dcbaa --- /dev/null +++ b/test/ru/javaops/webapp/storage/ArrayStorageTest.java @@ -0,0 +1,7 @@ +package ru.javaops.webapp.storage; + +public class ArrayStorageTest extends AbstractArrayStorageTest { + public ArrayStorageTest() { + super(new ArrayStorage()); + } +} \ No newline at end of file diff --git a/test/ru/javaops/webapp/storage/DataPathStorageTest.java b/test/ru/javaops/webapp/storage/DataPathStorageTest.java new file mode 100644 index 00000000..f1d90d35 --- /dev/null +++ b/test/ru/javaops/webapp/storage/DataPathStorageTest.java @@ -0,0 +1,9 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.storage.serialize.DataStreamSerializer; + +public class DataPathStorageTest extends AbstractStorageTest{ + public DataPathStorageTest() { + super(new PathStorage(STORAGE_DIR.toString(), new DataStreamSerializer())); + } +} diff --git a/test/ru/javaops/webapp/storage/FileStorageTest.java b/test/ru/javaops/webapp/storage/FileStorageTest.java new file mode 100644 index 00000000..5cd7f0bf --- /dev/null +++ b/test/ru/javaops/webapp/storage/FileStorageTest.java @@ -0,0 +1,7 @@ +package ru.javaops.webapp.storage; + +public class FileStorageTest extends AbstractStorageTest { + public FileStorageTest() { + super(new FileStorage(STORAGE_DIR, STREAM_SERIALIZER)); + } +} diff --git a/test/ru/javaops/webapp/storage/JsonPathStorageTest.java b/test/ru/javaops/webapp/storage/JsonPathStorageTest.java new file mode 100644 index 00000000..24948632 --- /dev/null +++ b/test/ru/javaops/webapp/storage/JsonPathStorageTest.java @@ -0,0 +1,10 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.storage.serialize.JsonStreamSerializer; + +public class JsonPathStorageTest extends AbstractStorageTest { + + public JsonPathStorageTest() { + super(new PathStorage(STORAGE_DIR.toString(), new JsonStreamSerializer())); + } +} diff --git a/test/ru/javaops/webapp/storage/ListStorageTest.java b/test/ru/javaops/webapp/storage/ListStorageTest.java new file mode 100644 index 00000000..48f7ce9d --- /dev/null +++ b/test/ru/javaops/webapp/storage/ListStorageTest.java @@ -0,0 +1,7 @@ +package ru.javaops.webapp.storage; + +public class ListStorageTest extends AbstractStorageTest { + public ListStorageTest() { + super(new ListStorage()); + } +} diff --git a/test/ru/javaops/webapp/storage/MapResumeStorageTest.java b/test/ru/javaops/webapp/storage/MapResumeStorageTest.java new file mode 100644 index 00000000..ee04ab2b --- /dev/null +++ b/test/ru/javaops/webapp/storage/MapResumeStorageTest.java @@ -0,0 +1,7 @@ +package ru.javaops.webapp.storage; + +public class MapResumeStorageTest extends AbstractStorageTest{ + public MapResumeStorageTest() { + super(new MapResumeStorage()); + } +} diff --git a/test/ru/javaops/webapp/storage/MapUuidStorageTest.java b/test/ru/javaops/webapp/storage/MapUuidStorageTest.java new file mode 100644 index 00000000..9f2bc0fd --- /dev/null +++ b/test/ru/javaops/webapp/storage/MapUuidStorageTest.java @@ -0,0 +1,7 @@ +package ru.javaops.webapp.storage; + +public class MapUuidStorageTest extends AbstractStorageTest { + public MapUuidStorageTest() { + super(new MapUuidStorage()); + } +} diff --git a/test/ru/javaops/webapp/storage/PathStorageTest.java b/test/ru/javaops/webapp/storage/PathStorageTest.java new file mode 100644 index 00000000..2697c238 --- /dev/null +++ b/test/ru/javaops/webapp/storage/PathStorageTest.java @@ -0,0 +1,9 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.storage.serialize.StreamSerializer; + +public class PathStorageTest extends AbstractStorageTest { + public PathStorageTest() { + super(new PathStorage(STORAGE_DIR.toString(), new StreamSerializer())); + } +} diff --git a/test/ru/javaops/webapp/storage/ResumeTestData.java b/test/ru/javaops/webapp/storage/ResumeTestData.java new file mode 100644 index 00000000..6f45d3e3 --- /dev/null +++ b/test/ru/javaops/webapp/storage/ResumeTestData.java @@ -0,0 +1,85 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.model.*; + +import java.time.Month; +import java.util.Arrays; + +public class ResumeTestData { + + protected static Resume resume1 = new Resume("uuid1", "Name1"); + protected static Resume resume2 = new Resume("uuid2", "Name2"); + + public static Resume getResume1() { + return resume1; + } + public static Resume getResume2() { + return resume2; + } + + static { + resume1.setContact(ContactType.PHONE, "+71231231212"); + resume1.setContact(ContactType.MOBILE_PHONE, "+79217340000"); + resume1.setContact(ContactType.EMAIL, "name1@mail.ru"); + resume1.setContact(ContactType.TELEGRAM, "telegram1"); + resume1.setContact(ContactType.SKYPE, "skype1"); + resume1.setContact(ContactType.LINKEDIN, "LinkedIn/name1"); + resume1.setContact(ContactType.GITHUB, "GitHub/name1"); + resume1.setContact(ContactType.STACKOVERFLOW, "Stackoverflow/name1"); + resume1.setContact(ContactType.HOME_PAGE, "homepage/name1"); + + resume1.setSection(SectionType.PERSONAL, new TextSection("Личные качества resume 1")); + resume1.setSection(SectionType.OBJECTIVE, new TextSection("Позиция для resume 1")); + + resume1.setSection(SectionType.ACHIEVEMENT, new ListSection(Arrays.asList("achievment 1", "achievment 2", "achievment 3"))); + resume1.setSection(SectionType.QUALIFICATIONS, new ListSection(Arrays.asList("qualification 1", "qualification 2", "qualification 3"))); + + resume1.setSection(SectionType.EXPERIENCE, new OrganisationSection( + new Organisation("Yandex", "yandex.ru", + new Organisation.Position(2008, Month.MAY, "Синьйор", null) + ), + new Organisation("JetBrains", "jetbrains.com", + new Organisation.Position(2005, Month.DECEMBER, 2008, Month.MAY, "Миддл", "Работал миддлом"), + new Organisation.Position(2004, Month.APRIL, 2005, Month.DECEMBER, "Джуниор", "Работал джуном") + ) + )); + + resume1.setSection(SectionType.EDUCATION, new OrganisationSection( + new Organisation("Study 1", "urlStudy1", + new Organisation.Position(2000, Month.SEPTEMBER, 2004, Month.JUNE, "Student", "Was a student") + ) + )); + + resume2.setContact(ContactType.PHONE, "+79999999999"); + resume2.setContact(ContactType.MOBILE_PHONE, "+79119999991"); + resume2.setContact(ContactType.EMAIL, "name2@mail.ru"); + resume2.setContact(ContactType.TELEGRAM, "telegram2"); + resume2.setContact(ContactType.SKYPE, "skype2"); + resume2.setContact(ContactType.LINKEDIN, "LinkedIn/name2"); + resume2.setContact(ContactType.GITHUB, "GitHub/name2"); + resume2.setContact(ContactType.STACKOVERFLOW, "Stackoverflow/name2"); + resume2.setContact(ContactType.HOME_PAGE, "homepage/name2"); + + resume2.setSection(SectionType.PERSONAL, new TextSection("Личные качества resume 2")); + resume2.setSection(SectionType.OBJECTIVE, new TextSection("Позиция для resume 2")); + + resume2.setSection(SectionType.ACHIEVEMENT, new ListSection(Arrays.asList("achievment 1", "achievment 2", "achievment 3"))); + resume2.setSection(SectionType.QUALIFICATIONS, new ListSection(Arrays.asList("qualification 1", "qualification 2", "qualification 3"))); + + resume2.setSection(SectionType.EXPERIENCE, new OrganisationSection( + new Organisation("Google", "google.com", + new Organisation.Position(2009, Month.MAY, "Старший уборщик", "Работаю старшим уборщиком") + ), + new Organisation("JetBrains", null, + new Organisation.Position(2005, Month.DECEMBER, 2008, Month.MAY, "Заместитель старшего уборщика", null), + new Organisation.Position(2004, Month.APRIL, 2005, Month.DECEMBER, "Младший убощик", null) + ) + )); + + resume2.setSection(SectionType.EDUCATION, new OrganisationSection( + new Organisation("Study 2", "urlStudy2", + new Organisation.Position(2000, Month.SEPTEMBER, 2004, Month.JUNE, "Student", null) + ) + )); + } +} diff --git a/test/ru/javaops/webapp/storage/SortedArrayStorageTest.java b/test/ru/javaops/webapp/storage/SortedArrayStorageTest.java new file mode 100644 index 00000000..f170edf6 --- /dev/null +++ b/test/ru/javaops/webapp/storage/SortedArrayStorageTest.java @@ -0,0 +1,7 @@ +package ru.javaops.webapp.storage; + +public class SortedArrayStorageTest extends AbstractArrayStorageTest { + public SortedArrayStorageTest() { + super(new SortedArrayStorage()); + } +} \ No newline at end of file diff --git a/test/ru/javaops/webapp/storage/SqlStorageTest.java b/test/ru/javaops/webapp/storage/SqlStorageTest.java new file mode 100644 index 00000000..2a917ef0 --- /dev/null +++ b/test/ru/javaops/webapp/storage/SqlStorageTest.java @@ -0,0 +1,11 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.Config; + +import java.util.Properties; + +public class SqlStorageTest extends AbstractStorageTest { + public SqlStorageTest() { + super(Config.getInstance().getStorage()); + } +} diff --git a/test/ru/javaops/webapp/storage/TestAll.java b/test/ru/javaops/webapp/storage/TestAll.java new file mode 100644 index 00000000..d3ce8b59 --- /dev/null +++ b/test/ru/javaops/webapp/storage/TestAll.java @@ -0,0 +1,21 @@ +package ru.javaops.webapp.storage; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + ArrayStorageTest.class, + SortedArrayStorageTest.class, + ListStorageTest.class, + MapUuidStorageTest.class, + MapResumeStorageTest.class, + FileStorageTest.class, + PathStorageTest.class, + XmlPathStorageTest.class, + JsonPathStorageTest.class, + DataPathStorageTest.class, + SqlStorageTest.class +}) +public class TestAll { +} diff --git a/test/ru/javaops/webapp/storage/XmlPathStorageTest.java b/test/ru/javaops/webapp/storage/XmlPathStorageTest.java new file mode 100644 index 00000000..662dd9b0 --- /dev/null +++ b/test/ru/javaops/webapp/storage/XmlPathStorageTest.java @@ -0,0 +1,9 @@ +package ru.javaops.webapp.storage; + +import ru.javaops.webapp.storage.serialize.XmlStreamSerializer; + +public class XmlPathStorageTest extends AbstractStorageTest{ + public XmlPathStorageTest() { + super(new PathStorage(STORAGE_DIR.toString(), new XmlStreamSerializer())); + } +} diff --git a/web/META-INF/MANIFEST.MF b/web/META-INF/MANIFEST.MF new file mode 100644 index 00000000..59499bce --- /dev/null +++ b/web/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/web/WEB-INF/jsp/add.jsp b/web/WEB-INF/jsp/add.jsp new file mode 100644 index 00000000..9a44644f --- /dev/null +++ b/web/WEB-INF/jsp/add.jsp @@ -0,0 +1,144 @@ +<%-- + Created by IntelliJ IDEA. + User: nick + Date: 19.12.2018 + Time: 21:36 + To change this template use File | Settings | File Templates. +--%> +<%@ page import="ru.javaops.webapp.model.ContactType" %> +<%@ page import="ru.javaops.webapp.model.SectionType" %> +<%@ page import="java.time.LocalDate" %> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + + + Новое резюме + + + +
+
+ + +
+
Имя:
+
+
+

Контакты:

+ +
+
${type.title}
+
+
+
+ + + + +

Личные качества:

+
+ +
+
+ +

Позиция:

+
+ +
+
+ +

Достижения:

+
+ +
+
+ +

Квалификация:

+
+ +
+
+ +

Опыт работы

+
+
Название организации:
+
+
+ +
+
Сайт организации:
+
+
+ + <%----%> +
+
с:
+
+
+ +
+
по:
+
+
+ +
+
Должность:
+
+
+ +
+
Описание:
+

+
+
+ +

Образование

+
+
Название организации:
+
+
+ +
+
Сайт организации:
+
+
+ + <%----%> +
+
с:
+
+
+ +
+
по:
+
+
+ +
+
Должность:
+
+
+ +
+
Описание:
+

+
+
+
+
+ +
+ + + +
+ + + diff --git a/web/WEB-INF/jsp/edit.jsp b/web/WEB-INF/jsp/edit.jsp new file mode 100644 index 00000000..1dd750c6 --- /dev/null +++ b/web/WEB-INF/jsp/edit.jsp @@ -0,0 +1,106 @@ +<%-- + Created by IntelliJ IDEA. + User: nick + Date: 19.12.2018 + Time: 21:36 + To change this template use File | Settings | File Templates. +--%> +<%@ page import="ru.javaops.webapp.model.*" %> +<%@ page import="java.time.LocalDate" %> +<%@ page import="ru.javaops.webapp.util.DateUtil" %> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + + + + Резюме ${resume.fullName} + + + +
+
+ +
+
Имя:
+
+
+

Контакты:

+ +
+
${type.title}
+
+
+
+ + + + + + +

${type.title}

+ + + + + + + + + +
+
Название организации:
+
+
+ +
+
Сайт организации:
+
+
+
+ + +
+
с:
+
+
+ +
+
по:
+
+
+ +
+
Должность:
+
+
+ +
+
Описание:
+

+
+
+
+
+
+
+
+
+ + +
+
+ + + diff --git a/web/WEB-INF/jsp/fragments/footer.jsp b/web/WEB-INF/jsp/fragments/footer.jsp new file mode 100644 index 00000000..6de3697f --- /dev/null +++ b/web/WEB-INF/jsp/fragments/footer.jsp @@ -0,0 +1,12 @@ +<%-- + Created by IntelliJ IDEA. + User: nick + Date: 19.12.2018 + Time: 19:20 + To change this template use File | Settings | File Templates. +--%> +<%@page contentType="text/html" pageEncoding="UTF-8" %> +
+ diff --git a/web/WEB-INF/jsp/fragments/header.jsp b/web/WEB-INF/jsp/fragments/header.jsp new file mode 100644 index 00000000..218125b4 --- /dev/null +++ b/web/WEB-INF/jsp/fragments/header.jsp @@ -0,0 +1,10 @@ +<%-- + Created by IntelliJ IDEA. + User: nick + Date: 19.12.2018 + Time: 19:21 + To change this template use File | Settings | File Templates. +--%> +<%@page contentType="text/html" pageEncoding="UTF-8" %> +
Управление резюме
+
diff --git a/web/WEB-INF/jsp/list.jsp b/web/WEB-INF/jsp/list.jsp new file mode 100644 index 00000000..b56532b6 --- /dev/null +++ b/web/WEB-INF/jsp/list.jsp @@ -0,0 +1,42 @@ +<%-- + Created by IntelliJ IDEA. + User: nick + Date: 19.12.2018 + Time: 18:56 + To change this template use File | Settings | File Templates. +--%> +<%@ page import="ru.javaops.webapp.model.ContactType" %> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + + + + Список всех резюме + + + +
+ + + + + + + + + + + + + + + + +
ИмяEmailAdd
${resume.fullName}<%=ContactType.EMAIL.toHtml(resume.getContact(ContactType.EMAIL))%>DeleteEdit
+
+ + + \ No newline at end of file diff --git a/web/WEB-INF/jsp/view.jsp b/web/WEB-INF/jsp/view.jsp new file mode 100644 index 00000000..518f05f2 --- /dev/null +++ b/web/WEB-INF/jsp/view.jsp @@ -0,0 +1,108 @@ +<%-- + Created by IntelliJ IDEA. + User: nick + Date: 19.12.2018 + Time: 21:35 + To change this template use File | Settings | File Templates. +--%> +<%@ page import="ru.javaops.webapp.model.ListSection" %> +<%@ page import="ru.javaops.webapp.model.TextSection" %> +<%@ page import="ru.javaops.webapp.model.OrganisationSection" %> +<%@ page import="ru.javaops.webapp.util.HtmlUtil" %> + +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + + + + Резюме ${resume.fullName} + + + +
+

${resume.fullName} 

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ <%=contactEntry.getKey().toHtml(contactEntry.getValue())%>
+

${type.title}

+ <%=((TextSection) section).getText()%> +
+ <%=((TextSection) section).getText()%> +
+
    + +
  • ${item}
  • +
    +
+
+ + +

${org.url}

+
+ +

${org.name}

+
+
+
<%=HtmlUtil.formatDates(position)%> + ${position.head}
${position.description}
+

+

+ + + diff --git a/web/WEB-INF/web.xml b/web/WEB-INF/web.xml new file mode 100644 index 00000000..0282a864 --- /dev/null +++ b/web/WEB-INF/web.xml @@ -0,0 +1,15 @@ + + + + + resumeServlet + ru.javaops.webapp.web.ResumeServlet + + + resumeServlet + /resume + + \ No newline at end of file diff --git a/web/css/style.css b/web/css/style.css new file mode 100644 index 00000000..8bf09534 --- /dev/null +++ b/web/css/style.css @@ -0,0 +1,84 @@ +section { + width: 900px; + margin: auto; +} + +header { + background: none repeat scroll 0 0 #A6C9E2; + color: #2E6E9E; + font-size: 20px; + padding: 5px 20px; + text-align: center; +} + +footer { + background: none repeat scroll 0 0 #A6C9E2; + color: #2E6E9E; + font-size: 20px; + padding: 5px 20px; + margin: 20px 0; + text-align: center; +} + +dl { + background: none repeat scroll 0 0 #FAFAFA; + margin: 8px 0; + padding: 0; +} + +dt { + display: inline-block; + width: 170px; +} + +dd { + display: inline-block; + margin-left: 8px; + vertical-align: top; +} + +.positions { + margin-left: 10%; +} + +h2, h3 { + margin: 15px 0 5px; +} + +p { + line-height: 1.5; +} + +li { + margin: 15px 0; +} + +a[href^="skype:"] { + padding-left: 20px !important; + background: url(../img/skype.png) no-repeat center left; +} + +a[href^="tg:"] { + padding-left: 20px !important; + background: url(../img/telegram.png) no-repeat center left; +} + +a[href^="mailto:"] { + padding-left: 20px !important; + background: url(../img/email.png) no-repeat center left; +} + +a[href^="https://stackoverflow.com"] { + padding-left: 20px !important; + background: url(../img/so.png) no-repeat center left; +} + +a[href^="https://linkedin.com"] { + padding-left: 20px !important; + background: url(../img/lin.png) no-repeat center left; +} + +a[href*="https://github.com"] { + padding-left: 20px !important; + background: url(../img/gh.png) no-repeat center left; +} \ No newline at end of file diff --git a/web/index.html b/web/index.html new file mode 100644 index 00000000..00a91dde --- /dev/null +++ b/web/index.html @@ -0,0 +1,9 @@ + + + + + + +

Resume List

+ + \ No newline at end of file diff --git a/web/test.html b/web/test.html new file mode 100644 index 00000000..9830809c --- /dev/null +++ b/web/test.html @@ -0,0 +1,10 @@ + + + + + Title + + +Test + + \ No newline at end of file