Автор Анна Евкова
Преподаватель который помогает студентам и школьникам в учёбе.

Технология «клиент-сервер» (Архитектура "клиент-сервер")

Содержание:

Введение

Клиент-сервер (англ. Client-server) - вычислительная или сетевая архитектура, в которой задания или сетевая нагрузка распределены между поставщиками услуг (сервисов), называемых серверами, и заказчиками услуг, называемых клиентами. Нередко клиенты и серверы взаимодействуют через компьютерную сеть и могут быть как различными физическими устройствами, так и программным обеспечением.

Преимущества

Делает возможным, в большинстве случаев, распределить функции вычислительной системы между несколькими независимыми компьютерами в сети. Это позволяет упростить обслуживание вычислительной системы. В частности, замена, ремонт, модернизация или перемещение сервера, не затрагивают клиентов.

Все данные хранятся на сервере, который, как правило, защищён гораздо лучше большинства клиентов. На сервере проще обеспечить контроль полномочий, чтобы разрешать доступ к данным только клиентам с соответствующими правами доступа.

Позволяет объединить различные клиенты. Использовать ресурсы одного сервера часто могут клиенты с разными аппаратными платформами, операционными системами и т.п.

Недостатки

Неработоспособность сервера может сделать неработоспособной всю вычислительную сеть.

Поддержка работы данной системы, требует отдельного специалиста - системного администратора.

Высокая стоимость оборудования.

1. Архитектура "клиент-сервер"

Применительно к системам баз данных архитектура "клиент-сервер" интересна и актуальна главным образом потому, что обеспечивает простое и относительно дешевое решение проблемы коллективного доступа к базам данных в локальной сети.

1.1 Открытые системы

Реальное распространение архитектуры "клиент-сервер" стало возможным благодаря развитию и широкому внедрению в практику концепции открытых систем. Поэтому мы начнем с краткого введения в открытые системы.

Основным смыслом подхода открытых систем является упрощение комплексирования вычислительных систем за счет международной и национальной стандартизации аппаратных и программных интерфейсов. Главной побудительной причиной развития концепции открытых систем явились повсеместный переход к использованию локальных компьютерных сетей и те проблемы комплексирования аппаратно-программных средств, которые вызвал этот переход. В связи с бурным развитием технологий глобальных коммуникаций открытые системы приобретают еще большее значение и масштабность.

Ключевой фразой открытых систем, направленной в сторону пользователей, является независимость от конкретного поставщика. Ориентируясь на продукцию компаний, придерживающихся стандартов открытых систем, потребитель, который приобретает любой продукт такой компании, не попадает к ней в рабство. Он может продолжить наращивание мощности своей системы путем приобретения продуктов любой другой компании, соблюдающей стандарты. Причем это касается как аппаратных, так и программных средств.

Практической опорой системных и прикладных программных средств открытых систем является стандартизованная операционная система. В настоящее время такой системой является UNIX. Фирмам-поставщикам различных вариантов ОС UNIX в результате длительной работы удалось придти к соглашению об основных стандартах этой операционной системы. Сейчас все распространенные версии UNIX в основном совместимы по части интерфейсов, предоставляемых прикладным (а в большинстве случаев и системным) программистам. Как кажется, несмотря на появление претендующей на стандарт системы Windows NT, именно UNIX останется основой открытых систем в ближайшие годы.

Технологии и стандарты открытых систем обеспечивают реальную и проверенную практикой возможность производства системных и прикладных программных средств со свойствами мобильности (portability) и интероперабельности (interoperability). Свойство мобильности означает сравнительную простоту переноса программной системы в широком спектре аппаратно-программных средств, соответствующих стандартам. Интероперабельность означает упрощения комплексирования новых программных систем на основе использования готовых компонентов со стандартными интерфейсами.

Преимуществом для пользователей является то, что они могут постепенно заменять компоненты системы на более совершенные, не утрачивая работоспособности системы. В частности, в этом кроется решение проблемы постепенного наращивания вычислительных, информационных и других мощностей компьютерной системы.

1.2 Клиенты и серверы локальных сетей

В основе широкого распространения локальных сетей компьютеров лежит известная идея разделения ресурсов. Высокая пропускная способность локальных сетей обеспечивает эффективный доступ из одного узла локальной сети к ресурсам, находящимся в других узлах.

Развитие этой идеи приводит к функциональному выделению компонентов сети: разумно иметь не только доступ к ресурсами удаленного компьютера, но также получать от этого компьютера некоторый сервис, который специфичен для ресурсов данного рода и программные средства. Так мы приходим к различению рабочих станций и серверов локальной сети.

Рабочая станция предназначена для непосредственной работы пользователя или категории пользователей и обладает ресурсами, соответствующими локальным потребностям данного пользователя.

Сервер локальной сети должен обладать ресурсами, соответствующими его функциональному назначению и потребностям сети. Заметим, что в связи с ориентацией на подход открытых систем, правильнее говорить о логических серверах (имея в виду набор ресурсов и программных средств, обеспечивающих услуги над этими ресурсами), которые располагаются не обязательно на разных компьютерах. Особенностью логического сервера в открытой системе является то, что если по соображениям эффективности сервер целесообразно переместить на отдельный компьютер, то это можно проделать без потребности в какой-либо переделке как его самого, так и использующих его прикладных программ.

Примерами сервером могут служить:

•сервер телекоммуникаций, обеспечивающий услуги по связи данной локальной сети с внешним миром;

•вычислительный сервер, дающий возможность производить вычисления, которые невозможно выполнить на рабочих станциях;

•дисковый сервер, обладающий расширенными ресурсами внешней памяти и предоставляющий их в использование рабочим станциями и, возможно, другим серверам;

•файловый сервер, поддерживающий общее хранилище файлов для всех рабочих станций;

•сервер баз данных фактически обычная СУБД, принимающая запросы по локальной сети и возвращающая результаты.

Сервер локальной сети предоставляет ресурсы (услуги) рабочим станциям и/или другим серверам.

Принято называть клиентом локальной сети, запрашивающий услуги у некоторого сервера и сервером - компонент локальной сети, оказывающий услуги некоторым клиентам.

1.3 Системная архитектура "клиент-сервер"

Понятно, что в общем случае, чтобы прикладная программа, выполняющаяся на рабочей станции, могла запросить услугу у некоторого сервера, как минимум требуется некоторый интерфейсный программный слой, поддерживающий такого рода взаимодействие (было бы по меньшей мере неестественно требовать, чтобы прикладная программа напрямую пользовалась примитивами транспортного уровня локальной сети). Из этого, собственно, и вытекают основные принципы системной архитектуры "клиент-сервер".

Система разбивается на две части, которые могут выполняться в разных узлах сети, - клиентскую и серверную части. Прикладная программа или конечный пользователь взаимодействуют с клиентской частью системы, которая в простейшем случае обеспечивает просто надсетевой интерфейс. Клиентская часть системы при потребности обращается по сети к серверной части. Заметим, что в развитых системах сетевое обращение к серверной части может и не понадобиться, если система может предугадывать потребности пользователя, и в клиентской части содержатся данные, способные удовлетворить его следующий запрос.

Интерфейс серверной части определен и фиксирован. Поэтому возможно создание новых клиентских частей существующей системы (пример интероперабельности на системном уровне).

Основной проблемой систем, основанных на архитектуре "клиент-сервер", является то, что в соответствии с концепцией открытых систем от них требуется мобильность в как можно более широком классе аппаратно-программных решений открытых систем. Даже если ограничиться UNIX-ориентированными локальными сетями, в разных сетях применяется разная аппаратура и протоколы связи. Попытки создания систем, поддерживающих все возможные протоколы, приводит к их перегрузке сетевыми деталями в ущерб функциональности.

Еще более сложный аспект этой проблемы связан с возможностью использования разных представлений данных в разных узлах неоднородной локальной сети. В разных компьютерах может существовать различная адресация, представление чисел, кодировка символов и т.д. Это особенно существенно для серверов высокого уровня: телекоммуникационных, вычислительных, баз данных.

Общим решением проблемы мобильности систем, основанных на архитектуре "клиент-сервер" является опора на программные пакеты, реализующие протоколы удаленного вызова процедур (RPC - Remote Procedure Call). При использовании таких средств обращение к сервису в удаленном узле выглядит как обычный вызов процедуры. Средства RPC, в которых, естественно, содержится вся информация о специфике аппаратуры локальной сети и сетевых протоколов, переводит вызов в последовательность сетевых взаимодействий. Тем самым, специфика сетевой среды и протоколов скрыта от прикладного программиста.

