diff --git a/README.md b/README.md index 1fdcab28..23796fe4 100644 --- a/README.md +++ b/README.md @@ -1,202 +1,15 @@ -# [Курс BaseJava (обновленный и переработанный)](http://javaops.ru/reg/basejava) - ## Разработка web-приложения "База данных резюме" -В данном курсе вы создадите с нуля web-приложение, реализуя разные способы хранения резюме. Проект включает в себя следующее: - - **Технологии:** Java 8, GitHub/Git, JUnit, Logging, GSON, JAXB, SQL, PostgreSQL, Сервлеты, HTML, JSP, JSTL, Tomcat, Maven и многое другое +Проект включает в себя следующее: + - **Технологии:** Java 8, GitHub/Git, JUnit, Logging, GSON, JAXB, SQL, PostgreSQL, Сервлеты, HTML, JSP, JSTL, Tomcat, Maven. - **Различные способы реализации хранения резюме:** - в сортированном и не сортированном массиве - в коллекциях (List, Map) - в файловой системе: - с использованием File и Path API - в стандартной и кастомной сериализации Java - - в формате JSON ([Google Gson](https://en.wikipedia.org/wiki/Gson)) - - в формате XML ([JAXB](https://ru.wikipedia.org/wiki/Java_Architecture_for_XML_Binding)) - - в реляционной базе [PostgreSQL](https://ru.wikipedia.org/wiki/PostgreSQL) + - в формате JSON + - в формате XML + - в реляционной базе PostgreSQL - **Установку (деплой) web-приложения:** - - в контейнер сервлетов [Tomcat](https://ru.wikipedia.org/wiki/Apache_Tomcat) - - в облачный сервис [Heroku](https://ru.wikipedia.org/wiki/Heroku) - -> Любое знание стоит воспринимать как подобие семантического дерева: убедитесь в том, что понимаете фундаментальные принципы, то есть ствол и крупные ветки, прежде чем лезть в мелкие листья-детали. Иначе последним не на чем будет держаться - -— Илон Маск - -### Изучите [бесплатный урок](lesson/lesson1.md). В конце урока находится домашнее задание, по которому можно оценить свой уровень готовности к проекту - -## Программа курса - -#### Занятие 1 (бесплатное) - - Обзор курса и методики обучения - - Подготовка и настройка рабочего окружения - - Подходы, применяемые при разработке ПО - - Обзор инструментов и технологий, используемых Java-разработчиками - - Введение в язык Java: история создания, JDK, JVM, JRE, JIT-компиляция - - Системы управления версиями. Git - - Домашнее задание - -#### Занятие 2 - - Типы данных - - Введение в объектно-ориентированное программирование - - Принципы ООП - - Классы и объекты - - Классы-обертки - - Модификаторы доступа - - Конструктор - - Структура памяти java-программы: Heap (куча), Stack (стек) - - Пакеты - - Домашнее задание - -#### Занятие 3 - - Разбор домашнего задания - - Обзор суперкласса Object - - Связь между equals() и hashCode() - - Статические методы и переменные - - Программирование с помощью интерфейсов - - Абстрактные классы - - Сложность алгоритмов - - Паттерн проектирования Template Method - - Домашнее задание - -#### Занятие 4 - - Разбор домашнего задания - - Конструктор - - Работа со строками: String, StringBuilder, StringBuffer - - String literal pool - - Исключения (Exceptions) - - Ключевые слова: this, super - - Reflection - - Аннотации - - Введение в модульное тестирование. JUnit - - Домашнее задание - -#### Занятие 5 - - Разбор домашнего задания - - Коллекций. Иерархия классов - - Списки (List) - - Множества (Set) - - Ассоциативные массивы (Map) - - Введение в Iterator - - Домашнее задание - -#### Занятие 6 - - Разбор домашнего задания - - Паттерн проектирования Iterator - - Autoboxing и Unboxing - - Вложенные классы - - Внутренние классы - - Локальные классы - - Анонимные классы - - Введение в лямбда-выражения - - Функциональный интерфейс - - Домашнее задание - -#### Занятие 7 - - Разбор домашнего задания - - Дженерики (Generic) - - Введение в логирование. Log4J, Java Logging API - - Паттерн проектирования Singleton - - Перечисления (Enum) - - Объектная модель - - Домашнее задание - -#### Занятие 8 - - Разбор домашнего задания - - Классы работы с датами: Date, Calendar, TimeZone - - Дата и время в Java 8+ - - File API - - Освобождение ресурсов: try-with-resources - - Домашнее задание - -#### Занятие 9 - - Разбор домашнего задания - - Обзор пакета java.io - - Классы чтения/записи потоков: InputStream и OutputStream - - Паттерн проектирования Decorator - - Классы чтения/записи символов: Reader и Writer - - Сериализация объектов - - Обзор пакета java.nio - - Введение в Java 8+ Stream API - - Домашнее задание - -#### Занятие 10 - - Разбор домашнего задания - - Паттерн проектирования Strategy - - Работа с XML (JAXB) - - Работа с JSON (GSON) - - Классы чтения/записи примитивных типов: DataInputStream и DataOutputStream - - Домашнее задание - -#### Занятие 11 - - Многопоточность - - Закон Мура и Амдала - - Потоки. Синхронизация доступа - - Обзор методов класса Object - - Ленивая инициализация - - Java Memory Model - - Deadlock - - Домашнее задание - -#### Занятие 12 - - Разбор домашнего задания - - Обзор классов java.util.concurrent - - Синхронизаторы - - ThreadLocal-переменные - - Сравнение с обменом (Compare-and-swap) - - Домашнее задание - -#### Занятие 13 - - Разбор домашнего задания - - Введение в реляционные базы данных - - Язык SQL - - Обзор NoSQL баз данных - - Установка и настройка СУБД PostgreSQL - - Работа с базами данных из IDEA - - Конфигурирование базы данных и каталога хранения - - Подключение базы данных к проекту - - Обзор JDBC-архитектуры - - Домашнее задание - -#### Занятие 14 - - Разбор домашнего задания - - Операции соединения таблиц. JOIN - - Транзакции - - Требования к транзакциям. ACID - - Уровни изоляции транзакций в SQL - - Установка и настройка контейнера сервлетов Tomcat - - Домашнее задание - -#### Занятие 15 - - Разбор домашнего задания - - Введение в HTML - - Основы протокола HTTP - - Настройка web.xml - - Деплой web-приложения в Tomcat - - Сервлеты - - Домашнее задание - -#### Занятие 16 - - Разбор домашнего задания - - Жизненный цикл сервлета - - Создание динамических страниц. JSP - - Расширенные возможности JSP. JSTL - - Redirect и Forward - - CRUD-операции - - Домашнее задание - -#### Занятие 17 - - Разбор домашнего задания - - Деплой приложения в облачный сервис Heroku - - Загрузка классов в Java. Classloader - - Домашнее задание - -## Рекомендуемые книги -- [Яков Файн, "Программирование на Java для начинающих"](http://myflex.org/books/java4kids/java4kids.htm) -- [Книги по Java: от новичка до профессионала](https://proglib.io/p/java-books-2019/) -- [Джошуа Блох, "Java. Эффективное программирование, 3-е издание"](https://www.ozon.ru/context/detail/id/148627191/) -- [Роберт Мартин, "Чистый код"](https://www.ozon.ru/context/detail/id/142429922/) -- [Серия Head First, "Паттерны проектирования"](https://www.ozon.ru/context/detail/id/144233005/) -- [Вайсфельд Мэтт, "Объектно-ориентированный подход"](https://www.ozon.ru/context/detail/id/166375103/?stat=YW5fMQ%3D%3D) - -## Ресурсы в сети -- [EduTools плагин от JetBrains для изучения Kotlin, Java, Python, Scala и других языков](http://javaops.ru/view/story/story21#edutools) -- [JetBrains Academy — интерактивный учебный курс по Java](https://www.jetbrains.com/ru-ru/academy/) + - в контейнер сервлетов Tomcat diff --git a/config/init_db.sql b/config/init_db.sql new file mode 100644 index 00000000..b307f888 --- /dev/null +++ b/config/init_db.sql @@ -0,0 +1,27 @@ +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); + +create table section +( + id serial primary key, + resume_uuid char(36) not null references resume (uuid) on delete cascade, + type text not null, + value text not null +); + +create unique index section_idx + on section (resume_uuid, type); \ No newline at end of file diff --git a/config/populate.sql b/config/populate.sql new file mode 100644 index 00000000..5c09d546 --- /dev/null +++ b/config/populate.sql @@ -0,0 +1,4 @@ +INSERT INTO resume (uuid, full_name) VALUES + ('9ae5ec0a-5255-4885-9867-660ed5a57e1b', 'Name1'), + ('d7e81904-29f4-462e-ae33-6f89f2b365bb', 'Name2'), + ('d487ad02-280e-488a-b25d-ac45f1686728', 'Name3'); \ No newline at end of file diff --git a/config/resumes.properties b/config/resumes.properties new file mode 100644 index 00000000..d5b791c1 --- /dev/null +++ b/config/resumes.properties @@ -0,0 +1,4 @@ +storage.dir=/Users/vladimirsafronov/Desktop/java/projects/basejava/storage +db.url=jdbc:postgresql://localhost:5433/resumes +db.user=postgres +db.password=admin \ No newline at end of file diff --git a/lesson/lesson1.md b/lesson/lesson1.md index 73fc4529..870dfaee 100644 --- a/lesson/lesson1.md +++ b/lesson/lesson1.md @@ -66,7 +66,7 @@ ![Screenshot_5](https://user-images.githubusercontent.com/29703461/199550057-fce7cf3c-7040-422f-b490-7b85b47ae952.png) -- Реализуйте методы `save, get, delete, clear, getAll, size` в классе `ArrayStorage`, организовав хранение резюме в массиве +- Реализуйте методы `save, get, delete, clear, getAll, size` в классе `storage.ru.javawebinar.basejava.ArrayStorage`, организовав хранение резюме в массиве - Храните все резюме в начале `storage` (без пустот в виде `null`), чтобы не перебирать каждый раз все 10_000 элементов - При реализации метода `delete` учитывайте, что после удаления резюме между оставшимися резюме не должно быть пустых ячеек, заполненных null ``` @@ -76,7 +76,7 @@ r1, r2, r3,..., rn, null, null,..., null <----- size -----> <------- storage.length (10000) -------> ``` -- Проверьте вашу реализацию с помощью классов `MainArray.main()` и `MainTestArrayStorage.main()` +- Проверьте вашу реализацию с помощью классов `ru.javawebinar.basejava.MainArray.main()` и `ru.javawebinar.basejava.MainTestArrayStorage.main()` - Изучите дополнительные материалы по IntelliJ IDEA: - [Idea Wiki](https://github.com/JavaOPs/topjava/wiki/IDEA) - [Отладка Java кода в IDEA. Основные возможности отладчика](https://youtu.be/Z1BQsf0A4xY) @@ -95,11 +95,11 @@ r1, r2, r3,..., rn, null, null,..., null 1. Перед каждым коммитом не забывайте пользоваться сочетанием клавиш `Ctrl + Alt + L` (автоматическое форматирование кода) 1. Удаляйте в классах неиспользуемые импорты (`Ctrl + Alt + O`) 1. Не игнорируй подсказки IDEA (подсвечивает) -1. В методе `clear()` обнуление массива предполагает обнуление (null) ячеек, где хранятся Resume, а не создание нового или присваивание ему null +1. В методе `clear()` обнуление массива предполагает обнуление (null) ячеек, где хранятся model.ru.javawebinar.basejava.Resume, а не создание нового или присваивание ему null 1. При реализации методов не используйте коллекции -1. Не меняйте сигнатуры методов в `ArrayStorage` -1. Не добавляйте в `Resume` новые поля -1. Resume r — давайте переменным осмысленные имена, например resume. r допустимо в коротких циклах и лямбда-выражениях +1. Не меняйте сигнатуры методов в `storage.ru.javawebinar.basejava.ArrayStorage` +1. Не добавляйте в `model.ru.javawebinar.basejava.Resume` новые поля +1. model.ru.javawebinar.basejava.Resume r — давайте переменным осмысленные имена, например resume. r допустимо в коротких циклах и лямбда-выражениях ## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. [Вебинар "Быть программистом: от детства к зрелости"](https://www.youtube.com/watch?v=D5Hej0TyLaU) - [Слайды вебинара](https://docs.google.com/presentation/d/1YwtCCZsaGMdl-V15kTDHiJxiS52IAl-qqheNPpiNr54/) diff --git a/lib/apiguardian-api-1.1.2.jar b/lib/apiguardian-api-1.1.2.jar new file mode 100644 index 00000000..2b678e15 Binary files /dev/null and b/lib/apiguardian-api-1.1.2.jar differ diff --git a/lib/gson-2.7.jar b/lib/gson-2.7.jar new file mode 100644 index 00000000..be5b59b7 Binary files /dev/null and b/lib/gson-2.7.jar differ diff --git a/lib/hamcrest-core-1.3.jar b/lib/hamcrest-core-1.3.jar new file mode 100644 index 00000000..9d5fe16e Binary files /dev/null and b/lib/hamcrest-core-1.3.jar differ diff --git a/lib/javax.servlet-api-4.0.1.jar b/lib/javax.servlet-api-4.0.1.jar new file mode 100644 index 00000000..844ec7f1 Binary files /dev/null and b/lib/javax.servlet-api-4.0.1.jar differ diff --git a/lib/javax.servlet.jsp-api-2.3.3.jar b/lib/javax.servlet.jsp-api-2.3.3.jar new file mode 100644 index 00000000..8b3333a2 Binary files /dev/null and b/lib/javax.servlet.jsp-api-2.3.3.jar differ diff --git a/lib/jstl-1.2.jar b/lib/jstl-1.2.jar new file mode 100644 index 00000000..0fd275e9 Binary files /dev/null and b/lib/jstl-1.2.jar differ diff --git a/lib/junit-4.13.1.jar b/lib/junit-4.13.1.jar new file mode 100644 index 00000000..b376ffc1 Binary files /dev/null and b/lib/junit-4.13.1.jar differ diff --git a/lib/junit-jupiter-5.8.1.jar b/lib/junit-jupiter-5.8.1.jar new file mode 100644 index 00000000..730b9ae2 Binary files /dev/null and b/lib/junit-jupiter-5.8.1.jar differ diff --git a/lib/junit-jupiter-api-5.8.1.jar b/lib/junit-jupiter-api-5.8.1.jar new file mode 100644 index 00000000..8424eca3 Binary files /dev/null and b/lib/junit-jupiter-api-5.8.1.jar differ diff --git a/lib/junit-jupiter-engine-5.8.1.jar b/lib/junit-jupiter-engine-5.8.1.jar new file mode 100644 index 00000000..cfa38d26 Binary files /dev/null and b/lib/junit-jupiter-engine-5.8.1.jar differ diff --git a/lib/junit-jupiter-params-5.8.1.jar b/lib/junit-jupiter-params-5.8.1.jar new file mode 100644 index 00000000..1e4d0ec4 Binary files /dev/null and b/lib/junit-jupiter-params-5.8.1.jar differ diff --git a/lib/junit-platform-commons-1.8.1.jar b/lib/junit-platform-commons-1.8.1.jar new file mode 100644 index 00000000..20185cd6 Binary files /dev/null and b/lib/junit-platform-commons-1.8.1.jar differ diff --git a/lib/junit-platform-engine-1.8.1.jar b/lib/junit-platform-engine-1.8.1.jar new file mode 100644 index 00000000..54ce0766 Binary files /dev/null and b/lib/junit-platform-engine-1.8.1.jar differ diff --git a/lib/opentest4j-1.2.0.jar b/lib/opentest4j-1.2.0.jar new file mode 100644 index 00000000..d5006361 Binary files /dev/null and b/lib/opentest4j-1.2.0.jar differ diff --git a/lib/postgresql-42.2.1.jar b/lib/postgresql-42.2.1.jar new file mode 100644 index 00000000..e934846c Binary files /dev/null and b/lib/postgresql-42.2.1.jar differ diff --git a/model.drawio b/model.drawio new file mode 100644 index 00000000..3b317d91 --- /dev/null +++ b/model.drawio @@ -0,0 +1 @@ +7Vxbc9o6EP41PIaxZXN75OI0nEKggfT2ckbFCqg1FrVFQ/rrK9mSQZah3Ax0xjOZTrSW1pb229Wn1aYlqz1fvQvgYtYnLvJKwHBXJatTAqBeq7F/ueAtFlRtMxZMA+zGog3BCP9GQmgI6RK7KFQ6UkI8iheqcEJ8H02oIoNBQF7Vbi/EU9+6gFOkCUYT6OnST9ilMzEtUFvLHxCezuSbzWojfjKHsrOYSTiDLnndEFlOyWoHhND4t/mqjTy+dnJd4nH3W54mHxYgn+414Pv9cAi+98OvX3/Wuj9fgw+91Z3Q8gt6SzHhEVtFTHzxzfRNLkT4iuce9Fmr9UJ8OhJPDNaezLDn9uAbWfIPCSmc/JCt1owE+DfrDz32yGQC9jigws6gyrVhz2sTjwRM4JPoBetBI65MvCZAIRs2lBM2U6I+XCkdezCk8gOJ58FFiL9Fn8wHzmEwxX6LUErmopNYCRRQtNq6xGZiOAZ4ROaIBm+sixhg2XY8RIA9sf3rGjqmLWSzDdhUgECsQOs0Ub02KPtF2PQA+1a22beH2UoXNj7cxpW6odi4em0TVzUT38XxkEYzarLGiAbYn2rmZotAI2sF5AdKmSfDYtDDU581PfTCh/FVxCxQNoWYkgVXtoAT9q5e1KdjryVPYim4iLCxL14UDGfYdZHPDUoopPBbArgFwT6N1qrSYj9sRdtGuVKqsA9vs7a5brMf3j2gbeKzuUAcGRIxaLwiDo89rb7VfXQoSPeu7md62e/spm9s92428cK7T/fuxJ5Xc29JhHb5d2zuqsfNJX29Ol1Dv/D5A3y+cds+b1rbnH4QTKGPf8OCvp3J+YGlOz8wLur8dqbzkw1Lh3oIUIFQBIIjA0HsaYdEgkxw5BcJaho4nBXbFNxQMzYTNvlhmLW+eYS7ZIuJhBub1bh5j/n7o/Wf0bl0+IAsfRe54gFaYfqZ/X5nlA2jJgRf+MOyYdVFu7Pa6N1522gMUYDZ5FEgZT5biM/iTVEj1lWRzbWqqCV1xRNErnZ2T7k6WwSyDCZoxyoK67CwNkU70ZANhs19QB4FAuQx3/ulfluW+YW6IUf7OgzZNTUMWbItVcRzEqM2T/xpRY1UPDNTiuJJa4oiNCZzPAGg9esC1EwBtPEv4lPa7JYAClRc2caRAE1vuJqivAGqH6AuG0ErKkDZjvMvItS+PYSmIp9lH4vQFNTBhREK9NNfQfPPTPOtfVN4ZhpFZ6NyQM/Dc57vonAS4EVk6SKRdwqX37hxuclTPQCZAKCY8nUsTJ+f6eXZ/Xqm1xM63PQzpn4YXU7Gx3v/R4l/grWCbDXuIhCwEN1h6y16EGZH1kSpboyWZHYqcHReHNXq++GomleuCOi5ol0s1sVwTnx3PMO+Ql5NhdkCex8ua5RtE2xQWeXwdQSXNcqgXt/ks2bZbIC/MNqolVZ5RpoL9j6I2RfjuQl1kQeo9An/WJ6rKcqb5+q3mHHIK/jt4VUIlSP5rQxh5w9NeqaSb00+nBfk5sRNqXrjvFZPAXLLLwOvMHyehr86q7X0xMVzqNPODUJCFshXs2p8x3dhOIv4hrmTiByZBdtxnQHMg/nLOcmGtSfZiEPrVchG5VxkQ1OUM9mw9GxL7kw5obYqQMG+CDVVcFZ2gvOcQNz3fmzb8SiP7K6VIjhH3z+k08QXzu5aetbnCYXLeVGZeQzvtVVjZhXfJhUdl0nrWtmpneWSjyrozwn0J/acg2ozskyfH/3JLtx5WXreY3Hqydn6GeT3wtbXy+5lzSacUFGx1YcLWbDVjh+M3xaIL+MaGbJ2S038xuV+uhpRB7ihRv4tR1EDlg/SMpK+mUjLLelr6Xmz3KmsTPoqjHQ3Ib2FKoU9eax1ueytmWIsSV3NoTw24a3bFOXNY/X8nhKLCjJ7eBI3RWYbWqC5cImClVHM13luN8fdwWO02yR7lPN56Dx1nce2o8o/PDd73ftu1pBB6z+nPe5+TI1gekaDx2ZPlTbbD13no9N3Hsf65ljcmJ9h09uSUzowqWzKwrCzY9HWEzgKhSrizcHxBqQL3fYNOJW8Ao6tJ0f6g1a35/w/fBg8piLFaNxsvx98dJ7ue4NP6qNe9/G90+mmg1S/2U3FldH7L8OU2nfd8cNzS5U9DPrOsPnOKWJPHrHH3gLTQ2MPyC326Ge7izHusq2UtJtlwzzhby7kHYek4+tLkCtWWUia8XeevuVklgNPT917mI0jaXo6xmqKcqbp9hUPi2VQa6jQtcAp0G1YlZvDrtyxbgm7ZuogkdydHAzexl8UHQ1e1lz/Lyhx9/V/JWM5fwA= \ No newline at end of file diff --git a/modelUML.png b/modelUML.png new file mode 100644 index 00000000..e7d6d3b9 Binary files /dev/null and b/modelUML.png differ 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/MainArray.java b/src/MainArray.java deleted file mode 100644 index 063364db..00000000 --- a/src/MainArray.java +++ /dev/null @@ -1,71 +0,0 @@ -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; - -/** - * Interactive test for ArrayStorage implementation - * (just run, no need to understand) - */ -public class MainArray { - private final static ArrayStorage ARRAY_STORAGE = new ArrayStorage(); - - public static void main(String[] args) throws IOException { - BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); - Resume r; - while (true) { - System.out.print("Введите одну из команд - (list | size | save uuid | delete uuid | get uuid | clear | exit): "); - String[] params = reader.readLine().trim().toLowerCase().split(" "); - if (params.length < 1 || params.length > 2) { - System.out.println("Неверная команда."); - continue; - } - String uuid = null; - if (params.length == 2) { - uuid = params[1].intern(); - } - switch (params[0]) { - case "list": - printAll(); - break; - case "size": - System.out.println(ARRAY_STORAGE.size()); - break; - case "save": - r = new Resume(); - r.uuid = uuid; - ARRAY_STORAGE.save(r); - printAll(); - break; - case "delete": - ARRAY_STORAGE.delete(uuid); - printAll(); - break; - case "get": - System.out.println(ARRAY_STORAGE.get(uuid)); - break; - case "clear": - ARRAY_STORAGE.clear(); - printAll(); - break; - case "exit": - return; - default: - System.out.println("Неверная команда."); - break; - } - } - } - - static void printAll() { - Resume[] all = ARRAY_STORAGE.getAll(); - System.out.println("----------------------------"); - if (all.length == 0) { - System.out.println("Empty"); - } else { - for (Resume r : all) { - System.out.println(r); - } - } - System.out.println("----------------------------"); - } -} diff --git a/src/MainTestArrayStorage.java b/src/MainTestArrayStorage.java deleted file mode 100644 index b15b81e2..00000000 --- a/src/MainTestArrayStorage.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Test for your ArrayStorage implementation - */ -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 8de4e4b8..00000000 --- a/src/Resume.java +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Initial resume class - */ -public class Resume { - - // Unique identifier - String uuid; - - @Override - public String toString() { - return uuid; - } -} diff --git a/src/ru/javawebinar/basejava/Config.java b/src/ru/javawebinar/basejava/Config.java new file mode 100644 index 00000000..8b9b15b0 --- /dev/null +++ b/src/ru/javawebinar/basejava/Config.java @@ -0,0 +1,56 @@ +package ru.javawebinar.basejava; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import ru.javawebinar.basejava.storage.SqlStorage; +import ru.javawebinar.basejava.storage.Storage; + +public class Config { + + private static Config INSTANCE; + private static File storageDir; + private static Storage storage; + + public static Config get() { + if (INSTANCE == null) { + Properties properties = new Properties(); + File fileWithProperties = new File(getHomeDir(), "config/resumes.properties"); + + try (InputStream is = new FileInputStream(fileWithProperties)) { + properties.load(is); + storageDir = new File(properties.getProperty("storage.dir")); + storage = new SqlStorage(properties.getProperty("db.url"), + properties.getProperty("db.user"), + properties.getProperty("db.password")); + } catch (IOException e) { + throw new IllegalStateException( + "Invalid config file " + fileWithProperties.getAbsolutePath()); + } + INSTANCE = new Config(); + } + return INSTANCE; + } + + private Config() { + } + + public File getStorageDir() { + return storageDir; + } + + public Storage 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 + " isn't directory!"); + } + return homeDir; + } +} diff --git a/src/ru/javawebinar/basejava/MainArray.java b/src/ru/javawebinar/basejava/MainArray.java new file mode 100644 index 00000000..eb26a608 --- /dev/null +++ b/src/ru/javawebinar/basejava/MainArray.java @@ -0,0 +1,82 @@ +package ru.javawebinar.basejava; + +import java.util.List; +import ru.javawebinar.basejava.model.Resume; +import ru.javawebinar.basejava.storage.ArrayStorage; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * Interactive test for com.urise.webapp.storage.ArrayStorage implementation (just run, no need to + * understand) + */ +public class MainArray { + + private final static ArrayStorage ARRAY_STORAGE = new ArrayStorage(); + + public static void main(String[] args) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + Resume r; + while (true) { + System.out.print("Введите одну из команд - " + + "(list | update uuid fullName | size | save fullName | delete uuid | get uuid | clear | exit): "); + String[] params = reader.readLine().trim().toLowerCase().split(" "); + if (params.length < 1 || params.length > 3) { + System.out.println("Неверная команда."); + continue; + } + String parameter = null; + if (params.length > 1) { + parameter = params[1].intern(); + } + switch (params[0]) { + case "list": + printAll(); + break; + case "update": + r = new Resume(parameter, params[2]); + ARRAY_STORAGE.update(r); + printAll(); + break; + case "size": + System.out.println(ARRAY_STORAGE.size()); + break; + case "save": + r = new Resume(parameter); + ARRAY_STORAGE.save(r); + printAll(); + break; + case "delete": + ARRAY_STORAGE.delete(parameter); + printAll(); + break; + case "get": + System.out.println(ARRAY_STORAGE.get(parameter)); + break; + case "clear": + ARRAY_STORAGE.clear(); + printAll(); + break; + case "exit": + return; + default: + System.out.println("Неверная команда."); + break; + } + } + } + + static void printAll() { + List all = ARRAY_STORAGE.getAllSorted(); + System.out.println("----------------------------"); + if (all.size() == 0) { + System.out.println("Empty"); + } else { + for (Resume r : all) { + System.out.println(r); + } + } + System.out.println("----------------------------"); + } +} diff --git a/src/ru/javawebinar/basejava/MainCollections.java b/src/ru/javawebinar/basejava/MainCollections.java new file mode 100644 index 00000000..77979b5a --- /dev/null +++ b/src/ru/javawebinar/basejava/MainCollections.java @@ -0,0 +1,28 @@ +package ru.javawebinar.basejava; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; +import ru.javawebinar.basejava.model.Resume; + +public class MainCollections { + + private static final String UUID_1 = "uuid1"; + private static final Resume RESUME_1 = new Resume(UUID_1, "Name1"); + private static final String UUID_2 = "uuid2"; + private static final Resume RESUME_2 = new Resume(UUID_2, "Name2"); + private static final String UUID_3 = "uuid3"; + private static final Resume RESUME_3 = new Resume(UUID_3, "Name3"); + private static final String UUID_4 = "uuid4"; + private static final Resume RESUME_4 = new Resume(UUID_4, "Name4"); + + public static void main(String[] args) { + Collection collection = new ArrayList<>(); + collection.add(RESUME_1); + collection.add(RESUME_2); + collection.add(RESUME_3); + + collection.removeIf(r -> Objects.equals(r.getUuid(), UUID_1)); +// System.out.println(collection.); + } +} diff --git a/src/ru/javawebinar/basejava/MainConcurrency.java b/src/ru/javawebinar/basejava/MainConcurrency.java new file mode 100644 index 00000000..7034967c --- /dev/null +++ b/src/ru/javawebinar/basejava/MainConcurrency.java @@ -0,0 +1,134 @@ +package ru.javawebinar.basejava; + +import java.text.SimpleDateFormat; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class MainConcurrency { + +// private static final Object LOCK = new Object(); +// private static final Lock lock = new ReentrantLock(); + private static final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); + private static final Lock READ_LOCK = reentrantReadWriteLock.readLock(); + private static final Lock WRITE_LOCK = reentrantReadWriteLock.writeLock(); + + private static final ThreadLocal threadLocal = ThreadLocal.withInitial( + SimpleDateFormat::new); + private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss"); + + public static final int THREADS_NUMBER = 10000; + private static int counter = 0; + private static final AtomicInteger atomicCounter = new AtomicInteger(); + + final String resource1 = "1"; + final String resource2 = "2"; + + public static void main(String[] args) throws InterruptedException { + System.out.println(Thread.currentThread().getName()); + Thread thread0 = new Thread() { + @Override + public void run() { + System.out.println(getName() + ", " + getState()); + } + }; + thread0.start(); + + new Thread(() -> { + System.out.println(Thread.currentThread().getName()); + System.out.println(Thread.currentThread().getState()); + }).start(); + + System.out.println(thread0.getState()); + MainConcurrency mainConcurrency = new MainConcurrency(); +// List threads = new ArrayList<>(THREADS_NUMBER); +// +// for (int i = 0; i < THREADS_NUMBER; i++) { +// Thread thread = new Thread(() -> { +// for (int j = 0; j < 100; j++) { +// mainConcurrency.increment(); +// } +// }); +// thread.start(); +// threads.add(thread); +//// Thread.sleep(500); +// threads.forEach(t -> { +// try { +// t.join(); +// } catch (InterruptedException e) { +// throw new RuntimeException(e); +// } +// }); +// } + CountDownLatch latch = new CountDownLatch(THREADS_NUMBER); + ExecutorService executor = Executors.newCachedThreadPool(); + for (int i = 0; i < THREADS_NUMBER; i++) { + executor.submit(() -> { + for (int j = 0; j < 100; j++) { + mainConcurrency.increment(); + System.out.println(threadLocal.get().format(new Date())); +// System.out.println(dtf.format((TemporalAccessor) new Date())); + } + latch.countDown(); + }); + } + latch.await(10, TimeUnit.SECONDS); + executor.shutdown(); +// System.out.println(counter); + System.out.println(atomicCounter.get()); + +// Thread deadLock1 = new Thread(() -> { +// synchronized (mainConcurrency.resource1) { +// System.out.println(Thread.currentThread().getName() + " caught resource1"); +// try { +// Thread.sleep(500); +// } catch (InterruptedException e) { +// throw new RuntimeException(e); +// } +// synchronized (mainConcurrency.resource2) { +// System.out.println(Thread.currentThread().getName() + " caught resource2"); +// } +// } +// }); +// +// Thread deadLock2 = new Thread(() -> { +// synchronized (mainConcurrency.resource2) { +// System.out.println(Thread.currentThread().getName() + " caught resource2"); +// try { +// Thread.sleep(500); +// } catch (InterruptedException e) { +// throw new RuntimeException(e); +// } +// synchronized (mainConcurrency.resource1) { +// System.out.println(Thread.currentThread().getName() + " caught resource1"); +// } +// } +// }); +// +// deadLock1.start(); +// deadLock2.start(); + } + + private void increment() { +// double a = Math.cos(13); +// synchronized (LOCK) { +// synchronized (this) { +// synchronized (MainConcurrency.class) { +// WRITE_LOCK.lock(); +// try { + atomicCounter.incrementAndGet(); +// counter++; +// } finally { +// WRITE_LOCK.unlock(); +// } +// } +// } +// } + } +} diff --git a/src/ru/javawebinar/basejava/MainDate.java b/src/ru/javawebinar/basejava/MainDate.java new file mode 100644 index 00000000..617c9c1d --- /dev/null +++ b/src/ru/javawebinar/basejava/MainDate.java @@ -0,0 +1,35 @@ +package ru.javawebinar.basejava; + +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; +import java.util.TimeZone; + +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(); + cal.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles")); + System.out.println(cal.getTime()); + + LocalDate localDate = LocalDate.now(); + LocalTime localTime = LocalTime.now(); + LocalDateTime ldt = LocalDateTime.of(localDate, localTime); + System.out.println(ldt); + + 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(ldt)); + } +} diff --git a/src/ru/javawebinar/basejava/MainFile.java b/src/ru/javawebinar/basejava/MainFile.java new file mode 100644 index 00000000..0bc98e8e --- /dev/null +++ b/src/ru/javawebinar/basejava/MainFile.java @@ -0,0 +1,48 @@ +package ru.javawebinar.basejava; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +public class MainFile { + + public static void main(String[] args) { + String filePath = ".gitignore"; + File file = new File(filePath); + try { + System.out.println(file.getCanonicalPath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + File dir = new File("/Users/vladimirsafronov/Desktop/java/projects/basejava/src"); + String[] listOfFiles = dir.list(); + if (listOfFiles != null) { + for (String name : listOfFiles) { + System.out.println(name); + } + } + + try (FileInputStream fis = new FileInputStream(filePath)) { + System.out.println(fis.read()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + showAllFiles(dir, ""); + } + + private static void showAllFiles(File dir, String offset) { + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isFile()) { + System.out.println(offset + "file: " + file.getName()); + } else if (file.isDirectory()) { + System.out.println(offset + "directory: " + file.getName()); + showAllFiles(file, offset + " "); + } + } + } + } +} diff --git a/src/ru/javawebinar/basejava/MainReflection.java b/src/ru/javawebinar/basejava/MainReflection.java new file mode 100644 index 00000000..5249d6bf --- /dev/null +++ b/src/ru/javawebinar/basejava/MainReflection.java @@ -0,0 +1,25 @@ +package ru.javawebinar.basejava; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import ru.javawebinar.basejava.model.Resume; + +public class MainReflection { + + public static void main(String[] args) + throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { + Resume r = new Resume("Name1"); + Class resumeClass = r.getClass(); + Field field = resumeClass.getDeclaredFields()[0]; + field.setAccessible(true); + System.out.println(field.getName()); + System.out.println(field.get(r)); + field.set(r, "plumber"); + + Method method = resumeClass.getMethod("toString"); + Object result = method.invoke(r); + + System.out.println(result); + } +} diff --git a/src/ru/javawebinar/basejava/MainStreams.java b/src/ru/javawebinar/basejava/MainStreams.java new file mode 100644 index 00000000..feff691b --- /dev/null +++ b/src/ru/javawebinar/basejava/MainStreams.java @@ -0,0 +1,54 @@ +package ru.javawebinar.basejava; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +public class MainStreams { + + public static void main(String[] args) { + int value = minValue(new int[]{3, 2, 6, 6, 3, 1, 2, 2}); + System.out.println(value); + + List numbers = new ArrayList<>(); + numbers.add(2); + numbers.add(3); + numbers.add(1); + numbers.add(6); + numbers.add(4); + numbers.add(5); + System.out.println(oddOrEven(numbers)); + } + + private static int minValue(int[] values) { + int[] sortedDistinctNumbers = Arrays.stream(values) + .distinct() + .sorted() + .toArray(); + AtomicInteger size = new AtomicInteger(sortedDistinctNumbers.length); + return Arrays.stream(sortedDistinctNumbers) + .map(number -> number * (int) Math.pow(10, size.decrementAndGet())) + .sum(); + } + + private static List oddOrEven(List integers) { + int sum = integers.stream() + .mapToInt(i -> i) + .sum(); + return sum % 2 == 0 ? getOdd(integers) : getEven(integers); + } + + private static List getOdd(List integers) { + return integers.stream() + .filter(n -> n % 2 != 0) + .collect(Collectors.toList()); + } + + private static List getEven(List integers) { + return integers.stream() + .filter(n -> n % 2 == 0) + .collect(Collectors.toList()); + } +} diff --git a/src/ru/javawebinar/basejava/MainTestArrayStorage.java b/src/ru/javawebinar/basejava/MainTestArrayStorage.java new file mode 100644 index 00000000..a0ae2376 --- /dev/null +++ b/src/ru/javawebinar/basejava/MainTestArrayStorage.java @@ -0,0 +1,43 @@ +package ru.javawebinar.basejava; + +import ru.javawebinar.basejava.model.Resume; +import ru.javawebinar.basejava.storage.ArrayStorage; +import ru.javawebinar.basejava.storage.Storage; + +/** + * Test for your com.urise.webapp.storage.ArrayStorage implementation + */ +public class MainTestArrayStorage { + + static final Storage ARRAY_STORAGE = new ArrayStorage(); + + public static void main(String[] args) { + Resume r1 = new Resume("uuid1", "Name1"); + Resume r2 = new Resume("uuid2", "Name2"); + Resume r3 = new Resume("uuid3", "Name3"); + + 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")); + + printAll(); + ARRAY_STORAGE.delete(r1.getUuid()); + 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.getAllSorted()) { + System.out.println(r); + } + } +} diff --git a/src/ru/javawebinar/basejava/TestSingleton.java b/src/ru/javawebinar/basejava/TestSingleton.java new file mode 100644 index 00000000..d9b8296c --- /dev/null +++ b/src/ru/javawebinar/basejava/TestSingleton.java @@ -0,0 +1,27 @@ +package ru.javawebinar.basejava; + +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("INSTANCE"); + System.out.println(instance.name()); + System.out.println(instance.ordinal()); + } + + public enum Singleton { + INSTANCE + } +} diff --git a/src/ru/javawebinar/basejava/exception/ExistStorageException.java b/src/ru/javawebinar/basejava/exception/ExistStorageException.java new file mode 100644 index 00000000..b4b40f0c --- /dev/null +++ b/src/ru/javawebinar/basejava/exception/ExistStorageException.java @@ -0,0 +1,8 @@ +package ru.javawebinar.basejava.exception; + +public class ExistStorageException extends StorageException { + + public ExistStorageException(String uuid) { + super("Resume " + uuid + " is already exists!", uuid); + } +} diff --git a/src/ru/javawebinar/basejava/exception/NotExistStorageException.java b/src/ru/javawebinar/basejava/exception/NotExistStorageException.java new file mode 100644 index 00000000..a571340f --- /dev/null +++ b/src/ru/javawebinar/basejava/exception/NotExistStorageException.java @@ -0,0 +1,8 @@ +package ru.javawebinar.basejava.exception; + +public class NotExistStorageException extends StorageException { + + public NotExistStorageException(String uuid) { + super("Resume " + uuid + " isn't exists!", uuid); + } +} diff --git a/src/ru/javawebinar/basejava/exception/StorageException.java b/src/ru/javawebinar/basejava/exception/StorageException.java new file mode 100644 index 00000000..c3cc860a --- /dev/null +++ b/src/ru/javawebinar/basejava/exception/StorageException.java @@ -0,0 +1,33 @@ +package ru.javawebinar.basejava.exception; + + +public class StorageException extends RuntimeException { + + private final String uuid; + + public StorageException(String message) { + this(message, null, null); + } + + 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 StorageException(Exception e) { + this(e.getMessage(), e); + } + + public String getUuid() { + return uuid; + } +} diff --git a/src/ru/javawebinar/basejava/model/ContactType.java b/src/ru/javawebinar/basejava/model/ContactType.java new file mode 100644 index 00000000..4829dc57 --- /dev/null +++ b/src/ru/javawebinar/basejava/model/ContactType.java @@ -0,0 +1,38 @@ +package ru.javawebinar.basejava.model; + +public enum ContactType { + MOBILE_PHONE("Mobile phone"), + SKYPE("Skype") { + @Override + public String toHtml0(String value) { + return "" + value + ""; + } + }, + EMAIL("Email") { + @Override + public String toHtml0(String value) { + return "" + value + ""; + } + }, + LINKEDIN("LinkedIn profile"), + GITHUB("Github profile"), + STACKOVERFLOW("Stackoverflow profile"), + HOME_PAGE("Home page"); + private final 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); + } +} diff --git a/src/ru/javawebinar/basejava/model/Link.java b/src/ru/javawebinar/basejava/model/Link.java new file mode 100644 index 00000000..f247f5c3 --- /dev/null +++ b/src/ru/javawebinar/basejava/model/Link.java @@ -0,0 +1,57 @@ +package ru.javawebinar.basejava.model; + +import java.io.Serializable; +import java.util.Objects; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +@XmlAccessorType(XmlAccessType.FIELD) +public class Link implements Serializable { + + private static final long serialVersionUID = 1L; + + private String name; + private String url; + + public Link() { + } + + public Link(String name, String url) { + Objects.requireNonNull(name, "name mustn't be null"); + this.name = name; + this.url = url == null ? "" : url; + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + @Override + public String toString() { + return "Link{" + + "name='" + name + '\'' + + ", url='" + url + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Link link = (Link) o; + return name.equals(link.name) && Objects.equals(url, link.url); + } + + @Override + public int hashCode() { + return Objects.hash(name, url); + } +} diff --git a/src/ru/javawebinar/basejava/model/Organization.java b/src/ru/javawebinar/basejava/model/Organization.java new file mode 100644 index 00000000..b2839088 --- /dev/null +++ b/src/ru/javawebinar/basejava/model/Organization.java @@ -0,0 +1,154 @@ +package ru.javawebinar.basejava.model; + +import static ru.javawebinar.basejava.util.DateUtil.NOW; +import static ru.javawebinar.basejava.util.DateUtil.of; + +import java.io.Serializable; +import java.time.LocalDate; +import java.time.Month; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import ru.javawebinar.basejava.util.LocalDateAdapter; + +@XmlAccessorType(XmlAccessType.FIELD) +public class Organization implements Serializable { + + public static final Organization EMPTY = new Organization("", "", Position.EMPTY); + + private static final long serialVersionUID = 1L; + + private Link homePage; + private List positions = new ArrayList<>(); + + public Organization() { + } + + public Organization(String name, String url, Position... positions) { + this(new Link(name, url), Arrays.asList(positions)); + } + + public Organization(Link homePage, List positions) { + Objects.requireNonNull(positions, "positions mustn't be null"); + this.homePage = homePage; + this.positions = positions; + } + + public List getPositions() { + return positions; + } + + public Link getHomePage() { + return homePage; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Organization that = (Organization) o; + return Objects.equals(homePage, that.homePage) && Objects.equals(positions, + that.positions); + } + + @Override + public int hashCode() { + return Objects.hash(homePage, positions); + } + + @Override + public String toString() { + return "Organization{" + + "homePage=" + homePage + + ", positions=" + positions + + '}'; + } + + @XmlAccessorType(XmlAccessType.FIELD) + public static class Position implements Serializable { + + public static final Position EMPTY = new Position(); + + @XmlJavaTypeAdapter(LocalDateAdapter.class) + private LocalDate startDate; + @XmlJavaTypeAdapter(LocalDateAdapter.class) + private LocalDate endDate; + private String title; + private String description; + + public Position() { + } + + public Position(int year, Month startMonth, String title, String description) { + this(of(year, startMonth), NOW, title, description); + } + + public Position(int startYear, Month startMonth, int endYear, Month endMonth, String title, + String description) { + this(of(startYear, startMonth), of(endYear, endMonth), title, description); + } + + public Position(LocalDate startDate, LocalDate endDate, String title, String description) { + Objects.requireNonNull(startDate, "startDate mustn't be null"); + Objects.requireNonNull(endDate, "endDate mustn't be null"); + Objects.requireNonNull(title, "title mustn't be null"); + this.startDate = startDate; + this.endDate = endDate; + this.title = title; + this.description = description == null ? "" : description; + } + + public LocalDate getStartDate() { + return startDate; + } + + public LocalDate getEndDate() { + return endDate; + } + + public String getTitle() { + return title; + } + + 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 startDate.equals(position.startDate) && endDate.equals(position.endDate) + && title.equals( + position.title) && Objects.equals(description, position.description); + } + + @Override + public int hashCode() { + return Objects.hash(startDate, endDate, title, description); + } + + @Override + public String toString() { + return "Position{" + + "startDate=" + startDate + + ", endDate=" + endDate + + ", title='" + title + '\'' + + ", description='" + description + '\'' + + '}'; + } + } +} diff --git a/src/ru/javawebinar/basejava/model/Resume.java b/src/ru/javawebinar/basejava/model/Resume.java new file mode 100644 index 00000000..42a9c51d --- /dev/null +++ b/src/ru/javawebinar/basejava/model/Resume.java @@ -0,0 +1,116 @@ +package ru.javawebinar.basejava.model; + +import java.io.Serializable; +import java.util.EnumMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + +@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, SectionLine.EMPTY); + EMPTY.setSection(SectionType.PERSONAL, SectionLine.EMPTY); + EMPTY.setSection(SectionType.ACHIEVEMENT, SectionList.EMPTY); + EMPTY.setSection(SectionType.QUALIFICATION, SectionList.EMPTY); + EMPTY.setSection(SectionType.EXPERIENCE, new SectionOrganization(Organization.EMPTY)); + EMPTY.setSection(SectionType.EDUCATION, new SectionOrganization(Organization.EMPTY)); + } + + 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 uuid, String fullName) { + Objects.requireNonNull(uuid, "uuid must not be null"); + Objects.requireNonNull(fullName, "fullName must not be null"); + this.uuid = uuid; + this.fullName = fullName; + } + + public Resume(String fullName) { + this(UUID.randomUUID().toString(), fullName); + } + + public String getUuid() { + return uuid; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public Map getContacts() { + return contacts; + } + + public Map getSections() { + return sections; + } + + 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) { + contacts.put(type, value); + } + + public void setSection(SectionType type, Section section) { + sections.put(type, section); + } + + @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 String toString() { + return "Resume{" + + "uuid='" + uuid + '\'' + + ", fullName='" + fullName + '\'' + + '}'; + } + + @Override + public int compareTo(Resume o) { + int compareFullName = fullName.compareTo(o.fullName); + return compareFullName != 0 ? compareFullName : uuid.compareTo(o.uuid); + } +} diff --git a/src/ru/javawebinar/basejava/model/Section.java b/src/ru/javawebinar/basejava/model/Section.java new file mode 100644 index 00000000..3f54d48a --- /dev/null +++ b/src/ru/javawebinar/basejava/model/Section.java @@ -0,0 +1,9 @@ +package ru.javawebinar.basejava.model; + +import java.io.Serializable; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +@XmlAccessorType(XmlAccessType.FIELD) +public abstract class Section implements Serializable { +} diff --git a/src/ru/javawebinar/basejava/model/SectionLine.java b/src/ru/javawebinar/basejava/model/SectionLine.java new file mode 100644 index 00000000..8f0132e7 --- /dev/null +++ b/src/ru/javawebinar/basejava/model/SectionLine.java @@ -0,0 +1,46 @@ +package ru.javawebinar.basejava.model; + +import java.util.Objects; + +public class SectionLine extends Section { + + private static final long serialVersionUID = 1L; + public static final SectionLine EMPTY = new SectionLine(""); + private String content; + + public SectionLine() { + } + + public SectionLine(String content) { + Objects.requireNonNull(content, "content mustn't be null"); + this.content = content; + } + + public String getContent() { + return content; + } + + @Override + public String toString() { + return "SectionLine{" + + "content='" + content + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SectionLine that = (SectionLine) o; + return content.equals(that.content); + } + + @Override + public int hashCode() { + return Objects.hash(content); + } +} diff --git a/src/ru/javawebinar/basejava/model/SectionList.java b/src/ru/javawebinar/basejava/model/SectionList.java new file mode 100644 index 00000000..f4c57ba6 --- /dev/null +++ b/src/ru/javawebinar/basejava/model/SectionList.java @@ -0,0 +1,52 @@ +package ru.javawebinar.basejava.model; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class SectionList extends Section { + + private static final long serialVersionUID = 1L; + public static final SectionList EMPTY = new SectionList(""); + private List content; + + public SectionList() { + } + + public SectionList(String... items) { + this(Arrays.asList(items)); + } + + public SectionList(List content) { + Objects.requireNonNull(content, "content mustn't be null"); + this.content = content; + } + + public List getContent() { + return content; + } + + @Override + public String toString() { + return "SectionList{" + + "content=" + content + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SectionList that = (SectionList) o; + return content.equals(that.content); + } + + @Override + public int hashCode() { + return Objects.hash(content); + } +} diff --git a/src/ru/javawebinar/basejava/model/SectionOrganization.java b/src/ru/javawebinar/basejava/model/SectionOrganization.java new file mode 100644 index 00000000..f3267448 --- /dev/null +++ b/src/ru/javawebinar/basejava/model/SectionOrganization.java @@ -0,0 +1,52 @@ +package ru.javawebinar.basejava.model; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class SectionOrganization extends Section { + + private static final long serialVersionUID = 1L; + + private List organizations; + + public SectionOrganization() { + } + + public SectionOrganization(Organization... organizations) { + this(Arrays.asList(organizations)); + } + + public SectionOrganization(List organizations) { + Objects.requireNonNull(organizations, "Organization mustn't be null"); + this.organizations = organizations; + } + + public List getOrganizations() { + return organizations; + } + + @Override + public String toString() { + return "SectionOrganization{" + + "organizations=" + organizations + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SectionOrganization that = (SectionOrganization) o; + return organizations.equals(that.organizations); + } + + @Override + public int hashCode() { + return Objects.hash(organizations); + } +} diff --git a/src/ru/javawebinar/basejava/model/SectionType.java b/src/ru/javawebinar/basejava/model/SectionType.java new file mode 100644 index 00000000..52b34b72 --- /dev/null +++ b/src/ru/javawebinar/basejava/model/SectionType.java @@ -0,0 +1,20 @@ +package ru.javawebinar.basejava.model; + +public enum SectionType { + OBJECTIVE("Objective"), + PERSONAL("Personal"), + ACHIEVEMENT("Achievement"), + QUALIFICATION("Qualification"), + EXPERIENCE("Experience"), + EDUCATION("Education"); + + private final String title; + + SectionType(String title) { + this.title = title; + } + + public String getTitle() { + return title; + } +} diff --git a/src/ru/javawebinar/basejava/sql/ConnectionFactory.java b/src/ru/javawebinar/basejava/sql/ConnectionFactory.java new file mode 100644 index 00000000..2f2398a8 --- /dev/null +++ b/src/ru/javawebinar/basejava/sql/ConnectionFactory.java @@ -0,0 +1,9 @@ +package ru.javawebinar.basejava.sql; + +import java.sql.Connection; +import java.sql.SQLException; + +public interface ConnectionFactory { + + Connection getConnection() throws SQLException; +} diff --git a/src/ru/javawebinar/basejava/sql/ExceptionUtil.java b/src/ru/javawebinar/basejava/sql/ExceptionUtil.java new file mode 100644 index 00000000..99f432f8 --- /dev/null +++ b/src/ru/javawebinar/basejava/sql/ExceptionUtil.java @@ -0,0 +1,24 @@ +package ru.javawebinar.basejava.sql; + +import java.sql.SQLException; +import org.postgresql.util.PSQLException; +import ru.javawebinar.basejava.exception.ExistStorageException; +import ru.javawebinar.basejava.exception.StorageException; + +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/javawebinar/basejava/sql/SqlExecutor.java b/src/ru/javawebinar/basejava/sql/SqlExecutor.java new file mode 100644 index 00000000..4d3a3118 --- /dev/null +++ b/src/ru/javawebinar/basejava/sql/SqlExecutor.java @@ -0,0 +1,9 @@ +package ru.javawebinar.basejava.sql; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public interface SqlExecutor { + + T execute(PreparedStatement ps) throws SQLException; +} diff --git a/src/ru/javawebinar/basejava/sql/SqlHelper.java b/src/ru/javawebinar/basejava/sql/SqlHelper.java new file mode 100644 index 00000000..2d21805e --- /dev/null +++ b/src/ru/javawebinar/basejava/sql/SqlHelper.java @@ -0,0 +1,44 @@ +package ru.javawebinar.basejava.sql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import ru.javawebinar.basejava.exception.StorageException; + +public class SqlHelper { + + private final ConnectionFactory connectionFactory; + + public SqlHelper(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + public void execute(String query) { + execute(query, PreparedStatement::execute); + } + + public T execute(String query, SqlExecutor sqlExecutor) { + try (Connection conn = connectionFactory.getConnection(); + PreparedStatement ps = conn.prepareStatement(query)) { + return sqlExecutor.execute(ps); + } catch (SQLException e) { + throw new StorageException(e); + } + } + + public T transactionalExecute(SqlTransaction 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/javawebinar/basejava/sql/SqlTransaction.java b/src/ru/javawebinar/basejava/sql/SqlTransaction.java new file mode 100644 index 00000000..bd23a125 --- /dev/null +++ b/src/ru/javawebinar/basejava/sql/SqlTransaction.java @@ -0,0 +1,9 @@ +package ru.javawebinar.basejava.sql; + +import java.sql.Connection; +import java.sql.SQLException; + +public interface SqlTransaction { + + T execute(Connection conn) throws SQLException; +} diff --git a/src/ru/javawebinar/basejava/storage/AbstractArrayStorage.java b/src/ru/javawebinar/basejava/storage/AbstractArrayStorage.java new file mode 100644 index 00000000..7c43abea --- /dev/null +++ b/src/ru/javawebinar/basejava/storage/AbstractArrayStorage.java @@ -0,0 +1,68 @@ +package ru.javawebinar.basejava.storage; + +import java.util.Arrays; +import java.util.List; +import ru.javawebinar.basejava.exception.StorageException; +import ru.javawebinar.basejava.model.Resume; + +public abstract class AbstractArrayStorage extends AbstractStorage { + + protected static final int STORAGE_LIMIT = 10000; + protected Resume[] storage = new Resume[STORAGE_LIMIT]; + protected int storageSize; + + @Override + protected boolean isExist(Integer index) { + return index >= 0; + } + + @Override + protected abstract Integer getSearchKey(String uuid); + + protected abstract void insertElement(Resume r, int index); + + protected abstract void fillDeletedElement(int index); + + @Override + protected void doUpdate(Resume r, Integer index) { + storage[index] = r; + } + + public int size() { + return storageSize; + } + + @Override + protected void doSave(Resume r, Integer index) { + if (storageSize == STORAGE_LIMIT) { + throw new StorageException("Storage is overflow!", r.getUuid()); + } + insertElement(r, index); + storageSize++; + } + + @Override + public void doDelete(Integer index) { + fillDeletedElement(index); + storage[storageSize - 1] = null; + storageSize--; + } + + @Override + public Resume doGet(Integer index) { + return storage[index]; + } + + /** + * @return array, contains only Resumes in storage (without null) + */ + @Override + public List doCopyAll() { + return Arrays.asList(Arrays.copyOf(storage, storageSize)); + } + + public void clear() { + Arrays.fill(storage, 0, storageSize, null); + storageSize = 0; + } +} diff --git a/src/ru/javawebinar/basejava/storage/AbstractStorage.java b/src/ru/javawebinar/basejava/storage/AbstractStorage.java new file mode 100644 index 00000000..f4f22fdd --- /dev/null +++ b/src/ru/javawebinar/basejava/storage/AbstractStorage.java @@ -0,0 +1,78 @@ +package ru.javawebinar.basejava.storage; + +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; +import ru.javawebinar.basejava.exception.ExistStorageException; +import ru.javawebinar.basejava.exception.NotExistStorageException; +import ru.javawebinar.basejava.model.Resume; + +public abstract class AbstractStorage implements Storage { + + // protected final Logger log = Logger.getLogger(getClass().getName()); + private final static Logger LOG = Logger.getLogger(AbstractStorage.class.getName()); + + protected abstract K getSearchKey(String uuid); + + protected abstract void doUpdate(Resume r, K searchKey); + + protected abstract boolean isExist(K searchKey); + + protected abstract void doSave(Resume r, K searchKey); + + protected abstract void doDelete(K searchKey); + + protected abstract Resume doGet(K searchKey); + + protected abstract List doCopyAll(); + + public void update(Resume r) { + LOG.info("Update " + r); + K searchKey = getExistedSearchKey(r.getUuid()); + doUpdate(r, searchKey); + } + + public void save(Resume r) { + LOG.info("Save " + r); + K searchKey = getNotExistedSearchKey(r.getUuid()); + doSave(r, searchKey); + } + + public void delete(String uuid) { + LOG.info("Delete " + uuid); + K searchKey = getExistedSearchKey(uuid); + doDelete(searchKey); + } + + public Resume get(String uuid) { + LOG.info("Get " + uuid); + K searchKey = getExistedSearchKey(uuid); + return doGet(searchKey); + } + + private K getNotExistedSearchKey(String uuid) { + K searchKey = getSearchKey(uuid); + if (isExist(searchKey)) { + LOG.warning("Resume " + uuid + " is already exists!"); + throw new ExistStorageException(uuid); + } + return searchKey; + } + + private K getExistedSearchKey(String uuid) { + K searchKey = getSearchKey(uuid); + if (!isExist(searchKey)) { + LOG.warning("Resume " + uuid + " isn't exists!"); + throw new NotExistStorageException(uuid); + } + return searchKey; + } + + @Override + public List getAllSorted() { + LOG.info("GetAllSorted"); + List list = doCopyAll(); + Collections.sort(list); + return list; + } +} diff --git a/src/ru/javawebinar/basejava/storage/ArrayStorage.java b/src/ru/javawebinar/basejava/storage/ArrayStorage.java new file mode 100644 index 00000000..2a9fd5ce --- /dev/null +++ b/src/ru/javawebinar/basejava/storage/ArrayStorage.java @@ -0,0 +1,29 @@ +package ru.javawebinar.basejava.storage; + +import ru.javawebinar.basejava.model.Resume; + +/** + * Array based storage for Resumes + */ +public class ArrayStorage extends AbstractArrayStorage { + + @Override + protected Integer getSearchKey(String uuid) { + for (int i = 0; i < storageSize; i++) { + if (uuid.equals(storage[i].getUuid())) { + return i; + } + } + return -1; + } + + @Override + protected void insertElement(Resume r, int index) { + storage[storageSize] = r; + } + + @Override + protected void fillDeletedElement(int index) { + storage[index] = storage[storageSize - 1]; + } +} diff --git a/src/ru/javawebinar/basejava/storage/FileStorage.java b/src/ru/javawebinar/basejava/storage/FileStorage.java new file mode 100644 index 00000000..d03c1db9 --- /dev/null +++ b/src/ru/javawebinar/basejava/storage/FileStorage.java @@ -0,0 +1,111 @@ +package ru.javawebinar.basejava.storage; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import ru.javawebinar.basejava.exception.StorageException; +import ru.javawebinar.basejava.model.Resume; +import ru.javawebinar.basejava.storage.serializer.StreamSerializer; + +public class FileStorage extends AbstractStorage { + + private final File directory; + private final StreamSerializer streamSerializer; + + protected FileStorage(File directory, StreamSerializer streamSerializer) { + Objects.requireNonNull(directory, "directory mustn't be null"); + + this.streamSerializer = streamSerializer; + if (!directory.isDirectory()) { + throw new IllegalArgumentException(directory.getAbsolutePath() + " isn't directory"); + } + if (!directory.canRead() || !directory.canWrite()) { + throw new IllegalArgumentException(directory.getAbsolutePath() + " isn't readable/writable!"); + } + this.directory = directory; + } + + @Override + protected File getSearchKey(String uuid) { + return new File(directory, uuid); + } + + @Override + protected void doUpdate(Resume r, File file) { + try { + streamSerializer.doWrite(r, new BufferedOutputStream(new FileOutputStream(file))); + } catch (IOException e) { + throw new StorageException("File write error", r.getUuid(), e); + } + } + + @Override + protected boolean isExist(File file) { + return file.exists(); + } + + @Override + protected void doSave(Resume r, File file) { + try { + file.createNewFile(); + } catch (IOException e) { + throw new StorageException("Couldn't create file" + file.getAbsolutePath(), file.getName(), + e); + } + doUpdate(r, file); + } + + @Override + protected void doDelete(File file) { + if (!file.delete()) { + throw new StorageException("File delete error", file.getName()); + } + } + + @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 List doCopyAll() { + File[] files = directory.listFiles(); + if (files == null) { + throw new StorageException("Directory read error"); + } + List resumeList = new ArrayList<>(files.length); + for (File file : files) { + resumeList.add(doGet(file)); + } + return resumeList; + } + + @Override + public void clear() { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + doDelete(file); + } + } + } + + @Override + public int size() { + String[] files = directory.list(); + if (files == null) { + throw new StorageException("Directory read error"); + } + return files.length; + } +} diff --git a/src/ru/javawebinar/basejava/storage/ListStorage.java b/src/ru/javawebinar/basejava/storage/ListStorage.java new file mode 100644 index 00000000..f67f2a6d --- /dev/null +++ b/src/ru/javawebinar/basejava/storage/ListStorage.java @@ -0,0 +1,60 @@ +package ru.javawebinar.basejava.storage; + +import java.util.ArrayList; +import java.util.List; +import ru.javawebinar.basejava.model.Resume; + +public class ListStorage extends AbstractStorage { + + private final List storage = new ArrayList<>(); + + @Override + protected boolean isExist(Integer searchKey) { + return searchKey >= 0; + } + + @Override + protected Integer getSearchKey(String uuid) { + for (int i = 0; i < storage.size(); i++) { + if (storage.get(i).getUuid().equals(uuid)) { + return i; + } + } + return -1; + } + + @Override + public void clear() { + storage.clear(); + } + + @Override + public void doUpdate(Resume r, Integer index) { + storage.set(index, r); + } + + @Override + public void doSave(Resume r, Integer index) { + storage.add(r); + } + + @Override + public Resume doGet(Integer index) { + return storage.get(index); + } + + @Override + public void doDelete(Integer index) { + storage.remove(index.intValue()); + } + + @Override + public List doCopyAll() { + return new ArrayList<>(storage); + } + + @Override + public int size() { + return storage.size(); + } +} diff --git a/src/ru/javawebinar/basejava/storage/MapResumeStorage.java b/src/ru/javawebinar/basejava/storage/MapResumeStorage.java new file mode 100644 index 00000000..6468d669 --- /dev/null +++ b/src/ru/javawebinar/basejava/storage/MapResumeStorage.java @@ -0,0 +1,57 @@ +package ru.javawebinar.basejava.storage; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import ru.javawebinar.basejava.model.Resume; + +public class MapResumeStorage extends AbstractStorage { + + Map mapStorage = new HashMap<>(); + + @Override + protected boolean isExist(Resume resume) { + return resume != null; + } + + @Override + protected Resume getSearchKey(String uuid) { + return mapStorage.get(uuid); + } + + @Override + protected void doUpdate(Resume r, Resume resume) { + mapStorage.put(r.getUuid(), r); + } + + @Override + protected void doSave(Resume r, Resume resume) { + mapStorage.put(r.getUuid(), r); + } + + @Override + protected void doDelete(Resume resume) { + mapStorage.remove(resume.getUuid()); + } + + @Override + protected Resume doGet(Resume resume) { + return resume; + } + + @Override + protected List doCopyAll() { + return new ArrayList<>(mapStorage.values()); + } + + @Override + public void clear() { + mapStorage.clear(); + } + + @Override + public int size() { + return mapStorage.size(); + } +} diff --git a/src/ru/javawebinar/basejava/storage/MapUuidStorage.java b/src/ru/javawebinar/basejava/storage/MapUuidStorage.java new file mode 100644 index 00000000..4caad676 --- /dev/null +++ b/src/ru/javawebinar/basejava/storage/MapUuidStorage.java @@ -0,0 +1,57 @@ +package ru.javawebinar.basejava.storage; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import ru.javawebinar.basejava.model.Resume; + +public class MapUuidStorage extends AbstractStorage { + + private final Map mapStorage = new HashMap<>(); + + @Override + protected boolean isExist(String searchKey) { + return mapStorage.containsKey(searchKey); + } + + @Override + protected String getSearchKey(String uuid) { + return uuid; + } + + @Override + protected void doUpdate(Resume r, String searchKey) { + mapStorage.put(searchKey, r); + } + + @Override + protected void doSave(Resume r, String searchKey) { + mapStorage.put(searchKey, r); + } + + @Override + protected void doDelete(String searchKey) { + mapStorage.remove(searchKey); + } + + @Override + protected Resume doGet(String searchKey) { + return mapStorage.get(searchKey); + } + + @Override + protected List doCopyAll() { + return new ArrayList<>(mapStorage.values()); + } + + @Override + public void clear() { + mapStorage.clear(); + } + + @Override + public int size() { + return mapStorage.size(); + } +} diff --git a/src/ru/javawebinar/basejava/storage/PathStorage.java b/src/ru/javawebinar/basejava/storage/PathStorage.java new file mode 100644 index 00000000..e0a6bca2 --- /dev/null +++ b/src/ru/javawebinar/basejava/storage/PathStorage.java @@ -0,0 +1,104 @@ +package ru.javawebinar.basejava.storage; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +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; +import ru.javawebinar.basejava.exception.StorageException; +import ru.javawebinar.basejava.model.Resume; +import ru.javawebinar.basejava.storage.serializer.StreamSerializer; + +public class PathStorage extends AbstractStorage { + + private final Path directory; + private final StreamSerializer streamSerializer; + + protected PathStorage(String dir, StreamSerializer streamSerializer) { + Objects.requireNonNull(dir, "directory mustn't be null"); + this.streamSerializer = streamSerializer; + directory = Paths.get(dir); + if (!Files.isDirectory(directory) || !Files.isWritable(directory)) { + throw new IllegalArgumentException(dir + " isn't directory or isn't writable"); + } + } + + @Override + protected Path getSearchKey(String uuid) { + return directory.resolve(uuid); + } + + @Override + protected void doUpdate(Resume r, Path path) { + try { + streamSerializer.doWrite(r, new BufferedOutputStream(Files.newOutputStream(path))); + } catch (IOException e) { + throw new StorageException("Path write error", r.getUuid(), e); + } + } + + @Override + protected boolean isExist(Path path) { + return Files.isRegularFile(path); + } + + @Override + protected void doSave(Resume r, Path path) { + try { + Files.createFile(path); + } catch (IOException e) { + throw new StorageException("Couldn't create path" + path, getFileName(path), e); + } + doUpdate(r, path); + } + + @Override + protected void doDelete(Path path) { + try { + Files.delete(path); + } catch (IOException e) { + throw new StorageException("Path delete error", getFileName(path), e); + } + } + + @Override + protected Resume doGet(Path path) { + try { + return streamSerializer.doRead(new BufferedInputStream(Files.newInputStream(path))); + } catch (IOException e) { + throw new StorageException("Path read error", getFileName(path), e); + } + } + + @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("Directory read error", e); + } + } +} diff --git a/src/ru/javawebinar/basejava/storage/SortedArrayStorage.java b/src/ru/javawebinar/basejava/storage/SortedArrayStorage.java new file mode 100644 index 00000000..0973c950 --- /dev/null +++ b/src/ru/javawebinar/basejava/storage/SortedArrayStorage.java @@ -0,0 +1,31 @@ +package ru.javawebinar.basejava.storage; + +import java.util.Comparator; +import ru.javawebinar.basejava.model.Resume; +import java.util.Arrays; + +public class SortedArrayStorage extends AbstractArrayStorage { + + private static final Comparator RESUME_COMPARATOR = Comparator.comparing(Resume::getUuid); + + @Override + protected Integer getSearchKey(String uuid) { + Resume searchKey = new Resume(uuid, "dummy"); + return Arrays.binarySearch(storage, 0, storageSize, searchKey, RESUME_COMPARATOR); + } + + @Override + protected void insertElement(Resume r, int index) { + int insertIndex = -index - 1; + System.arraycopy(storage, insertIndex, storage, insertIndex + 1, storageSize - insertIndex); + storage[insertIndex] = r; + } + + @Override + protected void fillDeletedElement(int index) { + int numMoved = storageSize - 1 - index; + if (numMoved >= 0) { + System.arraycopy(storage, index + 1, storage, index, numMoved); + } + } +} diff --git a/src/ru/javawebinar/basejava/storage/SqlStorage.java b/src/ru/javawebinar/basejava/storage/SqlStorage.java new file mode 100644 index 00000000..48c62465 --- /dev/null +++ b/src/ru/javawebinar/basejava/storage/SqlStorage.java @@ -0,0 +1,214 @@ +package ru.javawebinar.basejava.storage; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import ru.javawebinar.basejava.exception.NotExistStorageException; +import ru.javawebinar.basejava.model.ContactType; +import ru.javawebinar.basejava.model.Resume; +import ru.javawebinar.basejava.model.Section; +import ru.javawebinar.basejava.model.SectionType; +import ru.javawebinar.basejava.sql.SqlHelper; +import ru.javawebinar.basejava.util.JsonParser; + +public class SqlStorage implements Storage { + + 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 void update(Resume r) { + sqlHelper.transactionalExecute(conn -> { + try (PreparedStatement ps = conn.prepareStatement( + "UPDATE resume SET full_name = ? WHERE uuid = ?")) { + ps.setString(1, r.getFullName()); + ps.setString(2, r.getUuid()); + if (ps.executeUpdate() == 0) { + throw new NotExistStorageException(r.getUuid()); + } + deleteContacts(conn, r); + deleteSections(conn, r); + insertContacts(conn, r); + insertSections(conn, r); + return null; + } + }); + } + + @Override + public void save(Resume r) { + sqlHelper.transactionalExecute(conn -> { + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO resume (uuid, full_name) VALUES (?,?)")) { + ps.setString(1, r.getUuid()); + ps.setString(2, r.getFullName()); + ps.execute(); + } + insertContacts(conn, r); + insertSections(conn, r); + return null; + }); + } + + @Override + public Resume get(String uuid) { + return sqlHelper.transactionalExecute(conn -> { + Resume r; + try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM resume WHERE uuid = ?")) { + ps.setString(1, uuid); + ResultSet rs = ps.executeQuery(); + if (!rs.next()) { + throw new NotExistStorageException(uuid); + } + r = new Resume(uuid, rs.getString("full_name")); + } + + try (PreparedStatement ps = conn.prepareStatement( + "SELECT * FROM contact WHERE resume_uuid = ?")) { + ps.setString(1, uuid); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + addContact(rs, r); + } + } + + try (PreparedStatement ps = conn.prepareStatement( + "SELECT * FROM section WHERE resume_uuid = ?")) { + ps.setString(1, uuid); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + addSection(rs, r); + } + } + return r; + }); + } + + @Override + public void delete(String uuid) { + sqlHelper.execute("DELETE FROM resume WHERE uuid = ?", ps -> { + ps.setString(1, uuid); + if (ps.executeUpdate() == 0) { + throw new NotExistStorageException(uuid); + } + return null; + }); + } + + @Override + public List getAllSorted() { + return sqlHelper.transactionalExecute(conn -> { + Map resumes = new LinkedHashMap<>(); + + try (PreparedStatement ps = conn.prepareStatement( + "SELECT * FROM resume ORDER BY full_name, uuid")) { + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + String uuid = rs.getString("uuid"); + resumes.put(uuid, new Resume(uuid, rs.getString("full_name"))); + } + } + + try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM contact")) { + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + Resume r = resumes.get(rs.getString("resume_uuid")); + addContact(rs, r); + } + } + + try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM section")) { + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + Resume r = resumes.get(rs.getString("resume_uuid")); + addSection(rs, r); + } + } + return new ArrayList<>(resumes.values()); + }); + } + + @Override + public int size() { + return sqlHelper.execute("SELECT count(*) FROM resume", ps -> { + ResultSet rs = ps.executeQuery(); + return rs.next() ? rs.getInt("count") : 0; + }); + } + + private void deleteContacts(Connection conn, Resume r) throws SQLException { + deleteAttributes(conn, r, "DELETE FROM contact WHERE resume_uuid = ?"); + } + + private void deleteSections(Connection conn, Resume r) throws SQLException { + deleteAttributes(conn, r, "DELETE FROM section WHERE resume_uuid = ?"); + } + + private void deleteAttributes(Connection conn, Resume r, String query) throws SQLException { + try (PreparedStatement ps = conn.prepareStatement(query)) { + ps.setString(1, r.getUuid()); + ps.execute(); + } + } + + private void insertContacts(Connection conn, Resume r) throws SQLException { + try (PreparedStatement ps = conn.prepareStatement + ("INSERT INTO contact (resume_uuid, type, value) VALUES (?,?,?)")) { + for (Map.Entry e : r.getContacts().entrySet()) { + ps.setString(1, r.getUuid()); + ps.setString(2, e.getKey().name()); + ps.setString(3, e.getValue()); + ps.addBatch(); + } + ps.executeBatch(); + } + } + + private void insertSections(Connection conn, Resume r) throws SQLException { + try (PreparedStatement ps = conn.prepareStatement + ("INSERT INTO section (resume_uuid, type, value) VALUES (?,?,?)")) { + for (Map.Entry e : r.getSections().entrySet()) { + ps.setString(1, r.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 addContact(ResultSet rs, Resume r) throws SQLException { + String value = rs.getString("value"); + if (value != null) { + r.setContact(ContactType.valueOf(rs.getString("type")), value); + } + } + + private void addSection(ResultSet rs, Resume r) throws SQLException { + String value = rs.getString("value"); + if (value != null) { + SectionType type = SectionType.valueOf(rs.getString("type")); + r.setSection(type, JsonParser.read(value, Section.class)); + } + } +} diff --git a/src/ru/javawebinar/basejava/storage/Storage.java b/src/ru/javawebinar/basejava/storage/Storage.java new file mode 100644 index 00000000..1a71b4b9 --- /dev/null +++ b/src/ru/javawebinar/basejava/storage/Storage.java @@ -0,0 +1,24 @@ +package ru.javawebinar.basejava.storage; + +import java.util.List; +import ru.javawebinar.basejava.model.Resume; + +public interface Storage { + + void clear(); + + void update(Resume r); + + void save(Resume r); + + Resume get(String uuid); + + void delete(String uuid); + + /** + * @return array, contains only Resumes in storage (without null) + */ + List getAllSorted(); + + int size(); +} diff --git a/src/ru/javawebinar/basejava/storage/serializer/DataStreamSerializer.java b/src/ru/javawebinar/basejava/storage/serializer/DataStreamSerializer.java new file mode 100644 index 00000000..b0bc11a4 --- /dev/null +++ b/src/ru/javawebinar/basejava/storage/serializer/DataStreamSerializer.java @@ -0,0 +1,140 @@ +package ru.javawebinar.basejava.storage.serializer; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import ru.javawebinar.basejava.model.*; + +public class DataStreamSerializer implements StreamSerializer { + + @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()); + }); + + writeCollection(dos, r.getSections().entrySet(), entry -> { + SectionType type = entry.getKey(); + Section section = entry.getValue(); + dos.writeUTF(type.name()); + switch (type) { + case PERSONAL: + case OBJECTIVE: + dos.writeUTF(((SectionLine) section).getContent()); + break; + case ACHIEVEMENT: + case QUALIFICATION: + writeCollection(dos, ((SectionList) section).getContent(), dos::writeUTF); + break; + case EXPERIENCE: + case EDUCATION: + writeCollection(dos, ((SectionOrganization) section).getOrganizations(), org -> { + dos.writeUTF(org.getHomePage().getName()); + dos.writeUTF(org.getHomePage().getUrl()); + writeCollection(dos, org.getPositions(), position -> { + writeLocalDate(dos, position.getStartDate()); + writeLocalDate(dos, position.getEndDate()); + dos.writeUTF(position.getTitle()); + dos.writeUTF(position.getDescription()); + }); + }); + break; + } + }); + } + } + + private void writeLocalDate(DataOutputStream dos, LocalDate ld) throws IOException { + dos.writeInt(ld.getYear()); + dos.writeInt(ld.getMonth().getValue()); + } + + private LocalDate readLocalDate(DataInputStream dis) throws IOException { + return LocalDate.of(dis.readInt(), dis.readInt(), 1); + } + + @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); + readItems(dis, () -> resume.setContact(ContactType.valueOf(dis.readUTF()), dis.readUTF())); + readItems(dis, () -> { + SectionType sectionType = SectionType.valueOf(dis.readUTF()); + resume.setSection(sectionType, readSection(dis, sectionType)); + }); + return resume; + } + } + + private Section readSection(DataInputStream dis, SectionType sectionType) throws IOException { + switch (sectionType) { + case PERSONAL: + case OBJECTIVE: + return new SectionLine(dis.readUTF()); + case ACHIEVEMENT: + case QUALIFICATION: + return new SectionList(readList(dis, dis::readUTF)); + case EXPERIENCE: + case EDUCATION: + return new SectionOrganization( + readList(dis, () -> new Organization( + new Link(dis.readUTF(), dis.readUTF()), + readList(dis, () -> new Organization.Position( + readLocalDate(dis), readLocalDate(dis), dis.readUTF(), dis.readUTF() + )) + ))); + default: + throw new IllegalStateException(); + } + } + + private List readList(DataInputStream dis, ElementReader 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 interface ElementProcessor { + void process() throws IOException; + } + + private interface ElementReader { + T read() throws IOException; + } + + private interface ElementWriter { + void write(T t) throws IOException; + } + + private void readItems(DataInputStream dis, ElementProcessor processor) throws IOException { + int size = dis.readInt(); + for (int i = 0; i < size; i++) { + processor.process(); + } + } + + private void writeCollection(DataOutputStream dos, Collection collection, + ElementWriter writer) throws IOException { + dos.writeInt(collection.size()); + for (T item : collection) { + writer.write(item); + } + } +} diff --git a/src/ru/javawebinar/basejava/storage/serializer/JsonStreamSerializer.java b/src/ru/javawebinar/basejava/storage/serializer/JsonStreamSerializer.java new file mode 100644 index 00000000..d8809ca3 --- /dev/null +++ b/src/ru/javawebinar/basejava/storage/serializer/JsonStreamSerializer.java @@ -0,0 +1,29 @@ +package ru.javawebinar.basejava.storage.serializer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import ru.javawebinar.basejava.model.Resume; +import ru.javawebinar.basejava.util.JsonParser; + +public class JsonStreamSerializer implements StreamSerializer { + + @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/javawebinar/basejava/storage/serializer/StreamSerializer.java b/src/ru/javawebinar/basejava/storage/serializer/StreamSerializer.java new file mode 100644 index 00000000..c8c2832a --- /dev/null +++ b/src/ru/javawebinar/basejava/storage/serializer/StreamSerializer.java @@ -0,0 +1,13 @@ +package ru.javawebinar.basejava.storage.serializer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import ru.javawebinar.basejava.model.Resume; + +public interface StreamSerializer { + + void doWrite(Resume r, OutputStream os) throws IOException; + + Resume doRead(InputStream is) throws IOException; +} diff --git a/src/ru/javawebinar/basejava/storage/serializer/XmlStreamSerializer.java b/src/ru/javawebinar/basejava/storage/serializer/XmlStreamSerializer.java new file mode 100644 index 00000000..580c509e --- /dev/null +++ b/src/ru/javawebinar/basejava/storage/serializer/XmlStreamSerializer.java @@ -0,0 +1,37 @@ +package ru.javawebinar.basejava.storage.serializer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import ru.javawebinar.basejava.model.*; +import ru.javawebinar.basejava.util.XmlParser; + +public class XmlStreamSerializer implements StreamSerializer { + + private final XmlParser xmlParser; + + public XmlStreamSerializer() { + xmlParser = new XmlParser(Resume.class, Organization.class, Link.class, + SectionOrganization.class, + SectionList.class, SectionLine.class, Organization.Position.class); + } + + + @Override + public void doWrite(Resume r, OutputStream os) throws IOException { + try (Writer writer = new OutputStreamWriter(os, StandardCharsets.UTF_8)) { + xmlParser.marshall(r, writer); + } + } + + @Override + public Resume doRead(InputStream is) throws IOException { + try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + return xmlParser.unmarshall(reader); + } + } +} diff --git a/src/ru/javawebinar/basejava/util/DateUtil.java b/src/ru/javawebinar/basejava/util/DateUtil.java new file mode 100644 index 00000000..e794a2a4 --- /dev/null +++ b/src/ru/javawebinar/basejava/util/DateUtil.java @@ -0,0 +1,31 @@ +package ru.javawebinar.basejava.util; + +import java.time.LocalDate; +import java.time.Month; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; + +public class DateUtil { + + public static final LocalDate NOW = LocalDate.of(3000, 1, 1); + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("MM/yyyy"); + + public static LocalDate of(int year, Month month) { + return LocalDate.of(year, month, 1); + } + + public static String format(LocalDate date) { + if (date == null) { + return ""; + } + return date.equals(NOW) ? "Nowadays" : date.format(DATE_FORMATTER); + } + + public static LocalDate parse(String date) { + if (HtmlUtil.isEmpty(date) || date.equals("Nowadays")) { + return NOW; + } + YearMonth yearMonth = YearMonth.parse(date, DATE_FORMATTER); + return LocalDate.of(yearMonth.getYear(), yearMonth.getMonth(), 1); + } +} diff --git a/src/ru/javawebinar/basejava/util/HtmlUtil.java b/src/ru/javawebinar/basejava/util/HtmlUtil.java new file mode 100644 index 00000000..d07cb733 --- /dev/null +++ b/src/ru/javawebinar/basejava/util/HtmlUtil.java @@ -0,0 +1,16 @@ +package ru.javawebinar.basejava.util; + +import ru.javawebinar.basejava.model.Organization; + +public class HtmlUtil { + + public static boolean isEmpty(String str) { + return str == null || str.trim().length() == 0; + } + + public static String formatDates(Organization.Position position) { + return DateUtil.format(position.getStartDate()) + " - " + DateUtil.format( + position.getEndDate()); + } + +} diff --git a/src/ru/javawebinar/basejava/util/JsonParser.java b/src/ru/javawebinar/basejava/util/JsonParser.java new file mode 100644 index 00000000..e29aed5a --- /dev/null +++ b/src/ru/javawebinar/basejava/util/JsonParser.java @@ -0,0 +1,34 @@ +package ru.javawebinar.basejava.util; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.io.Reader; +import java.io.Writer; +import ru.javawebinar.basejava.model.Section; + +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 void write(T object, Writer writer) { + GSON.toJson(object, writer); + } + + public static T read(String content, Class clazz) { + return GSON.fromJson(content, clazz); + } + + public static String write(T object) { + return GSON.toJson(object); + } + + public static String write(T object, Class clazz) { + return GSON.toJson(object, clazz); + } +} diff --git a/src/ru/javawebinar/basejava/util/JsonSectionAdapter.java b/src/ru/javawebinar/basejava/util/JsonSectionAdapter.java new file mode 100644 index 00000000..5f7d0d25 --- /dev/null +++ b/src/ru/javawebinar/basejava/util/JsonSectionAdapter.java @@ -0,0 +1,34 @@ +package ru.javawebinar.basejava.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/javawebinar/basejava/util/LazySingleton.java b/src/ru/javawebinar/basejava/util/LazySingleton.java new file mode 100644 index 00000000..fbe580cb --- /dev/null +++ b/src/ru/javawebinar/basejava/util/LazySingleton.java @@ -0,0 +1,29 @@ +package ru.javawebinar.basejava.util; + +public class LazySingleton { + +// private static volatile LazySingleton INSTANCE; + + private LazySingleton() { + } + +// public static LazySingleton getInstance() { +// if (INSTANCE == null) { +// synchronized (LazySingleton.class) { +// if (INSTANCE == null) { +// INSTANCE = new LazySingleton(); +// } +// } +// } +// return INSTANCE; +// } + + private static class LazySingletonHolder{ + static final LazySingleton INSTANCE = new LazySingleton(); + } + + public static LazySingleton getInstance() { + return LazySingletonHolder.INSTANCE; + } + +} diff --git a/src/ru/javawebinar/basejava/util/LocalDateAdapter.java b/src/ru/javawebinar/basejava/util/LocalDateAdapter.java new file mode 100644 index 00000000..02c4efee --- /dev/null +++ b/src/ru/javawebinar/basejava/util/LocalDateAdapter.java @@ -0,0 +1,17 @@ +package ru.javawebinar.basejava.util; + +import java.time.LocalDate; +import javax.xml.bind.annotation.adapters.XmlAdapter; + +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/javawebinar/basejava/util/XmlParser.java b/src/ru/javawebinar/basejava/util/XmlParser.java new file mode 100644 index 00000000..6558cdae --- /dev/null +++ b/src/ru/javawebinar/basejava/util/XmlParser.java @@ -0,0 +1,43 @@ +package ru.javawebinar.basejava.util; + +import java.io.Reader; +import java.io.Writer; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; + +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"); + + 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/javawebinar/basejava/web/ResumeServlet.java b/src/ru/javawebinar/basejava/web/ResumeServlet.java new file mode 100644 index 00000000..b9b274e5 --- /dev/null +++ b/src/ru/javawebinar/basejava/web/ResumeServlet.java @@ -0,0 +1,165 @@ +package ru.javawebinar.basejava.web; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.*; +import javax.servlet.http.*; +import java.io.IOException; +import ru.javawebinar.basejava.Config; +import ru.javawebinar.basejava.model.ContactType; +import ru.javawebinar.basejava.model.Link; +import ru.javawebinar.basejava.model.Organization; +import ru.javawebinar.basejava.model.Resume; +import ru.javawebinar.basejava.model.Section; +import ru.javawebinar.basejava.model.SectionLine; +import ru.javawebinar.basejava.model.SectionList; +import ru.javawebinar.basejava.model.SectionOrganization; +import ru.javawebinar.basejava.model.SectionType; +import ru.javawebinar.basejava.storage.Storage; +import ru.javawebinar.basejava.util.DateUtil; +import ru.javawebinar.basejava.util.HtmlUtil; + +public class ResumeServlet extends HttpServlet { + + private final Storage storage = Config.get().getStorage(); + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws 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 resume; + switch (action) { + case "delete": + storage.delete(uuid); + response.sendRedirect("resume"); + return; + case "view": + resume = storage.get(uuid); + break; + case "add": + resume = Resume.EMPTY; + break; + case "edit": + resume = storage.get(uuid); + for (SectionType type : SectionType.values()) { + Section section = resume.getSection(type); + switch (type) { + case OBJECTIVE: + case PERSONAL: + if (section == null) { + section = SectionLine.EMPTY; + } + break; + case ACHIEVEMENT: + case QUALIFICATION: + if (section == null) { + section = SectionList.EMPTY; + } + break; + case EXPERIENCE: + case EDUCATION: + SectionOrganization orgSection = (SectionOrganization) section; + List emptyFirstOrganizations = new ArrayList<>(); + emptyFirstOrganizations.add(Organization.EMPTY); + if (orgSection != null) { + for (Organization org : orgSection.getOrganizations()) { + List emptyFirstPositions = new ArrayList<>(); + emptyFirstPositions.add(Organization.Position.EMPTY); + emptyFirstPositions.addAll(org.getPositions()); + emptyFirstOrganizations.add( + new Organization(org.getHomePage(), emptyFirstPositions)); + } + } + section = new SectionOrganization(emptyFirstOrganizations); + break; + } + resume.setSection(type, section); + } + break; + default: + throw new IllegalArgumentException("Action " + action + " is illegal"); + } + request.setAttribute("resume", resume); + request.getRequestDispatcher( + ("view".equals(action) ? "/WEB-INF/jsp/view.jsp" : "/WEB-INF/jsp/edit.jsp") + ).forward(request, response); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + String uuid = request.getParameter("uuid"); + String fullName = request.getParameter("fullName"); + Resume resume; + final boolean isNotCreate = (uuid == null || uuid.length() == 0); + if (isNotCreate) { + resume = new Resume(fullName); + } else { + resume = storage.get(uuid); + resume.setFullName(fullName); + } + for (ContactType type : ContactType.values()) { + String value = request.getParameter(type.name()); + if (HtmlUtil.isEmpty(value)) { + resume.getContacts().remove(type); + } else { + resume.setContact(type, value); + } + } + for (SectionType type : SectionType.values()) { + String value = request.getParameter(type.name()); + String[] values = request.getParameterValues(type.name()); + if (HtmlUtil.isEmpty(value) && values.length < 2) { + resume.getSections().remove(type); + } else { + switch (type) { + case OBJECTIVE: + case PERSONAL: + resume.setSection(type, new SectionLine(value)); + break; + case ACHIEVEMENT: + case QUALIFICATION: + resume.setSection(type, new SectionList(value.split("\\n"))); + break; + case EDUCATION: + case EXPERIENCE: + List organizations = 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[] titles = request.getParameterValues(prefix + "title"); + String[] descriptions = request.getParameterValues(prefix + "description"); + for (int j = 0; j < titles.length; j++) { + if (!HtmlUtil.isEmpty(titles[j])) { + positions.add(new Organization.Position(DateUtil.parse(startDates[j]), + DateUtil.parse(endDates[j]), titles[j], descriptions[j])); + } + } + organizations.add(new Organization(new Link(name, urls[i]), positions)); + } + } + resume.setSection(type, new SectionOrganization(organizations)); + break; + } + } + } + if (isNotCreate) { + storage.save(resume); + } else { + storage.update(resume); + } + response.sendRedirect("resume"); + } +} diff --git a/storage/23b0b02e-9e06-484f-bd89-ea4161410f11 b/storage/23b0b02e-9e06-484f-bd89-ea4161410f11 new file mode 100644 index 00000000..9b528d30 --- /dev/null +++ b/storage/23b0b02e-9e06-484f-bd89-ea4161410f11 @@ -0,0 +1,7 @@ + + + 23b0b02e-9e06-484f-bd89-ea4161410f11 + Name3 + + + diff --git a/storage/34f56565-f314-43d4-967d-7e6f4791a952 b/storage/34f56565-f314-43d4-967d-7e6f4791a952 new file mode 100644 index 00000000..4421015d --- /dev/null +++ b/storage/34f56565-f314-43d4-967d-7e6f4791a952 @@ -0,0 +1,34 @@ + + + 34f56565-f314-43d4-967d-7e6f4791a952 + Name2 + + + MOBILE_PHONE + 1234567 + + + SKYPE + @SecondMember + + + + + EXPERIENCE + + + + Organization21 + http://organization21.ru + + + 2018-07-01 + 3000-01-01 + position21 + content21 + + + + + + diff --git a/storage/7f5521fd-c569-4dba-93f2-0c19e285d538 b/storage/7f5521fd-c569-4dba-93f2-0c19e285d538 new file mode 100644 index 00000000..4fc85b3b --- /dev/null +++ b/storage/7f5521fd-c569-4dba-93f2-0c19e285d538 @@ -0,0 +1,101 @@ + + + 7f5521fd-c569-4dba-93f2-0c19e285d538 + Name1 + + + MOBILE_PHONE + +79877654321 + + + SKYPE + newSkype + + + EMAIL + m1@google.com + + + + + OBJECTIVE + + Objective + + + + PERSONAL + + Personal data + + + + ACHIEVEMENT + + Achievement11 + Achievement12 + Achievement13 + + + + QUALIFICATION + + Java + git + SQL + + + + EXPERIENCE + + + + Organization11 + http://organization11.ru + + + 2015-01-01 + 3000-01-01 + position11 + content11 + + + 2010-07-01 + 2014-12-01 + position12 + content12 + + + + + + EDUCATION + + + + university + + + + 2005-09-01 + 2010-06-01 + aspirant + + + + 2001-09-01 + 2005-04-01 + student + IT + + + + + Organization12 + http://organization12.ru + + + + + + diff --git a/test/ru/javawebinar/basejava/TestData.java b/test/ru/javawebinar/basejava/TestData.java new file mode 100644 index 00000000..0d88b051 --- /dev/null +++ b/test/ru/javawebinar/basejava/TestData.java @@ -0,0 +1,63 @@ +package ru.javawebinar.basejava; + +import java.time.Month; +import java.util.UUID; +import ru.javawebinar.basejava.model.ContactType; +import ru.javawebinar.basejava.model.Organization; +import ru.javawebinar.basejava.model.Resume; +import ru.javawebinar.basejava.model.SectionLine; +import ru.javawebinar.basejava.model.SectionList; +import ru.javawebinar.basejava.model.SectionOrganization; +import ru.javawebinar.basejava.model.SectionType; + +public class TestData { + + public static final String UUID_1 = UUID.randomUUID().toString(); + public static final String UUID_2 = UUID.randomUUID().toString(); + public static final String UUID_3 = UUID.randomUUID().toString(); + public static final String UUID_4 = UUID.randomUUID().toString(); + public static final Resume R1; + public static final Resume R2; + public static final Resume R3; + public static final Resume R4; + + static { + R1 = new Resume(UUID_1, "Name1"); + R2 = new Resume(UUID_2, "Name2"); + R3 = new Resume(UUID_3, "Name3"); + R4 = new Resume(UUID_4, "Name4"); + + R1.setContact(ContactType.MOBILE_PHONE, "+79001234567"); + R1.setContact(ContactType.EMAIL, "abc@mail.ru"); + R4.setContact(ContactType.SKYPE, "Skype"); + R4.setContact(ContactType.MOBILE_PHONE, "+76666666666"); + + R1.setSection(SectionType.OBJECTIVE, new SectionLine("Objective")); + R1.setSection(SectionType.PERSONAL, new SectionLine("Personal data")); + R1.setSection(SectionType.ACHIEVEMENT, + new SectionList("Achievement11", "Achievement12", "Achievement13")); + R1.setSection(SectionType.QUALIFICATION, new SectionList("Java", "git", "SQL")); + R1.setSection(SectionType.EXPERIENCE, + new SectionOrganization( + new Organization("Organization11", "http://organization11.ru", + new Organization.Position(2015, Month.JANUARY, "position11", "content11"), + new Organization.Position(2010, Month.JULY, 2014, Month.DECEMBER, "position12", + "content12")))); + R1.setSection(SectionType.EDUCATION, + new SectionOrganization( + new Organization("university", null, + new Organization.Position(2005, Month.SEPTEMBER, 2010, Month.JUNE, "aspirant", + null), + new Organization.Position(2001, Month.SEPTEMBER, 2005, Month.APRIL, "student", + "IT")), + new Organization("Organization12", "http://organization12.ru"))); + + R2.setContact(ContactType.MOBILE_PHONE, "1234567"); + R2.setContact(ContactType.SKYPE, "@SecondMember"); + R2.setSection(SectionType.EXPERIENCE, + new SectionOrganization( + new Organization("Organization21", "http://organization21.ru", + new Organization.Position(2018, Month.JULY, "position21", "content21")))); + } + +} \ No newline at end of file diff --git a/test/ru/javawebinar/basejava/storage/AbstractStorageTest.java b/test/ru/javawebinar/basejava/storage/AbstractStorageTest.java new file mode 100644 index 00000000..38c3ec3b --- /dev/null +++ b/test/ru/javawebinar/basejava/storage/AbstractStorageTest.java @@ -0,0 +1,120 @@ +package ru.javawebinar.basejava.storage; + + +import static ru.javawebinar.basejava.TestData.*; + +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import ru.javawebinar.basejava.exception.ExistStorageException; +import ru.javawebinar.basejava.exception.NotExistStorageException; +import ru.javawebinar.basejava.model.*; +import ru.javawebinar.basejava.Config; + +public abstract class AbstractStorageTest { + + protected static final File STORAGE_DIR = Config.get().getStorageDir(); + + protected Storage storage; + + protected AbstractStorageTest(Storage storage) { + this.storage = storage; + } + + @BeforeEach + public void setUp() { + storage.clear(); + storage.save(R1); + storage.save(R2); + storage.save(R3); + } + + @Test + public void update() { + Resume newResume = new Resume(UUID_1, "New Name"); + R1.setContact(ContactType.EMAIL, "m1@google.com"); + R1.setContact(ContactType.SKYPE, "newSkype"); + R1.setContact(ContactType.MOBILE_PHONE, "+79877654321"); + storage.update(newResume); + Assertions.assertEquals(newResume, storage.get(UUID_1)); + } + + @Test + public void resumeNotExist() { + Assertions.assertThrows(NotExistStorageException.class, + () -> storage.get("dummy")); + } + + @Test + public void size() { + assertSize(3); + } + + @Test + public void save() { + storage.save(R4); + assertSize(4); + assertGet(R4); + } + + @Test + public void saveExist() { + Assertions.assertThrows(ExistStorageException.class, + () -> storage.save(R1)); + } + + @Test + public void delete() { + storage.delete(UUID_1); + assertSize(2); + Assertions.assertThrows(NotExistStorageException.class, + () -> storage.delete(UUID_1)); + } + + @Test + public void deleteNotExist() { + Assertions.assertThrows(NotExistStorageException.class, + () -> storage.delete("dummy")); + } + + @Test + public void get() { + assertGet(R1); + assertGet(R2); + assertGet(R3); + } + + @Test + public void getNotExist() throws Exception { + Assertions.assertThrows(NotExistStorageException.class, + () -> storage.get("dummy")); + } + + @Test + public void getAllSorted() throws Exception { + List testStorage = storage.getAllSorted(); + Assertions.assertEquals(3, testStorage.size()); + List sortedList = Arrays.asList(R1, R2, R3); + Collections.sort(sortedList); + Assertions.assertEquals(sortedList, testStorage); + + } + + @Test + public void clear() throws Exception { + storage.clear(); + assertSize(0); + } + + private void assertSize(int expectedSize) { + Assertions.assertEquals(expectedSize, storage.size()); + } + + private void assertGet(Resume r) { + Assertions.assertEquals(r, storage.get(r.getUuid())); + } +} \ No newline at end of file diff --git a/test/ru/javawebinar/basejava/storage/DataPathStorageTest.java b/test/ru/javawebinar/basejava/storage/DataPathStorageTest.java new file mode 100644 index 00000000..e94129f9 --- /dev/null +++ b/test/ru/javawebinar/basejava/storage/DataPathStorageTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.basejava.storage; + +import ru.javawebinar.basejava.storage.serializer.DataStreamSerializer; + +public class DataPathStorageTest extends AbstractStorageTest { + + public DataPathStorageTest() { + super(new PathStorage(STORAGE_DIR.getAbsolutePath(), new DataStreamSerializer())); + } +} diff --git a/test/ru/javawebinar/basejava/storage/JsonPathStorageTest.java b/test/ru/javawebinar/basejava/storage/JsonPathStorageTest.java new file mode 100644 index 00000000..67a65afc --- /dev/null +++ b/test/ru/javawebinar/basejava/storage/JsonPathStorageTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.basejava.storage; + +import ru.javawebinar.basejava.storage.serializer.JsonStreamSerializer; + +public class JsonPathStorageTest extends AbstractStorageTest { + + protected JsonPathStorageTest() { + super(new PathStorage(STORAGE_DIR.getAbsolutePath(), new JsonStreamSerializer())); + } +} diff --git a/test/ru/javawebinar/basejava/storage/ListStorageTest.java b/test/ru/javawebinar/basejava/storage/ListStorageTest.java new file mode 100644 index 00000000..18cefc3e --- /dev/null +++ b/test/ru/javawebinar/basejava/storage/ListStorageTest.java @@ -0,0 +1,9 @@ +package ru.javawebinar.basejava.storage; + + +public class ListStorageTest extends AbstractStorageTest { + + protected ListStorageTest() { + super(new ListStorage()); + } +} diff --git a/test/ru/javawebinar/basejava/storage/MapResumeStorageTest.java b/test/ru/javawebinar/basejava/storage/MapResumeStorageTest.java new file mode 100644 index 00000000..61f3d752 --- /dev/null +++ b/test/ru/javawebinar/basejava/storage/MapResumeStorageTest.java @@ -0,0 +1,8 @@ +package ru.javawebinar.basejava.storage; + +public class MapResumeStorageTest extends AbstractStorageTest { + + public MapResumeStorageTest() { + super(new MapResumeStorage()); + } +} diff --git a/test/ru/javawebinar/basejava/storage/MapUuidStorageTest.java b/test/ru/javawebinar/basejava/storage/MapUuidStorageTest.java new file mode 100644 index 00000000..46f4e347 --- /dev/null +++ b/test/ru/javawebinar/basejava/storage/MapUuidStorageTest.java @@ -0,0 +1,8 @@ +package ru.javawebinar.basejava.storage; + +public class MapUuidStorageTest extends AbstractStorageTest { + + public MapUuidStorageTest() { + super(new MapUuidStorage()); + } +} diff --git a/test/ru/javawebinar/basejava/storage/ObjectFileStorageTest.java b/test/ru/javawebinar/basejava/storage/ObjectFileStorageTest.java new file mode 100644 index 00000000..5c043798 --- /dev/null +++ b/test/ru/javawebinar/basejava/storage/ObjectFileStorageTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.basejava.storage; + +import ru.javawebinar.basejava.storage.serializer.DataStreamSerializer; + +public class ObjectFileStorageTest extends AbstractStorageTest { + + public ObjectFileStorageTest() { + super(new FileStorage(STORAGE_DIR, new DataStreamSerializer())); + } +} diff --git a/test/ru/javawebinar/basejava/storage/ObjectPathStorageTest.java b/test/ru/javawebinar/basejava/storage/ObjectPathStorageTest.java new file mode 100644 index 00000000..a2acb6ac --- /dev/null +++ b/test/ru/javawebinar/basejava/storage/ObjectPathStorageTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.basejava.storage; + +import ru.javawebinar.basejava.storage.serializer.DataStreamSerializer; + +public class ObjectPathStorageTest extends AbstractStorageTest { + + public ObjectPathStorageTest() { + super(new PathStorage(STORAGE_DIR.getAbsolutePath(), new DataStreamSerializer())); + } +} diff --git a/test/ru/javawebinar/basejava/storage/SortedStorageTest.java b/test/ru/javawebinar/basejava/storage/SortedStorageTest.java new file mode 100644 index 00000000..2d296134 --- /dev/null +++ b/test/ru/javawebinar/basejava/storage/SortedStorageTest.java @@ -0,0 +1,26 @@ +package ru.javawebinar.basejava.storage; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import ru.javawebinar.basejava.exception.StorageException; +import ru.javawebinar.basejava.model.Resume; + +public class SortedStorageTest extends AbstractStorageTest { + + public SortedStorageTest() { + super(new SortedArrayStorage()); + } + + @Test + public void saveOverflow() { + try { + for (int i = 4; i <= AbstractArrayStorage.STORAGE_LIMIT; i++) { + storage.save(new Resume("name" + i)); + } + } catch (StorageException e) { + Assertions.fail("Overflow was too early"); + } + Assertions.assertThrows(StorageException.class, + () -> storage.save(new Resume("Overflow"))); + } +} \ No newline at end of file diff --git a/test/ru/javawebinar/basejava/storage/SqlStorageTest.java b/test/ru/javawebinar/basejava/storage/SqlStorageTest.java new file mode 100644 index 00000000..4dedaecc --- /dev/null +++ b/test/ru/javawebinar/basejava/storage/SqlStorageTest.java @@ -0,0 +1,10 @@ +package ru.javawebinar.basejava.storage; + +import ru.javawebinar.basejava.Config; + +public class SqlStorageTest extends AbstractStorageTest { + + protected SqlStorageTest() { + super(Config.get().getStorage()); + } +} diff --git a/test/ru/javawebinar/basejava/storage/StorageTest.java b/test/ru/javawebinar/basejava/storage/StorageTest.java new file mode 100644 index 00000000..fda05292 --- /dev/null +++ b/test/ru/javawebinar/basejava/storage/StorageTest.java @@ -0,0 +1,26 @@ +package ru.javawebinar.basejava.storage; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import ru.javawebinar.basejava.exception.StorageException; +import ru.javawebinar.basejava.model.Resume; + +public class StorageTest extends AbstractStorageTest { + + public StorageTest() { + super(new ArrayStorage()); + } + + @Test + public void saveOverflow() { + try { + for (int i = 4; i <= AbstractArrayStorage.STORAGE_LIMIT; i++) { + storage.save(new Resume("name" + i)); + } + } catch (StorageException e) { + Assertions.fail("Overflow was too early"); + } + Assertions.assertThrows(StorageException.class, + () -> storage.save(new Resume("Overflow"))); + } +} \ No newline at end of file diff --git a/test/ru/javawebinar/basejava/storage/XmlPathStorageTest.java b/test/ru/javawebinar/basejava/storage/XmlPathStorageTest.java new file mode 100644 index 00000000..f7d736be --- /dev/null +++ b/test/ru/javawebinar/basejava/storage/XmlPathStorageTest.java @@ -0,0 +1,9 @@ +package ru.javawebinar.basejava.storage; + +import ru.javawebinar.basejava.storage.serializer.XmlStreamSerializer; + +public class XmlPathStorageTest extends AbstractStorageTest { + public XmlPathStorageTest() { + super(new PathStorage(STORAGE_DIR.getAbsolutePath(), new XmlStreamSerializer())); + } +} diff --git a/test/ru/javawebinar/basejava/util/JsonParserTest.java b/test/ru/javawebinar/basejava/util/JsonParserTest.java new file mode 100644 index 00000000..86dc94ed --- /dev/null +++ b/test/ru/javawebinar/basejava/util/JsonParserTest.java @@ -0,0 +1,30 @@ +package ru.javawebinar.basejava.util; + +import static org.junit.jupiter.api.Assertions.*; +import static ru.javawebinar.basejava.TestData.R1; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import ru.javawebinar.basejava.model.Resume; +import ru.javawebinar.basejava.model.Section; +import ru.javawebinar.basejava.model.SectionLine; + +class JsonParserTest { + + @Test + void testResume() { + String json = JsonParser.write(R1); + System.out.println(json); + Resume resume = JsonParser.read(json, Resume.class); + Assertions.assertEquals(R1, resume); + } + + @Test + void testSection() { + Section section = new SectionLine("Personal1"); + String json = JsonParser.write(section, Section.class); + System.out.println(json); + Section section2 = JsonParser.read(json, Section.class); + Assertions.assertEquals(section, section2); + } +} \ No newline at end of file diff --git a/web/WEB-INF/jsp/edit.jsp b/web/WEB-INF/jsp/edit.jsp new file mode 100644 index 00000000..9d2d7dc5 --- /dev/null +++ b/web/WEB-INF/jsp/edit.jsp @@ -0,0 +1,138 @@ +<%@ page import="ru.javawebinar.basejava.model.ContactType" %> +<%@ page import="ru.javawebinar.basejava.model.SectionType" %> +<%@ page import="ru.javawebinar.basejava.model.SectionList" %> +<%@ page import="ru.javawebinar.basejava.model.SectionOrganization" %> +<%@ page import="ru.javawebinar.basejava.util.DateUtil" %> +<%@ page import="ru.javawebinar.basejava.model.SectionLine" %> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + + + + Resume ${resume.fullName} + + + +
+
+ +

