Клиент для Google Wave в виде Java Web Application

Здравствуйте.

sametimed

Итак, я написал небольшой проект на Java, который представляет собой клиент к Google Wave, который, в свою очередь, вы можете расширить необходимыми вам возможностями. Визуально выглядит он не очень презентабельно (впрочем, а-ля Windows 3.11, почему бы и нет), поскольку веб-дизайнер из меня не очень хороший, однако абсолютно за весь исходный код я отвечаю головой :). Так, например, требующиеся для отображения CSS-стили при этом выглядят максимально понятно, а XHTML-структура максимально упрощена (не в ущерб стандартам) для того, чтобы настоящий дизайнер мог развернуться и сделать действительно красиво :).

screenshot

Сайт, исходные коды, и war-пакет с текущим состоянием проекта находятся по адресу [http://code.google.com/p/sametimed](http://code.google.com/p/sametimed)

Сейчас у меня нет под рукой общедоступного хостинга, чтобы я мог прямо вот так запустить проект и дать поиграться с ним не отходя от хабра, однако, я могу предложить вам видео (на vimeo), демонстирующее его основные возможности, что, думаю, тоже вполне ничего, а запустить проект вы сможете сами, благо подробные инструкции есть на сайте проекта, а развёрнутые пояснения и подробное описание работы (и руководство к расширению возможностей) — внутри…

video

Установка

Для того, чтобы запустить этот клиент, нужно, по идее, четыре вещи:

Чуть более подробные инструкции как его запустить сам по себе или в виде рабочего проекта для Eclipse представлены на странице проекта. Здесь же я рассмотрю структуру кода и опишу как он действует (пока без что UML-диаграмм, извините, по мере возможности я их добавлю).

Общая концепция

Здесь под сочетанием «серверная сторона/часть» подразумевается не сервер wave-protocol, а серверная строна веб-приложения.

Поскольку в wave-protocol сейчас существует только консольный клиент, релизованный в виде jar-приложения, основной вопрос состоит в своевременной передаче информации с клиента на серверную часть и наоборот.

Посему стало необходимо ввести два понятия:

  • Команда: отправляется с клиента на сервер, требует «открыть волну», «добавить участника», «отменить действие», «сказать привет» и т.п., отправляется по действию пользователя.
  • Сообщение: отправляется с сервера на клиент, сообщает о том, что «кто-то пригласил в волну», «sonya ответила привет», «добавился участник», «произошла ошибка», отправляется при любом обновлении.

При этом, команду с клиента можно отправить сразу, сильно не задумываясь о нагрузке на сервер, а вот сообщения об обновлениях требуется отсылать только по факту происшествия. Однако, на клиенте находится javascript, который без сложных приспоcоблений не может осуществлять такие вещи. Здесь потребовался Reverse Ajax, вернее его реализация для Java — библиотека DWR (Direct Web Remoting), она позволяет вызывать функцию на клинете с сервера в тот момент, когда решит сервер, а не клиент. Их сайт рассказывает о всех возможностях, которые вовсе не ограничены данным применением, но для проекта, как я уже сказал, из всех возможностей я использовал только эту.

Команды и сообщения передаются в виде XML, а содержимое сообщений об обновлениях в виде JSON, засчёт чего в построении интерфейса участвует только JavaScript (я использовал JQuery), а серверная сторона даже не задумывается о существовании UI.

(для серверной стороны в проекте есть интерфейс Renderer, реализация которого вызывается при необходимых обновлениях, но он предназначен как раз для случаев вроде консольного клиента)

Описание процесса, происходящего в текущей версии

При запуске клиента, вы видите страницу с полем для ввода юзернейма и единственной кнопкой. Wave-protocol сервер при этом должен быть запущен. При нажатии на кнопку введённый вами юзернейм передаётся в сервлет (GetClientViewServlet), который подключает вас «к волне», и возвращает всю структуру (модель) клиента в виде JSON. JQuery на клиентской стороне строит весь интерфейс вейвлета. Если вы нажмёте кнопку ещё раз, вы запросите ещё один вейвлет, при этом между собой они будут различаться по внутреннему ID (сгенерированному автоматически), указанному на синей полоске в скобках. По этому ID и сервер и клиент определяют, к какому клиенту относится команда и/или сообщение.

Одновременно с этим DWR начинает ожидать вестей об обновлениях, а вы в это время можете ввести любую из доступных команд в поле консоли и нажать «send». Например, создать волну командой «\new» , открыть её командой «\open <id>» и что-нибудь сказать, набрав просто текст (как в скайпе). При нажатии на кнопку «send» отправляется POST-запрос к другому сервлету (CommandsReceiverServlet), который получает сгенерированную в виде XML команду и тут же её выполняет, передавая данные серверу wave-protocol.

Обновления в данный момент приходят с серверной стороны (и от сервера wave-protocol) в виде XML-сообщения (на клиентской стороне есть обработчик, который вызывается при получении новых сообщений), в котором содержится алиас изменившейся модели (например, “chat”, “inbox”, “userslist”, “errors” или “editor”) и её содержимое в виде JSON, которое тут же обрабатывается и обновляет соответствующую часть вейвлета.

Как видите, всё до невозможности просто.

Структура проекта и возможности для расширения

Java

  • name.shamansir.sametimed.wave Все классы, лежащие «снаружи» и относящиеся к клиенту непосредственно; здесь находятся абстрактные классы AUpdatingWavelet и ADocumentsWavelet — определяющие структуру соответствующего типа вейвлетов (обновляющийся вейвлет и его расширение, вейвлет содержащий документы). Класс SimpleWavelet служит примером такой реализации. Класс WavesClient занимается обработкой команд и именно он возвращает модель содержащегося в нём вейвлета в GetClientViewServlet.
  • name.shamansir.sametimed.wave.messaging Все, что относится к командам и сообщениям; Идентификаторы команд/сообщений в CommandID/MessageTypeID, сами классы Command и UpdateMessage, сервлет-обработчик CommandsReceiverServlet.
  • name.shamansir.sametimed.wave.model Классы, описывающие модель вейвлета; Они содержат описание каждой суб-модели, вроде списка участников, чата или текстового документа. И ModelID, описывающий возможные модели с абстрактным классом AModel, конечно. Плюс, ModelFactory, фабрика моделей.
  • name.shamansir.sametimed.wave.model.base Значения моделей, вроде «набора строк чата», «набора блоков текста в документе», «списка доступных волн» и т.д.; Именно в этих классах описывается преобразование в JSON.
  • name.shamansir.sametimed.wave.model.base.atom То, из чего состоят значения, если это необходимо для их структуры — «строка чата», «блок текста», «идентификатор волны»;
  • name.shamansir.sametimed.wave.model.base.chat Вейвлет с функцией чата и клиент для него;
  • name.shamansir.sametimed.wave.model.base.editor Вейвлет с поддержкой редактируемого документа, на данный момент не имплементирован, поэтому отключен;
  • name.shamansir.sametimed.wave.render Классы, относящиеся к рендерингу; Здесь находится тот самый класс JSUpdatesListener, который средствами DWR вызывает функцию обновления на клиенте.

Самый логичный способ расширения — имплементация класса ADocumentsWavelet или расширение класса WaveletWithChat. Поскольку, в наиболее вероятном случае, вы будете оперировать понятием «документ» (А чат или что-либо другое, как раз являются документами), то именно такой способ должен подойти вам лучше всего. Также вам понадобится задуматься о модели данного документа (создав соответсвующий тип, реализующий AModel, добавить id модели в тип ModelID и генерацию модели в ModelFactory).

Если документ не будет обрабатывать какие-либо новые команды, то этого достаточно — можно заменить в GetClientViewServlet возвращаемый вейвлет на ваш собственный и вуаля!. Ах, да, ещё генерация UI на клиенте, но об этом ниже.

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

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

JavaScript

  • ui.js занимается генерацией интерфейса, для каждого блока модели есть собственный метод
  • command.js отправляет и генерирует команды, принимает сообщения об обновлениях, а также содержит обработчики для кнопок
  • ajax.js скрипт, который давно пора заменить на соответствующий метод jquery, но никак не доходят руки :). используется в command.js