При вызове удаленной процедуры программы RPC производят преобразование форматов данных клиента в промежуточные машинно-независимые форматы и затем преобразование в форматы данных сервера. При передаче ответных параметров производятся аналогичные преобразования.

Если система реализована на основе стандартного пакета RPC, она может быть легко перенесена в любую открытую среду.

Технология “клиент-сервер” применительно к СУБД сводится к разделению системы на две части – приложение-клиент (front-end) и сервер базы данных (back-end). Эта архитектура совмещает лучшие черты обработки данных на мэйнфреймах и технологии “файл-сервер”. От мэйнфреймов технология “клиент-сервер” позаимствовала такие черты, как централизованное администрирование, безопасность, надежность. От технологии “файл-сервер” унаследованы низкая стоимость и возможность распределенной обработки данных, используя ресурсы компьютеров-клиентов. Сейчас графический интерфейс пользователя стал стандартом для систем “клиент-сервер”. Кроме того, архитектура “клиент-сервер” значительно упрощает и ускоряет разработку приложений за счет того, что правила проверки целостности данных находятся на сервере. Неправильно работающее клиентское приложение не может привести к потере или искажению данных. Все эти возможности, ранее свойственные только сложным и дорогостоящим системам, сейчас доступны даже небольшим организациям. Стоимость оборудования, программного обеспечения и обслуживания для персональных компьютеров в десятки раз ниже, чем для мэйнфреймов.

1.4 Серверы баз данных

Термин "сервер баз данных" обычно используют для обозначения всей СУБД, основанной на архитектуре "клиент-сервер", включая и серверную, и клиентскую части. Такие системы предназначены для хранения и обеспечения доступа к базам данных.

Хотя обычно одна база данных целиком хранится в одном узле сети и поддерживается одним сервером, серверы баз данных представляют собой простое и дешевое приближение к распределенным базам данных, поскольку общая база данных доступна для всех пользователей локальной сети.

1.5 Принципы взаимодействия между клиентскими и серверными частями

Доступ к базе данных от прикладной программы или пользователя производится путем обращения к клиентской части системы. В качестве основного интерфейса между клиентской и серверной частями выступает язык баз данных SQL.

Это язык по сути дела представляет собой текущий стандарт интерфейса СУБД в открытых системах. Собирательное название SQL-сервер относится ко всем серверам баз данных, основанных на SQL.

Серверы баз данных, интерфейс которых основан исключительно на языке SQL, обладают своими преимуществами и своими недостатками. Очевидное преимущество – стандартность интерфейса. В пределе, хотя пока это не совсем так, клиентские части любой SQL-ориентированной СУБД могли бы работать с любым SQL-сервером вне зависимости от того, кто его произвел.

Недостаток тоже довольно очевиден. При таком высоком уровне интерфейса между клиентской и серверной частями системы на стороне клиента работает слишком мало программ СУБД. Это нормально, если на стороне клиента используется маломощная рабочая станция. Но если клиентский компьютер обладает достаточной мощностью, то часто возникает желание возложить на него больше функций управления базами данных, разгрузив сервер, который является узким местом всей системы.

Одним из перспективных направлений СУБД является гибкое конфигурирование системы, при котором распределение функций между клиентской и пользовательской частями СУБД определяется при установке системы.

1.6 Преимущества протоколов удаленного вызова процедур

Упоминавшиеся выше протоколы удаленного вызова процедур особенно важны в системах управления базами данных, основанных на архитектуре "клиент-сервер".

Во-первых, использование механизма удаленных процедур позволяет действительно перераспределять функции между клиентской и серверной частями системы, поскольку в тексте программы удаленный вызов процедуры ничем не отличается от удаленного вызова, и следовательно, теоретически любой компонент системы может располагаться и на стороне сервера, и на стороне клиента.

Во-вторых, механизм удаленного вызова скрывает различия между взаимодействующими компьютерами. Физически неоднородная локальная сеть компьютеров приводится к логически однородной сети взаимодействующих программных компонентов. В результате пользователи не обязаны серьезно заботиться о разовой закупке совместимых серверов и рабочих станций.

1.7 Типичное разделение функций между клиентами и серверами

В типичном на сегодняшний день случае на стороне клиента СУБД работает только такое программное обеспечение, которое не имеет непосредственного доступа к базам данных, а обращается для этого к серверу с использованием языка SQL.

В некоторых случаях хотелось бы включить в состав клиентской части системы некоторые функции для работы с "локальным кэшем" базы данных, т.е. с той ее частью, которая интенсивно используется клиентской прикладной программой. В современной технологии это можно сделать только путем формального создания на стороне клиента локальной копии сервера базы данных и рассмотрения всей системы как набора взаимодействующих серверов.

С другой стороны, иногда хотелось бы перенести большую часть прикладной системы на сторону сервера, если разница в мощности клиентских рабочих станций и сервера чересчур велика. Вобщем-то при использовании RPC это сделать нетрудно. Но требуется, чтобы базовое программное обеспечение сервера действительно позволяло это. В частности, при использовании ОС UNIX проблемы практически не возникают.

1.8 Архитектуры процессора базы данных

Основная часть любой системы “клиент-сервер” – это сервер БД. Со времени возникновения архитектуры “клиент-сервер” появилось много вариантов архитектуры процессора БД, поскольку он во многом определяет успех всей системы. Основное требование к серверу БД – обеспечение минимального времени выполнения запросов при максимально возможном числе пользователей. Существуют две основные архитектуры для построения процессора БД: архитектура с несколькими процессами и многопоточная архитектура.

1. Архитектура с несколькими процессами

Характеризуется тем, что несколько экземпляров исполняемого файла работают одновременно. Эти системы отличаются хорошей масштабируемостью, но требуют значительных расходов памяти, так как память каждому экземпляру приложения выделяется отдельно. Эта архитектура подразумевает наличие эффективного механизма взаимодействия процессов и полагается на операционную систему при разделении процессорного времени между отдельными экземплярами приложения. Самый известный пример сервера, построенного по этой архитектуре, - Oracle Server. Когда пользователь подключается к БД Oracle, он в действительности запускает отдельный экземпляр исполняемого файла процессора базы данных.

2. Многопоточная архитектура

Эта архитектура использует только один исполняемый файл, с несколькими потоками исполнения. Главное преимущество – более скромные требования к оборудованию, чем для архитектуры с несколькими процессами. Здесь сервер берет на себя разделение времени между отдельными потоками, иногда давая преимущество некоторым задачам над другими. Кроме того, отпадает необходимость в сложном механизме взаимодействия процессов. По этой архитектуре построены MS SQL Server и Sybase SQL Server.

2. Трехуровневая архитектура “клиент-сервер”

На верхнем уровне абстрагирования взаимодействия клиента и сервера достаточно четко можно выделить следующие компоненты:

•презентационная логика (Presentation Layer - PL), предназначенная для работы с данными пользователя;

•бизнес-логика (Business Layer - BL), предназначенная для проверки правильности данных, поддержки ссылочной целостности ..;

•логика доступа к ресурсам (Access Layer - AL), предназначенная для хранения данных;

Таким образом можно, можно придти к нескольким моделям клиент-серверного взаимодействия:

Наиболее часто встречающийся вариант реализации архитектуры клиент-сервер в уже внедренных и активно используемых системах. Такая модель подразумевает объединение в клиентском приложении как PL, так и BL, таким образом, обеспечивается полная децентрализация управления бизнес-логикой. Однако в случае необходимости выполнения каких-либо изменений в клиентском приложении придется менять исходный код. Серверная часть, при описанном подходе, представляет собой сервер баз данных, реализующий AL. К описанной модели часто применяют аббревиатуру RDA - Remote Data Access.

Модель, начинающая активно использоваться в корпоративной среде в связи с распространением Internet-технологий и, в первую очередь, Web-браузеров. В этом случае клиентское приложение обеспечивает реализацию PL, поэтому клиент может довольствоваться довольно скромной аппаратной платформой, а сервер объединяет BL и AL. Максимальная загрузка сервера предусматривает выполнение бизнес-логики только с помощью хранимых процедур сервера (Хранимые процедуры – откомпилированные SQL-инструкции, хранящиеся на сервере). Это позволяет максимально централизовать контроль над данными и легко изменять правила работы сразу для целого предприятия. С другой стороны, незначительная корректировка правил, касающаяся только части пользователей, потребует длительной процедуры согласования. В этом случае невозможно реализовать какие-то исключения из общих правил для некоторых пользователей или приложений. В принципе, это хорошо и является залогом безопасности и целостности данных.