Name:

+
+ +
+

Contacts:

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

${type.title}

+ + + + + + + + + + + + +
+
Organization name:
+
+
+
+
Organization url:
+
+
+
+
+ + +
+
Start date:
+
+ +
+
+
+
End date:
+
+ +
+
+
Position:
+
+ +
+
+
Description:
+
+ +
+
+
+
+
+
+
+
+ + +
+
+ + + \ No newline at end of file diff --git a/web/WEB-INF/jsp/fragments/footer.jsp b/web/WEB-INF/jsp/fragments/footer.jsp new file mode 100644 index 00000000..5c385ecb --- /dev/null +++ b/web/WEB-INF/jsp/fragments/footer.jsp @@ -0,0 +1,4 @@ +<%@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..ef80f834 --- /dev/null +++ b/web/WEB-INF/jsp/fragments/header.jsp @@ -0,0 +1,3 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" %> +
Resume management
+
diff --git a/web/WEB-INF/jsp/list.jsp b/web/WEB-INF/jsp/list.jsp new file mode 100644 index 00000000..1ca40400 --- /dev/null +++ b/web/WEB-INF/jsp/list.jsp @@ -0,0 +1,39 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ page import="ru.javawebinar.basejava.model.ContactType" %> + + + + + + Resumes list + + + +
+ +
+ + + + + + + + + + + + + + + + +
NameEmail
${resume.fullName}<%=ContactType.EMAIL.toHtml(resume.getContact(ContactType.EMAIL))%> + +
+
+ + + diff --git a/web/WEB-INF/jsp/view.jsp b/web/WEB-INF/jsp/view.jsp new file mode 100644 index 00000000..8274dbe7 --- /dev/null +++ b/web/WEB-INF/jsp/view.jsp @@ -0,0 +1,101 @@ +<%@ page import="ru.javawebinar.basejava.model.SectionLine" %> +<%@ page import="ru.javawebinar.basejava.model.SectionList" %> +<%@ page import="ru.javawebinar.basejava.model.SectionOrganization" %> +<%@ page import="ru.javawebinar.basejava.util.HtmlUtil" %> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + + + + Resume ${resume.fullName} + + + +
+