Для того, чтобы добавить генерацию вашей модели, достаточно добавить строку, вызывающую ваш метод обработки в методы сreateClient и renderUpdate объекта ClientRenderer в ui.js и собственно написать этот метод. Всё остальное будет (должно) работать самостоятельно.

CSS

  • sametimed-plain.css ещё чуть более ужасный интерфейс чем цветной :)
  • sametimed-colored.css цветной, Windows 3.11-подобный интерфейс :)

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

Эпилог

Вышло много текста про одно небольшое веб-приложение, но зато пост описывает все его возможности. Надеюсь найдётся не один человек, которому эта разработка будет интересна и если найдётся, планирую усовершенствовать его дальше. Пока это небольшая поделка «из интереса», но немного стараний — и её можно превратить в полноценный проект.

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

Участие в разработке приветствуется но поощрить нечем, только лицензией :).

Важное пояснение

Если вы будете проверять приложение в работе одновременно с консольным клиентом wave-protocol, сообщения отосланные из консольного клиента будут приходить на одно позже. Это не баг и не фича, это способ генерации «документа» чата. В консольном случае Google, по крайней мере как посчитал я, изменили порядок записи элементов документа так, чтобы их было удобнее отображать в консоли (начало элемента, конец элемента, затем его содержимое). В моём случае элемент строится в привычном порядке (начало, содержимое, конец), этим и обусловлено это несоответствие. Если исправить порядок генерации в моём проекте на тот же, что и в консоли, они будут будут полностью друг другу соответствовать.

И да, специальной проверки/валидации введённых на клиенте данных пока не производится.

P.S. Сопутствующая история. Где-то с месяц назад в поисках работы я получил тестовое задание от некоторой фирмы X, аж на три недели. При том, что большую часть задания я выполнил и показал текущий на то время вариант в работе и прошёл 80% тестов на собеседовании, меня не приняли. Посему я считаю что имею право поделиться и исходным кодом и руководством к использованию с хабраобществом. Код я развил немного в другом направлении, но тем не менее он может оказаться полезным, даже если у гугла неожиданно окажется есть всё необходимое и в рабочем (доступном разработчикам) варианте.

да-да, слово editor используется часто применительно к проекту, потому что изначально он планировался (и планируется) как одновременный редактор

#google-wave #java #javascript #ajax #dwr