Модель с физически выделенным в отдельное приложение блоком BL, таким образом получаем трехуровневую архитектуру “клиент-сервер”. На сервере БД может функционировать “универсальная” часть бизнес-логики (правила на уровне предприятия или группы связанных приложений). Такая схема позволяет поддерживать тонких клиентов на пользовательских компьютерах и в то же время разгрузить сервер БД от чрезмерной загрузки при сохранении гибкой системы работы с бизнес-правилами. В качестве промежуточного сервера может использоваться второй SQL-сервер, но чаще рациональней задействовать персональную СУБД, которая менее требовательна к аппаратным ресурсам и может обеспечить удобные средства построения и поддержки бизнес-логики.

3. Программные средства разработки

3.1 Разработка ПО клиент-сервер

Модель клиент сервер легко реализуется средствами современных языков программирования. В основу программы данной курсовой работы лёг язык прорграммирования JAVA в среде Intellij Idea.

Рис.1 Интерфейс среды программирования.

В программе была реализована система «Музыкальная коллекция», в которой на сервере хранятся исходные данные треков и вызываются в графическом клиенте пользователей по запросу. Обработка и принятие запросов происходит через систему SOCKET с мультипоточной серверной частью. При включении сервера ранее сохраненные треки загружаются из базы данных, реализованной в виде простейших XML документов(доступных для испорта и экспорта), и программа ожидает подключения клиентов. При включении клиента программа автоматически пытается подключится по введённому адресу, и в случае успеха отображает полученный список.

Рис.2 Интерфейс клиента.

Клиентская прогроамма поддерживает функции удаления, добавления а так-же редактирования трека или нескольких треков в базе данных.

Рис.3 Функции редактирования трека.

При запуске функции редактирования или создания трека открывается дополнительное диалоговое окно с исходными данными трека в котором можно изменить названия, автора и прочие параметры.

Рис.4 Диалоговое окно редактирования трека.

Так-же в этом окне можно изменить жанр, либо открыть на кнопу дополнительное окно редактирования жанров.

Рис.5 Диалоговое окно редактирования жанров.

В качестве модели взаимодействия выбран шаблон MVC — схема разделения данных приложения, пользовательского интерфейса и управляющей логики на три отдельных компонента: модель, представление и контроллер — таким образом, что модификация каждого компонента может осуществляться независимо.

  • Модель (Model) предоставляет данные и реагирует на команды контроллера, изменяя своё состояние.
  • Представление (View) отвечает за отображение данных модели пользователю, реагируя на изменения модели.
  • Контроллер (Controller) интерпретирует действия пользователя, оповещая модель о необходимости изменений.

Рис.6 Шаблон программирования MVC.

Заключение

В результате выполнения курсовой работы можно понять, как работает система клиент-сервер. Была продемонстрирована реализация программы с применением современных технологий разработки и языка программирования Java. Разработанная программа полностью функциональна и хорошо выполняет функции клиент-сервеной части.

В общем случае для организации работы пользователей сети с информационными ресурсами, распределенными по различным компьютерам, необходимы три составляющих:

программа, установленная на компьютере пользователя, которая может осуществлять сетевой запрос с целью получения объекта, и предназначенная для его обработки (например, просмотра, изменения или печати документа);

программа, установленная, как правило, на компьютере, где расположен информационный объект, которая может осуществлять по запросу поиск и пересылку объекта, а также упорядочивание доступа к нему нескольких пользователей;

правила (протокол) взаимодействия между этими программами.

Следует особо отметить, что набор действий, понимаемых как запрашиваемая услуга, – это не обязательно чтение (получение) объекта. В том числе это может быть сохранение (запись), пересылка объекта и т.д.

Список используемой литературы

  1. Шилдт Г. “Java. Руководство для начинающих” изд. “Диалектика” 2019
  2. Microsoft Press “Секреты создания интрасетей” изд. “Питер” Санкт-Петербург, 1998.

Размещено н

Приложение

Листинг программы

Controller.java

package com.infosystem.mvc.controllers;

import com.infosystem.XML.JAXB.JAXB;
import com.infosystem.mvc.models.Genre;
import com.infosystem.mvc.models.Track;
import com.infosystem.utils.Root;
import org.slf4j.Logger;
import org.xml.sax.SAXException;

import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.File;
import java.io.Serializable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Pattern;

public class Controller {
private Root root;
private final Genre DEFAULT_GENRE = new Genre();

public Controller(Logger logger) {
root = new Root();
File file = new File("dataBase.xml");
try {
if(file.exists()) {
root = (Root) JAXB.unmarshaller(Root.class, file);
}
} catch (JAXBException | SAXException | ParserConfigurationException | TransformerException e1) {
logger.error(e1.getMessage());
}
}
public Controller(Root tracks) {
this.root = tracks;
}
public synchronized boolean addTrack(Track track){
////////compare by name and artist//////
for (Track track1 : root.getTracks()){
if(track1.getName().equals(track.getName()) && track1.getArtist().equals(track.getArtist())){
return false;
}
}
//////////////////////////////////////////////////
track.setID(root.getFreeIDForTrack(track.getID()));
root.getTracks().add(track);
return true;
}
public synchronized void addGenre(Genre genre){
////////compare by name//////
for (Genre genre1 : root.getGenres()){
if(genre1.getName().equals(genre.getName())){
return;
}
}
//////////////////////////////////////////////////
root.getGenres().add(new Genre(genre.getName(), root.getFreeIDForGenre(genre.getID())));
}
public synchronized void deleteTrack(int trackID){
for(Track track : root.getTracks()){
if(track.getID() == trackID) {
root.getTracks().remove(track);
}
}
}
public synchronized void deleteGenre(int genreID){
for(Genre genre : root.getGenres()){
if(genre.getID() == genreID) {
root.getGenres().remove(genre);
break;
}
}
for(Track track : root.getTracks()){
if(track.getGenreID() == genreID) {
track.setID(-1);
}
}
}
public synchronized CopyOnWriteArrayList<Track> getTracks(){
return root.getTracks();
}
public synchronized CopyOnWriteArrayList<Genre> getGenres(){
return root.getGenres();
}
public synchronized void replaceTrack(Track trackOriginal, Track trackNew){
int i = root.getTracks().indexOf(trackOriginal);
if(i != -1 && root.getTracks().indexOf(trackNew) == -1){
Track trackOne = new Track(trackNew.getName(), trackNew.getArtist(), trackNew.getAlbum(), trackNew.getLength(), trackNew.getGenreID(), trackNew.getID());
root.getTracks().set(i, trackOne);
}
}
public synchronized void replaceGenre(String genreOriginal, String genreNew){
for(Genre genre : root.getGenres()){
if(genre.getName().equals(genreOriginal)){
root.getGenres().get(root.getGenres().indexOf(genre)).setName(genreNew);
}
}
}
/** @return count of added {@link Track} */
public synchronized int addTracks(Root root) throws InterruptedException {
int coutOfAdded = 0;
for(Track track : root.getTracks()){
if (addTrack(track)) {
coutOfAdded++;
}
}
return coutOfAdded;
}
/** if {@param } searchText is empty return all tracks
*
@param searchText is regexp tracks filter
*
@return all matching {@link Track} */
public synchronized CopyOnWriteArrayList<Track> getMatchingTracks(String searchText){
CopyOnWriteArrayList<Track> foundTracks = new CopyOnWriteArrayList<>();
if (searchText.equals("")) {
return root.getTracks();
}
else {
Pattern pattern = Pattern.compile(searchText);
String genreName;
for(Track track : root.getTracks()) {
genreName = "";
for(Genre genre : root.getGenres()){
if(genre.getID() == track.getGenreID()){
genreName = genre.getName();
break;
}
}
if(pattern.matcher(track.toString()+ genreName).find()) {
foundTracks.add(track);
}
}
return foundTracks;
}
}
public Root getRoot() {
return root;
}
}

Genre.java

package com.infosystem.mvc.models;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import java.io.Serializable;