${resume.fullName} 

+

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

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

${type.title}

+ <%=((SectionLine) section).getContent()%> +
+ <%=((SectionLine) section).getContent()%> +
+
    + +
  • ${item}
  • +
    +
+
+ + + ${org.homePage.name} + + + ${org.homePage.name} + + +
<%=HtmlUtil.formatDates(position)%> +
${position.title}
${position.description}
+ +
+ + + \ No newline at end of file diff --git a/web/WEB-INF/web.xml b/web/WEB-INF/web.xml new file mode 100644 index 00000000..4b72be2d --- /dev/null +++ b/web/WEB-INF/web.xml @@ -0,0 +1,15 @@ + + + + + resumeServlet + ru.javawebinar.basejava.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..10b7f67a --- /dev/null +++ b/web/css/style.css @@ -0,0 +1,64 @@ +header { + background: none repeat scroll 0 0 #A6C9E2; + color: #2E6E9E; + font-size: 20px; + padding: 5px 20px; +} + +footer { + background: none repeat scroll 0 0 #A6C9E2; + color: #2E6E9E; + font-size: 20px; + padding: 5px 20px; + margin: 20px 0; +} + +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; +} + +h2, h3 { + margin: 15px 0 5px; +} + +/*li {*/ +/* margin: 15px 0;*/ +/*}*/ + +/*a[href^="skype:"] {*/ +/* padding-left: 20px !important;*/ +/* background: url(../img/skype.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://www.linkedin.com"] {*/ +/* padding-left: 20px !important;*/ +/* background: url(../img/lin.png) no-repeat center left;*/ +/*}*/ + +/*a[href*="github."] {*/ +/* padding-left: 20px !important;*/ +/* background: url(../img/gh.png) no-repeat center left;*/ +/*}*/ \ No newline at end of file diff --git a/web/img/add.png b/web/img/add.png new file mode 100644 index 00000000..0ea124a7 Binary files /dev/null and b/web/img/add.png differ diff --git a/web/img/delete.png b/web/img/delete.png new file mode 100644 index 00000000..ace289ed Binary files /dev/null and b/web/img/delete.png differ diff --git a/web/img/email.png b/web/img/email.png new file mode 100644 index 00000000..83278739 Binary files /dev/null and b/web/img/email.png differ diff --git a/web/img/gh.png b/web/img/gh.png new file mode 100644 index 00000000..f1c40a4a Binary files /dev/null and b/web/img/gh.png differ diff --git a/web/img/lin.png b/web/img/lin.png new file mode 100644 index 00000000..48678662 Binary files /dev/null and b/web/img/lin.png differ diff --git a/web/img/pencil.png b/web/img/pencil.png new file mode 100644 index 00000000..d5ba3d59 Binary files /dev/null and b/web/img/pencil.png differ diff --git a/web/img/skype.png b/web/img/skype.png new file mode 100644 index 00000000..7d5e24a6 Binary files /dev/null and b/web/img/skype.png differ diff --git a/web/img/so.png b/web/img/so.png new file mode 100644 index 00000000..dae5738a Binary files /dev/null and b/web/img/so.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 00000000..43262363 --- /dev/null +++ b/web/index.html @@ -0,0 +1,10 @@ + + + + + + + +

Resumes list

+ + diff --git a/web/test.html b/web/test.html new file mode 100644 index 00000000..80b8f884 --- /dev/null +++ b/web/test.html @@ -0,0 +1,10 @@ + + + + + Title + + +Test111 + +