@XmlAccessorType(XmlAccessType.FIELD)
public class Genre implements Serializable {
@XmlAttribute(name="name")
private String name;
@XmlAttribute(name="id")
private int id;

public Genre(String name, int id) {
this.name = name;
this.id = id;
}
public Genre() {
this.name = "unknown";
id = -1;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getID() {
return id;
}
public void setID(int id) {
this.id = id;
}
@Override
public String toString(){
return name;
}
@Override
public boolean equals(Object obj){
if (obj == null) return false;
if (!(obj instanceof Genre)) return false;
return ((Genre)obj).getName().equals(this.name) && ((Genre)obj).getID() == this.id;
}
@Override
public int hashCode(){
int hash = 31;
hash = hash * 17 + name.hashCode();
hash = hash * 17 + id;
return hash;
}
}

Track.java

package com.infosystem.mvc.models;

import javax.xml.bind.annotation.*;
import java.io.Serializable;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"name", "artist", "album", "length", "genreID", "id"})
public class Track implements Serializable{
@XmlAttribute(name = "name")
private String name;
@XmlAttribute(name = "artist")
private String artist;
@XmlAttribute(name = "album")
private String album;
@XmlAttribute(name = "length")
private int length; // в секундах
@XmlAttribute(name = "genreID")
private int genreID;
@XmlAttribute(name="id")
private int id;

public Track() {
name = "";
artist = "";
album = "";
length = 0;
genreID = -1;
}
public Track(String name, String artist, String album, int length, int genreID, int id){
this.name = name;
this.artist = artist;
this.length = length;
this.album = album;
this.genreID = genreID;
this.id = id;
}
public String getName() {
return name;
}
public String getArtist() {
return artist;
}
public String getAlbum() {
return album;
}
public int getLength() {
return length;
}
public int getGenreID() {
return genreID;
}
public void setGenreID(int genreID) {
this.genreID = genreID;
}
public int getID() {
return id;
}
public void setID(int id) {
this.id = id;
}
public synchronized Object[] getFieldsAsDataArray(){
return new Object[]{name, artist, album, length, genreID};
}
@Override
public boolean equals(Object obj){
if (obj == null) return false;
if (!(obj instanceof Track)) return false;
Track o = (Track) obj;
return o.album.equals(this.album)
&& o.artist.equals(this.artist)
&& o.name.equals(this.name)
&& o.length == this.length
&& o.genreID == this.genreID
&& o.id == this.id;
}
@Override
public int hashCode(){
int hash = 31;
hash = hash * 17 + name.hashCode();
hash = hash * 17 + artist.hashCode();
hash = hash * 17 + album.hashCode();
hash = hash * 17 + genreID;
hash = hash * 17 + length;
hash = hash * 17 + id;
return hash;
}
@Override
public String toString(){
return name + " " + artist + " " + album + " " + length;
}
}

GenreEditor.java

package com.infosystem.mvc.views;

import com.infosystem.mvc.models.Genre;
import com.infosystem.net.socket.client.TCPClient;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.net.InetAddress;
import java.util.concurrent.CopyOnWriteArrayList;

class GenresEditor extends JFrame {
private JTable table;
private TCPClient tcpClient;

GenresEditor(TCPClient tcpClient) throws HeadlessException, IOException, ClassNotFoundException {
super("Genres Editor");
this.tcpClient = tcpClient;
table = new JTable() {
public boolean isCellEditable(int row, int column) {
return column != 0;
}
};
refreshTable();
JScrollPane tableScrollPane = new JScrollPane(table);
JButton addGenreButton = new JButton("Add New Genre");
addGenreButton.addActionListener((ActionEvent e) -> {
String name = JOptionPane.showInputDialog("Input new Genre Name");
if(name != null && !name.equals("")) {
try {
this.tcpClient.addGenre(new Genre(name, -1));
refreshTable();
} catch (ClassNotFoundException | IOException e1) {
JOptionPane.showMessageDialog(null, "Genre creating error!");
}
}
});
this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
this.setSize(500, 500);
this.add(tableScrollPane);
this.add(BorderLayout.AFTER_LAST_LINE, addGenreButton);
}
private void refreshTable() throws IOException, ClassNotFoundException {
//swing dont have default cell button. its suck
table.setModel(new DefaultTableModel(genresToRawDataArray(tcpClient.getGenres()), new String[]{"Genre", "", ""}));
table.getColumnModel().getColumn(1).setCellEditor(new ButtonEditor(new JCheckBox()) );
table.getColumnModel().getColumn(1).setCellRenderer(new ButtonRenderer() );
table.getColumnModel().getColumn(2).setCellEditor(new ButtonEditor(new JCheckBox()) );
table.getColumnModel().getColumn(2).setCellRenderer(new ButtonRenderer() );
table.getTableHeader().setReorderingAllowed(false);
}

private Object[][] genresToRawDataArray(CopyOnWriteArrayList<Genre> genres) {
Object[][] genresData = new Object[genres.size()][];

for (int i = 0; i < genres.size(); i++) {
genresData[i] = new Object[3];
genresData[i][0] = genres.get(i).getName();
genresData[i][1] = "Edit";
genresData[i][2] = "Delete";
}
return genresData;
}

class ButtonRenderer extends JButton implements TableCellRenderer {
ButtonRenderer() {
setOpaque(true);
}

@Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(UIManager.getColor("Button.background"));
}
setText((value == null) ? "" : value.toString());
return this;
}
}

class ButtonEditor extends DefaultCellEditor {
JButton button;
private String label;
//private boolean isPushed;

ButtonEditor(JCheckBox checkBox) {
super(checkBox);
button = new JButton();
button.setOpaque(true);
button.addActionListener(e -> {
if (button.getText().equals("Edit")) {
String oldName = table.getValueAt(table.getSelectedRows()[0], 0).toString();
String newName = JOptionPane.showInputDialog("Edit genre", oldName);
if (newName != null && !newName.equals(oldName)){
try {
tcpClient.replaceGenre(oldName, newName);
refreshTable();
} catch (IOException | ClassNotFoundException e1) {
JOptionPane.showMessageDialog(null, "Genre edit error!");
}
}
} else {
if (JOptionPane.showConfirmDialog(null, "Do U wanna delete Genre: "
+ table.getValueAt(table.getSelectedRows()[0], 0).toString()) == 0) {
try {
CopyOnWriteArrayList<Genre> genres = tcpClient.getGenres();
tcpClient.deleteGenre(genres.get(table.getSelectedRows()[0]).getID());
refreshTable();
} catch (IOException | ClassNotFoundException e1) {
JOptionPane.showMessageDialog(null, "Genre delete error!");
}
}
}
fireEditingStopped();
});
}

@Override
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
if (isSelected) {
button.setForeground(table.getSelectionForeground());
button.setBackground(table.getSelectionBackground());
} else {
button.setForeground(table.getForeground());
button.setBackground(table.getBackground());
}
label = (value == null) ? "" : value.toString();
button.setText(label);
//isPushed = true;
return button;
}

@Override
public Object getCellEditorValue() {
// isPushed = false;
return label;
}

@Override
public boolean stopCellEditing() {
//isPushed = false;
return super.stopCellEditing();
}
}
}

MainForm.java

package com.infosystem.mvc.views;

import com.infosystem.mvc.models.Genre;
import com.infosystem.mvc.models.Track;
import com.infosystem.net.socket.client.TCPClient;
import com.infosystem.XML.JAXB.JAXB;
import com.infosystem.utils.HintTextFieldUI;
import com.infosystem.utils.Root;
import org.xml.sax.SAXException;

import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.DefaultTableModel;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.prefs.Preferences;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MainForm extends JFrame {
static final String[] TABLE_HEADER = new String[]{"Name", "Artist", "Album", "Length", "Genre"};
private JTable table;
private JTextField adressTextField;

private TCPClient tcpClient;
private static final String IPADDRESS_PATTERN = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";

public MainForm() throws UnknownHostException, ParseException {
super("Track Base");
adressTextField = new JTextField("127.0.0.1");
loadPreferences();
try {
tcpClient = new TCPClient(InetAddress.getByName(adressTextField.getText()));
tcpClient.run();
adressTextField.setBackground(Color.GREEN);
} catch (Exception e) {
JOptionPane.showMessageDialog(null, "Connection Failed!");
adressTextField.setBackground(Color.RED);
}
try {
table = new JTable(new DefaultTableModel(getTracksAsRawDataArray(), TABLE_HEADER)) {
public boolean isCellEditable(int row, int column) {
return false;
}
};
} catch (Exception e) {
JOptionPane.showMessageDialog(null, "Connection Lost!");
adressTextField.setBackground(Color.RED);
table = new JTable();
}
table.getTableHeader().setReorderingAllowed(false);
//cell douvle click
table.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (e.getClickCount() == 2 && table.getSelectedRow() != -1) {
TrackEditForm trackEditForm = null;
try {
trackEditForm = new TrackEditForm(tcpClient, TrackEditForm.FormState.Edit, table.getSelectedRows());
} catch (IOException | ClassNotFoundException e1) {
JOptionPane.showMessageDialog(null, "Edit Track Fail!");
}
assert trackEditForm != null;
trackEditForm.setLocationRelativeTo(table);
trackEditForm.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
try {
table.setModel(new DefaultTableModel(getTracksAsRawDataArray(), TABLE_HEADER));
} catch (IOException | ClassNotFoundException e1) {
JOptionPane.showMessageDialog(null, "Connection Lost!");
adressTextField.setBackground(Color.RED);
}

}
});
table.clearSelection();
}
}
/*@Override
public void mouseReleased(MouseEvent e)
{
rIndex = table.rowAtPoint(e.getPoint());
cIndex= table.columnAtPoint(e.getPoint());
if(e.getButton() == MouseEvent.BUTTON3){
popup.show(e.getComponent(), e.getX(), e.getY());
}
}*/

});
JScrollPane tableScrollPane = new JScrollPane(table);
JMenuBar menuBar = new JMenuBar();
menuBar.setBackground(Color.lightGray);
JMenu menu = new JMenu("DataBase");
//Export
JMenuItem menuItem = new JMenuItem("Export", new ImageIcon("resources/exp.png"));
menuItem.addActionListener((ActionEvent e) -> {
Root root = null;
try {
root = tcpClient.getRoot();
} catch (IOException | ClassNotFoundException e1) {
JOptionPane.showMessageDialog(null, "Connection Lost!");
adressTextField.setBackground(Color.RED);
}
try {
JAXB.marshall(root, new File("exportedDB.xml"));
JOptionPane.showMessageDialog(null, "Database Exported!",
"InfoBox!", JOptionPane.INFORMATION_MESSAGE
);
} catch (JAXBException | ParserConfigurationException | TransformerException | SAXException e1) {
JOptionPane.showMessageDialog(null, "Database Export Fail!",
"InfoBox!", JOptionPane.INFORMATION_MESSAGE
);
}
});
menu.add(menuItem);
menu.addSeparator();
//Import
menuItem = new JMenuItem("Import", new ImageIcon("resources/imp.png"));
menuItem.addActionListener((ActionEvent e) -> {
JFileChooser jFileChooser = new JFileChooser();
jFileChooser.setDialogTitle("Choose XML serialized DB");
jFileChooser.removeChoosableFileFilter(jFileChooser.getChoosableFileFilters()[0]);
jFileChooser.setFileFilter(new FileNameExtensionFilter("XML DataBase", "xml"));
int ret = jFileChooser.showDialog(null, "Открыть файл");
if (ret == JFileChooser.APPROVE_OPTION) {
File file = jFileChooser.getSelectedFile();
try {
int countOfAddedItems = tcpClient.addTracks((Root) JAXB.unmarshaller(Root.class, file));
JOptionPane.showMessageDialog(
null, "Database Added succefully " + countOfAddedItems + " items",
"InfoBox!", JOptionPane.INFORMATION_MESSAGE
);
table.setModel(new DefaultTableModel(getTracksAsRawDataArray(), TABLE_HEADER));
} catch (IOException | ClassNotFoundException | JAXBException | SAXException | ParserConfigurationException | TransformerException e1) {
JOptionPane.showMessageDialog(null, "Database Add Fail!",
"InfoBox!", JOptionPane.INFORMATION_MESSAGE
);
}
}
});
menu.add(menuItem);
menuBar.add(menu);
menu = new JMenu("Edit");
//Add Track
menuItem = new JMenuItem("Add New Track", new ImageIcon("resources/add.png"));
menuItem.addActionListener((ActionEvent e) -> {
try {
TrackEditForm trackEditForm = new TrackEditForm(tcpClient, TrackEditForm.FormState.Create, new int[0]);
trackEditForm.setLocationRelativeTo(this);
trackEditForm.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
try {
table.setModel(new DefaultTableModel(getTracksAsRawDataArray(), TABLE_HEADER));
} catch (IOException | ClassNotFoundException e1) {
JOptionPane.showMessageDialog(null, "Connection Lost!");
adressTextField.setBackground(Color.RED);
}

}
});
} catch (Exception ex) {
JOptionPane.showMessageDialog(null, "Adding Fail!",
"InfoBox!", JOptionPane.INFORMATION_MESSAGE
);
}
});
menu.add(menuItem);
menu.addSeparator();
//Delete Tracks
menuItem = new JMenuItem("Delete Selected Tracks", new ImageIcon("resources/del.png"));
menuItem.addActionListener((ActionEvent e) -> {
int[] selectedRowsIndexes = table.getSelectedRows();
if (selectedRowsIndexes.length > 0) {
try {
CopyOnWriteArrayList<Track> tracks = tcpClient.getTracks();
for (int selectedRowsIndexe : selectedRowsIndexes) {
tcpClient.deleteTrack(tracks.get(selectedRowsIndexe).getID());
}
table.setModel(new DefaultTableModel(getTracksAsRawDataArray(), TABLE_HEADER));
} catch (Exception ec) {
JOptionPane.showMessageDialog(null, "Connection Lost!");
adressTextField.setBackground(Color.RED);
}
}
});
menu.add(menuItem);
menu.addSeparator();
//Edit Tracks
menuItem = new JMenuItem("Edit Selected Tracks", new ImageIcon("resources/ed.png"));
menuItem.addActionListener((ActionEvent e) -> {
if(table.getSelectedRows().length > 0) {
//swing and normal multithreading? I dont think so
try {
TrackEditForm trackEditForm = new TrackEditForm(tcpClient, TrackEditForm.FormState.Edit, table.getSelectedRows());
trackEditForm.setLocationRelativeTo(this);
trackEditForm.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
try {
table.setModel(new DefaultTableModel(getTracksAsRawDataArray(), TABLE_HEADER));
} catch (IOException | ClassNotFoundException e1) {
JOptionPane.showMessageDialog(null, "Connection Lost!");
adressTextField.setBackground(Color.RED);
}
}
});
table.clearSelection();
} catch (Exception ec) {
JOptionPane.showMessageDialog(null, "Editing Tracks Failed!",
"InfoBox!", JOptionPane.INFORMATION_MESSAGE
);
}
}
});
menu.add(menuItem);
menu.addSeparator();
//Edit Genres
menuItem = new JMenuItem("Edit Genres", new ImageIcon("resources/ed.png"));
menuItem.addActionListener((ActionEvent e) -> {
//check double open bug
try {
GenresEditor genresEditor = new GenresEditor(tcpClient);
genresEditor.setLocationRelativeTo(this);
genresEditor.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
CopyOnWriteArrayList<Track> a = null;
try {
table.setModel(new DefaultTableModel(getTracksAsRawDataArray(), TABLE_HEADER));
} catch (IOException | ClassNotFoundException e1) {
JOptionPane.showMessageDialog(null, "Connection Lost!");
adressTextField.setBackground(Color.RED);
}

super.windowClosed(e);
}
});
genresEditor.setVisible(true);
} catch (IOException | ClassNotFoundException e1) {
JOptionPane.showMessageDialog(null, "Load Genres Fail!");
}
});
menu.add(menuItem);
menuBar.add(menu);

menu = new JMenu("Net");
//Connect
menuItem = new JMenuItem("Connect", new ImageIcon("resources/add.png"));
menuItem.addActionListener((ActionEvent e) -> {
Matcher matcher = Pattern.compile(IPADDRESS_PATTERN).matcher(adressTextField.getText());
if (matcher.matches()) {
try {
if (tcpClient != null) tcpClient.close();
tcpClient = new TCPClient(InetAddress.getByName(adressTextField.getText()));
tcpClient.run();
adressTextField.setBackground(Color.GREEN);
table.setModel(new DefaultTableModel(getTracksAsRawDataArray(), TABLE_HEADER));
} catch (ClassNotFoundException | IOException e1) {
JOptionPane.showMessageDialog(null, "Connection Lost!");
adressTextField.setBackground(Color.RED);
}
}
});
menu.add(menuItem);
menu.addSeparator();
//IP field
menu.add(adressTextField);
menuBar.add(menu);
JTextField searchTextField = new JTextField(25);
searchTextField.setUI(new HintTextFieldUI("Enter search words", false));
//поисковик
searchTextField.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_ENTER){
try {
CopyOnWriteArrayList<Track> searchTracks = tcpClient.getMatchingTracks(((JTextField)menuBar.getComponent(3)).getText());
CopyOnWriteArrayList<Genre> genres = tcpClient.getGenres();
table.setModel(new DefaultTableModel(getTracksAsRawDataArray(searchTracks, genres), TABLE_HEADER));
} catch (IOException | ClassNotFoundException e1) {
JOptionPane.showMessageDialog(null, "Loading search results Fail!");
}
}
super.keyReleased(e);
}
});
menuBar.add(searchTextField);

this.setTitle("Information System");
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.getContentPane().add(BorderLayout.NORTH, menuBar);
this.add(tableScrollPane);
// this.pack(); optimize frame size
this.addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
loadPreferences();
super.windowOpened(e);
}

@Override
public void windowClosing(WindowEvent e) {
savePreferences();
try {
tcpClient.close();
} catch (IOException e1) {
JOptionPane.showMessageDialog(null, "Closing connection Fail!");
}
System.exit(0);
}
});
}

private String[] getRowAt(int row) {
return new String[]{table.getModel().getValueAt(row, 0).toString(),
table.getModel().getValueAt(row, 1).toString(),
table.getModel().getValueAt(row, 2).toString(),
table.getModel().getValueAt(row, 3).toString(),
table.getModel().getValueAt(row, 4).toString()};
}

private Object[][] getTracksAsRawDataArray() throws IOException, ClassNotFoundException {

CopyOnWriteArrayList<Genre> genres = tcpClient.getGenres();
CopyOnWriteArrayList<Track> tracks = tcpClient.getTracks();
return getTracksAsRawDataArray(tracks, genres);
}

private Object[][] getTracksAsRawDataArray(CopyOnWriteArrayList<Track> tracks, CopyOnWriteArrayList<Genre> genres) throws IOException, ClassNotFoundException {
Object[][] tracksData = new Object[tracks.size()][];
for (int i = 0; i < tracks.size(); i++) {
tracksData[i] = tracks.get(i).getFieldsAsDataArray();
Object cell = tracksData[i][4];
for (Genre genre : genres) {
if (genre.getID() == (int)tracksData[i][4]) {
tracksData[i][4] = genre.getName();
break;
}
}
if(cell.equals(tracksData[i][4])){
tracksData[i][4] = "unknown";
}
}
return tracksData;
}

private void loadPreferences() {
Preferences prefs = Preferences.userRoot();
this.setSize(prefs.getInt("widthMainWindow", 700), prefs.getInt("hightMainWindow", 500));
adressTextField.setText(prefs.get("host", "127.0.0.1"));
}

private void savePreferences() {
Rectangle frameBounds = this.getBounds();
Preferences prefs = Preferences.userRoot();
prefs.put("host", adressTextField.getText());
prefs.putInt("widthMainWindow", frameBounds.width);
prefs.putInt("hightMainWindow", frameBounds.height);
}
}

TrackEditForm.java

package com.infosystem.mvc.views;

import com.infosystem.mvc.models.Genre;
import com.infosystem.mvc.models.Track;
import com.infosystem.net.socket.client.TCPClient;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

class TrackEditForm extends JFrame{
public enum FormState{Edit, Create}
private JTextField[] jTxtFlds;
private Track orignTrack;
private int[] selectedTrackIndexes;
private int megaIndex = 0;
private JButton okButton, cancelButton, genreEditButton;
private JComboBox jComboBox;
private TCPClient tcpClient;

TrackEditForm(TCPClient client, FormState formState, int[] selectedTrackIndexes) throws IOException, ClassNotFoundException {
super(FormState.Edit + " 1/" + selectedTrackIndexes.length +" tracks");
this.tcpClient = client;
this.selectedTrackIndexes = selectedTrackIndexes;
jComboBox = new JComboBox(tcpClient.getGenres().toArray());

genreEditButton = new JButton( "Edit Genres");
genreEditButton.addActionListener((ActionEvent e) -> {
try {
GenresEditor genresEditor = new GenresEditor(tcpClient);
genresEditor.setLocationRelativeTo(this);
genresEditor.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
loadComboboxGenre();
super.windowClosed(e);
}
});
genresEditor.setVisible(true);
} catch (IOException | ClassNotFoundException e1) {
JOptionPane.showMessageDialog(null, "Load Genres Fail!",
"InfoBox!", JOptionPane.INFORMATION_MESSAGE
);
}
});
loadComboboxGenre();
okButton = new JButton( "OK");
okButton.addActionListener(e -> {
if(formState == FormState.Create && !jTxtFlds[0].getText().equals("") && !jTxtFlds[1].getText().equals("")){
try {
if (jTxtFlds[3].getText().equals("") | jTxtFlds[3].getText() == null) {
jTxtFlds[3].setText("0");
}
tcpClient.addTrack(new Track(jTxtFlds[0].getText(), jTxtFlds[1].getText(),
jTxtFlds[2].getText(),Integer.parseInt(jTxtFlds[3].getText()), ((Genre)jComboBox.getSelectedItem()).getID(), -1));
setVisible(false);
JOptionPane.showMessageDialog(null, "Track Added!",
"InfoBox!", JOptionPane.INFORMATION_MESSAGE
);
dispose();
} catch (Exception ex) {
JOptionPane.showMessageDialog(null, "Adding Track Fail!",
"InfoBox!", JOptionPane.INFORMATION_MESSAGE
);
}
}else if(!jTxtFlds[0].getText().equals("") && !jTxtFlds[1].getText().equals("")){
try {
if (jTxtFlds[3].getText().equals("") | jTxtFlds[3].getText() == null) {
jTxtFlds[3].setText("0");
}
tcpClient.replaceTrack(orignTrack, new Track(jTxtFlds[0].getText(), jTxtFlds[1].getText(),
jTxtFlds[2].getText(), Integer.parseInt(jTxtFlds[3].getText()), ((Genre)jComboBox.getSelectedItem()).getID(), -1));
JOptionPane.showMessageDialog(null, "Track Edited!",
"InfoBox!", JOptionPane.INFORMATION_MESSAGE
);
megaIndex++;
} catch (Exception ex) {
JOptionPane.showMessageDialog(null, "Editing Track Fail!",
"InfoBox!", JOptionPane.INFORMATION_MESSAGE
);
}
if(megaIndex == selectedTrackIndexes.length){
setVisible(false);
dispose();
}else try {
formCreate(formState, selectedTrackIndexes);
} catch (IOException | ClassNotFoundException e1) {
JOptionPane.showMessageDialog(null, "Open new Track Fail!",
"InfoBox!", JOptionPane.INFORMATION_MESSAGE
);
}
}
});
cancelButton = new JButton("Cancel");
cancelButton.addActionListener(e -> {
megaIndex++;
if(megaIndex >= selectedTrackIndexes.length){
setVisible(false);
dispose();
}else {
try {
formCreate(formState, selectedTrackIndexes);
} catch (IOException | ClassNotFoundException e1) {
JOptionPane.showMessageDialog(null, "Open new Track Fail!",
"InfoBox!", JOptionPane.INFORMATION_MESSAGE
);
}
}
});
formCreate(formState, selectedTrackIndexes);
}

private void formCreate(FormState formState, int[] selectedTrackIndexes) throws IOException, ClassNotFoundException {
setTitle(formState + " " + String.valueOf(megaIndex+1) + "/" + selectedTrackIndexes.length +" tracks");
CopyOnWriteArrayList<Track> tracks = tcpClient.getTracks();

if(formState == FormState.Edit) {
orignTrack = tracks.get(selectedTrackIndexes[megaIndex]);
}
JPanel northPanel = new JPanel() {
public Insets getInsets() {
return new Insets(8, 8, 8, 8);
}
};
JPanel southPanel = new JPanel() {
public Insets getInsets() {
return new Insets(8, 8, 8, 8);
}
};
northPanel.setLayout(new BoxLayout(northPanel, BoxLayout.Y_AXIS));
JLabel[] jLabels = new JLabel[5];
jTxtFlds = new JTextField[5];

for (int i = 0; i < 4; i++) {
jLabels[i] = new JLabel(MainForm.TABLE_HEADER[i]);
if(formState == FormState.Edit) {
jTxtFlds[i] = new JTextField(tracks.get(selectedTrackIndexes[megaIndex]).getFieldsAsDataArray()[i].toString(), 20);
}else{
jTxtFlds[i] = new JTextField(20);
}
northPanel.add(jLabels[i]);
northPanel.add(jTxtFlds[i]);
}

northPanel.add(new JLabel(MainForm.TABLE_HEADER[4]));
loadComboboxGenre();
northPanel.add(jComboBox);
northPanel.add(genreEditButton);
if(getContentPane().getComponentCount()>0) getContentPane().remove(0);
getContentPane().add(BorderLayout.NORTH, northPanel);
if(getComponentCount()>1) {
remove(1);
remove(0);
}
southPanel.add(okButton);
southPanel.add(cancelButton);
getContentPane().removeAll();
getContentPane().add(BorderLayout.NORTH, northPanel);
getContentPane().add(BorderLayout.SOUTH, southPanel);
pack();
setVisible(true);
}

private void loadComboboxGenre(){
try{
jComboBox.removeAllItems();
CopyOnWriteArrayList<Genre> genres = tcpClient.getGenres();
CopyOnWriteArrayList<Track> tracks = tcpClient.getTracks();
jComboBox.setModel(new DefaultComboBoxModel(tcpClient.getGenres().toArray()));
int genreID = -1;
if(selectedTrackIndexes.length > 0) {
genreID = tracks.get(selectedTrackIndexes[megaIndex]).getGenreID();
}
if(genreID == -1 ){
jComboBox.setSelectedIndex(-1);
jComboBox.setSelectedItem(new Genre());
}else{
for(Genre genre : genres){
if(genre.getID() == genreID){
jComboBox.setSelectedIndex(genres.indexOf(genre));
}
}
}
} catch (IOException | ClassNotFoundException e1) {
JOptionPane.showMessageDialog(null, "Reload Genres Fail!",
"InfoBox!", JOptionPane.INFORMATION_MESSAGE
);
}
}
}

TcpClient.java

package com.infosystem.net.socket.client;

import com.infosystem.mvc.models.Genre;
import com.infosystem.mvc.models.Track;
import com.infosystem.utils.Root;

import javax.swing.*;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.concurrent.CopyOnWriteArrayList;

public class TCPClient extends Thread {
private Socket socket;
private ObjectInputStream objInStream;
private ObjectOutputStream objOutStream;
private InetAddress inetAddress;
public TCPClient(InetAddress inetAddress) throws IOException {
socket = new Socket(inetAddress, 8808);
socket.setKeepAlive(true);
this.inetAddress = inetAddress;
}

public void run() {
try {
objOutStream = new ObjectOutputStream(socket.getOutputStream());
objOutStream.flush();
objInStream = new ObjectInputStream(socket.getInputStream());
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Client could not connect to server!");
}
}

public synchronized void addTrack(Track track) throws IOException {
objOutStream.writeObject("addTrack");
objOutStream.writeUnshared(track);
objOutStream.flush();
objOutStream.reset();
}

public synchronized void addGenre(Genre genre) throws IOException {
objOutStream.writeObject("addGenre");
objOutStream.writeUnshared(genre);
objOutStream.flush();
objOutStream.reset();
}

/** @return count of added {@link Track} */
public synchronized int addTracks(Root tracks) throws IOException, ClassNotFoundException {
objOutStream.writeObject("addTracks");
objOutStream.writeUnshared(tracks);
objOutStream.flush();
objOutStream.reset();
Object object = objInStream.readUnshared();
try{
return (int) object;
}catch (Exception e){
JOptionPane.showMessageDialog(null, "Recive data error: invalid data received!");
return 0;
}
}

public synchronized Root getRoot() throws IOException, ClassNotFoundException {
objOutStream.writeObject("getRoot");
objOutStream.flush();
Object object = objInStream.readUnshared();
try{
return (Root) object;
}catch (Exception e){
JOptionPane.showMessageDialog(null, "Recive data error: invalid data received!");
return new Root();
}
}

public synchronized CopyOnWriteArrayList<Track> getTracks() throws IOException, ClassNotFoundException {
CopyOnWriteArrayList<Track> tracks = new CopyOnWriteArrayList<>();
objOutStream.writeObject("getTracks");
objOutStream.flush();
Object object = objInStream.readUnshared();
try{
return (CopyOnWriteArrayList<Track>) object;
}catch (Exception e){
JOptionPane.showMessageDialog(null, "Recive data error: invalid data received!");
return new CopyOnWriteArrayList<Track>();
}
}

public synchronized CopyOnWriteArrayList<Genre> getGenres() throws IOException, ClassNotFoundException {
objOutStream.writeObject("getGenres");
objOutStream.flush();
Object object = objInStream.readUnshared();
try{
return (CopyOnWriteArrayList<Genre>) object;
}catch (Exception e){
JOptionPane.showMessageDialog(null, "Recive data error: invalid data received!");
return new CopyOnWriteArrayList<Genre>();
}
}

public synchronized CopyOnWriteArrayList<String> getGenresAsString() throws IOException, ClassNotFoundException {
CopyOnWriteArrayList<String> strings = new CopyOnWriteArrayList<>();
objOutStream.writeObject("getGenres");
objOutStream.flush();
Object object = objInStream.readUnshared();
try{
return (CopyOnWriteArrayList<String>) object;
}catch (Exception e){
JOptionPane.showMessageDialog(null, "Recive data error: invalid data received!");
return new CopyOnWriteArrayList<String>();
}
}

/** if {@param } searchText is empty return all tracks
*
@param searchText is regexp tracks filter
*
@return all matching {@link Track} */
public synchronized CopyOnWriteArrayList<Track> getMatchingTracks(String searchText) throws IOException, ClassNotFoundException {
objOutStream.writeObject("getMatchingTracks");
objOutStream.writeObject(searchText);
objOutStream.flush();
objOutStream.reset();
Object object = objInStream.readUnshared();
try{
return (CopyOnWriteArrayList<Track>) object;
}catch (Exception e){
JOptionPane.showMessageDialog(null, "Recive data error: invalid data received!");
return new CopyOnWriteArrayList<>();
}
}

public synchronized void replaceTrack(Track trackOriginal, Track trackNew) throws IOException {
objOutStream.writeObject("replaceTrack");
objOutStream.writeObject(trackOriginal);
objOutStream.writeObject(trackNew);
objOutStream.flush();
objOutStream.reset();
}

public synchronized void deleteTrack(int trackID) throws IOException {
objOutStream.writeObject("deleteTrack");
objOutStream.writeObject(trackID);
objOutStream.flush();
}

public synchronized void replaceGenre(String genreOriginal, String genreNew) throws IOException {
objOutStream.writeObject("replaceGenre");
objOutStream.writeObject(genreOriginal);
objOutStream.writeObject(genreNew);
objOutStream.flush();
objOutStream.reset();
}

public synchronized void deleteGenre(int genreID) throws IOException {
objOutStream.writeObject("deleteGenre");
objOutStream.writeObject(genreID);
objOutStream.flush();
objOutStream.reset();
}

public synchronized void close() throws IOException {
objOutStream.close();
objOutStream.close();
socket.close();
}
}

MultiThreadServer.java

package com.infosystem.net.socket.server;

import com.infosystem.mvc.controllers.Controller;
import com.infosystem.XML.JAXB.JAXB;
import com.infosystem.utils.Root;
import org.xml.sax.SAXException;

import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Timer;
import java.util.TimerTask;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MultiThreadServer {
public static Logger logger;
public static void main(String[] args) throws Exception {
logger = LoggerFactory.getLogger(MultiThreadServer.class);
Controller controller = new Controller(logger);
ServerSocket serverSocket = new ServerSocket();
ServerTimerTask serverTimerTask = new ServerTimerTask(controller);
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(serverTimerTask, 0, 30*1000);

try {
serverSocket = new ServerSocket(8808);
long clientID = 0;
logger.info("Server Started!");
while (true) {
Socket serverClient = serverSocket.accept(); // сервер принимает запрос на подключение клиента
clientID++;
logger .info("Client IP:" + serverClient.getInetAddress() + " started! ID: " + clientID);
Server sct = new Server(serverClient, clientID, controller, logger); // отправляем запрос в отдельный поток
sct.start();
}
} catch (Exception e) {
logger.error(e.getMessage());
}

Root root = controller.getRoot();
ServerSocket finalServerSocket = serverSocket;
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
JAXB.marshall(root, new File("dataBase.xml"));
logger .info("DataBase Saved!");
finalServerSocket.close();
} catch (IOException | JAXBException | ParserConfigurationException | TransformerException | SAXException e) {
logger.error(e.getMessage());
}
}
});
}

static class ServerTimerTask extends TimerTask{
private Controller controller;
private ServerTimerTask(Controller controller) {
this.controller = controller;
}
@Override
public void run() {
Root root = controller.getRoot();
try {
JAXB.marshall(root, new File("dataBase.xml"));
} catch (JAXBException | ParserConfigurationException | SAXException | TransformerException e) {
e.printStackTrace();
}
logger .info("DataBase Saved!");
}
}
}

Server.java

package com.infosystem.net.socket.server;

import com.infosystem.mvc.controllers.Controller;
import com.infosystem.mvc.models.Genre;
import com.infosystem.mvc.models.Track;
import com.infosystem.utils.Root;

import java.io.*;
import java.net.Socket;
import java.util.concurrent.CopyOnWriteArrayList;

import org.slf4j.Logger;

class Server extends Thread {
private Controller controller;
private Socket clientSocket;
private long clientNo;
private Logger logger;

Server(Socket clientSocket, long counter, Controller controller, Logger logger) throws IOException {
this.logger = logger;
this.clientSocket = clientSocket;
clientNo = counter;
this.controller = controller;
}

public void run() {
try {
ObjectOutputStream objOutStream = new ObjectOutputStream(clientSocket.getOutputStream());
objOutStream.flush();
ObjectInputStream objInStream = new ObjectInputStream(clientSocket.getInputStream());
OutputStream outputStream = clientSocket.getOutputStream();
outputStream.flush();

String clientMessage = "";
while (!clientMessage.equals("bye")) {
clientMessage = objInStream.readObject().toString();
logger.info(">>Client No: " + clientNo + " send command = " + clientMessage);
switch (clientMessage) {
case "getRoot":
objOutStream.writeUnshared(controller.getRoot());
objOutStream.flush();
objOutStream.reset();
logger.info(">>Server SEND to client №:" + clientNo + " root obj");
break;
case "addTrack":
controller.addTrack((Track) objInStream.readUnshared()); //do operation with model
logger.info(">>Server RECIVE from client №:" + clientNo + " track");
break;
case "addGenre":
controller.addGenre((Genre)objInStream.readUnshared()); //do operation with model
logger.info(">>Server RECIVE from client №:" + clientNo + " genre");
break;
case "addTracks":
int res = controller.addTracks((Root) objInStream.readUnshared());
objOutStream.writeUnshared(res); //send count of added tracks
objOutStream.flush();
objOutStream.reset();
logger.info(">>Server SEND to client №:" + clientNo + " count of added tracks");
break;
case "getTracks": {
objOutStream.writeUnshared(controller.getTracks()); //send tracks count
objOutStream.flush();
objOutStream.reset();
logger.info(">>Server SEND to client №:" + clientNo + " " + controller.getTracks().size() + " tracks");
break;
}
case "getGenres": {
objOutStream.writeUnshared(controller.getGenres());
objOutStream.flush();
objOutStream.reset();
logger.info(">>Server SEND to client №:" + clientNo + " " + controller.getGenres().size() + " genres");
break;
}
case "getMatchingTracks":
CopyOnWriteArrayList<Track> matchingTracks = controller.getMatchingTracks(objInStream.readObject().toString()); //do operation with model
objOutStream.writeUnshared(matchingTracks); //send tracks count
objOutStream.flush();
objOutStream.reset();
logger.info(">>Server SEND to client №:" + clientNo + " " + controller.getTracks().size() + " matching tracks");
break;
case "replaceTrack":
controller.replaceTrack((Track) objInStream.readObject(), (Track) objInStream.readObject()); //do operation with model
logger.info(">>Server RECIVE from client №:" + clientNo + " track for replace");
break;
case "replaceGenre":
controller.replaceGenre(objInStream.readObject().toString(), objInStream.readObject().toString()); //do operation with model
logger.info(">>Server RECIVE from client №:" + clientNo + " genre for replace");
break;
case "deleteTrack":
controller.deleteTrack((int) objInStream.readObject()); //do operation with model
logger.info(">>Server RECIVE from client №:" + clientNo + " track for deleting");
break;
case "deleteGenre":
controller.deleteGenre((int) objInStream.readObject()); //do operation with model
logger.info(">>Server RECIVE from client №:" + clientNo + " genre for deleting");
break;
}
}
objInStream.close();
objOutStream.close();
clientSocket.close();
} catch (ClassNotFoundException | IOException | InterruptedException ex) {
logger.error(ex.getLocalizedMessage());
} finally {
logger.info("Client №" + clientNo + " exit!! ");
}
}
}

Root.java

package com.infosystem.utils;

import com.infosystem.mvc.models.Genre;
import com.infosystem.mvc.models.Track;

import javax.xml.bind.annotation.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.concurrent.CopyOnWriteArrayList;

@XmlType
@XmlRootElement(name = "dataBase")
public class Root implements Serializable {
private CopyOnWriteArrayList<Track> tracks;
private CopyOnWriteArrayList<Genre> genres;
public Root() {
tracks = new CopyOnWriteArrayList<>();
genres = new CopyOnWriteArrayList<>();
}
@XmlElementWrapper(name = "tracks", nillable = true)
@XmlElement(name="track", type = Track.class)
public CopyOnWriteArrayList<Track> getTracks() {
return tracks;
}
public void setTracks(CopyOnWriteArrayList<Track> tracks) {
this.tracks = tracks;
}
public int getFreeIDForTrack(int posibleIndex) {
CopyOnWriteArrayList<Track> sortTracks = new CopyOnWriteArrayList<>(tracks);
sortTracks.sort((lhs, rhs) -> {
// -1 - less than, 1 - greater than, 0 - equal, all inversed for descending
return lhs.getID() < rhs.getID() ? -1 : (lhs.getID() > rhs.getID()) ? 1 : 0;
});
//есть ли похожий индекс если есть - меняю
if(posibleIndex != -1) {
for (Track track : sortTracks) {
if (track.getID() == posibleIndex) {
posibleIndex = -1;
break;
}
}
}
for (Track track : sortTracks) {
posibleIndex++;
if (track.getID() != posibleIndex) {
return posibleIndex;
}
}
return posibleIndex;
}
@XmlElementWrapper(name = "genres", nillable = true)
@XmlElement(name="genre", type = Genre.class)
public CopyOnWriteArrayList<Genre> getGenres() {
return genres;
}
public void setGenres(CopyOnWriteArrayList<Genre> genres) {
this.genres = genres;
}
public int getFreeIDForGenre(int posibleIndex) {
CopyOnWriteArrayList<Genre> sortGenres = new CopyOnWriteArrayList<>(genres);
sortGenres.sort((lhs, rhs) -> {
// -1 - less than, 1 - greater than, 0 - equal, all inversed for descending
return lhs.getID() < rhs.getID() ? -1 : (lhs.getID() > rhs.getID()) ? 1 : 0;
});
//есть ли похожий индекс если есть - меняю
if(posibleIndex != -1) {
for (Genre genre : sortGenres) {
if (genre.getID() == posibleIndex) {
posibleIndex = -1;
break;
}
}
}
for (Genre genre : sortGenres) {
posibleIndex++;
if (genre.getID() != posibleIndex) {
return posibleIndex;
}
}
return posibleIndex;
}
}

JAXB.java

package com.infosystem.XML.JAXB;

import org.xml.sax.SAXException;
import javax.xml.bind.*;
import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.File;

public class JAXB {
public static <T> void marshall(T object, File file) throws JAXBException, ParserConfigurationException, SAXException, TransformerException {
JAXBContext jaxbContext = JAXBContext.newInstance(object.getClass());
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
JAXBIntrospector introspector = jaxbContext.createJAXBIntrospector();
if(null == introspector.getElementName(object)) {
QName qName = new QName(object.getClass().getCanonicalName(), object.getClass().getSimpleName());
JAXBElement<Object> jaxbElement = new JAXBElement<>(qName, Object.class, object);
marshaller.marshal(jaxbElement, file);
} else {
marshaller.marshal(object, file);
}
}

public static Object unmarshaller(Class<?> objClass, File file) throws JAXBException, ParserConfigurationException, SAXException, TransformerException{
JAXBContext jaxbContext = JAXBContext.newInstance(objClass);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
return unmarshaller.unmarshal(file);
}
}

Runner.java

package com.infosystem;

import com.infosystem.mvc.views.MainForm;
import javax.swing.*;

public class Runner {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
try {
MainForm mainForm = new MainForm();
mainForm.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}