Платформа CUBA. Руководство по разработке приложений Manual

User Manual: Pdf

Open the PDF directly: View PDF PDF.
Page Count: 855 [warning: Documents this large are best viewed by clicking the View PDF Link!]

Платформа CUBA. CUBA.
Руководство по
разработке приложений
Версия 6.7
Предисловие
Данное руководство содержит справочную информацию
по платформе CUBA и охватывает наиболее важные темы
разработки бизнес-приложений на платформе.
Для успешной работы с платформой требуется знание
следующих технологий:
Java Standard Edition
Реляционные базы данных (SQL, DDL)
Для глубокого понимания принципов работы платформы
полезным является знакомство со следующими
технологиями и фреймворками:
Gradle build system
Spring Framework
Java Persistence API
Vaadin web applications framework
HTML / CSS / JavaScript
Java Servlets
Настоящее руководство, а также другая документация по
платформе CUBA, доступны по адресу www.cuba-
platform.ru/manual. Обучающие видео-материалы и
презентации располагаются по адресу www.cuba-
презентации располагаются по адресу www.cuba-
platform.ru/tutorials. Онлайн демо-приложения могут быть
запущены со страницы www.cuba-platform.ru/online-demo.
Если у Вас имеются предложения по улучшению данного
руководства, обратитесь пожалуйста в службу
поддержки по адресу www.cuba-platform.ru/support. При
обнаружении ошибки в документации укажите,
пожалуйста, номер главы и приведите небольшой участок
окружающего текста для облегчения поиска.
1. 1. Введение
В данной главе приводятся сведения о назначении и
возможностях платформы CUBA.
1.1. 1.1. Обзор платформы
Платформа CUBA предназначена для разработки
корпоративных решений, характеризующихся сложной
моделью данных, десятками или сотнями экранов,
большим количеством бизнес-логики, а также
требованиями к контролю прав доступа,
масштабируемости и отказоустойчивости.
Широкий набор готовой функциональности, развитые
средства генерации кода, визуальный дизайнер
интерфейсов, а также поддержка hot deploy радикально
сокращают время и стоимость разработки решения.
Платформа полностью построена на стеке открытых Java
технологий, что позволяет использовать наработки
крупнейшей экосистемы свободного ПО в мире, а также
контролировать исходный код всего стека.
Открытая архитектура позволяет переопределять
поведение большинства механизмов платформы,
поведение большинства механизмов платформы,
обеспечивая высокую гибкость. Разработчики могут
использовать популярные Java IDE и имеют полный доступ
к исходному коду.
Приложения на платформе легко встраиваются в ИТ-
инфраструктуру благодаря поддержке основных баз
данных и серверов приложений, а также возможности
работы в облаке. Платформа позволяет обеспечить
отказоустойчивость и масштабируемость решений и
предоставляет средства интеграции с внешними
системами.
1.2. 1.2. Технические требования
Минимальные требования для ведения разработки на
платформе CUBA:
Оперативная память - 4 ГБ.
Место на жестком диске - 5 ГБ.
Операционная система - Microsoft WindowsMicrosoft Windows, LinuxLinux или
macOSmacOS.
1.3. Release Notes1.3. Release Notes
Список изменений в платформе доступен по адресу
http://les.cuba-platform.com/cuba/release-notes/6.7
2. 2. Установка и настройка
инструментария
Минимально необходимым набором программного
обеспечения является:
Java SE Development Kit (JDK) 8Java SE Development Kit (JDK) 8
Установите Java SE Development Kit (JDK) 8 и проверьте
Установите Java SE Development Kit (JDK) 8 и проверьте
его работоспособность, выполнив в консоли команду
java -version
В ответ должно быть выведено сообщение с номером
версии Java, например 1.8.0_152.
Java 9 пока не поддерживается. Собирать и
запускать CUBA-приложения можно только на Java 8.
Для сборки и запуска проектов вне Studio в переменной
окружения JAVA_HOME необходимо установить путь к
корневому каталогу JDK, например C:\Program
Files\Java\jdk1.8.0_152.
Для WindowsWindows это можно сделать, открыв Компьютер
Свойства системы Дополнительные параметры
системы Дополнительно Переменные среды, и
задав значение переменной в списке Системные
переменные.
Для macOSmacOS JAVA_HOME рекомендуется задать в
~/.bash_profile:
export JAVA_HOME=$(/usr/libexec/java_home -v
1.8)
Если вы установили Java 9 на macOS, CUBA Studio и
приложения перестанут запускаться, независимо от
значения JAVA_HOME. В этом случае обратитесь к
информации в разделе решение проблем ниже.
CCреда разработки на Java Java
IntelliJ IDEAIntelliJ IDEA или EclipseEclipse. Рекомендуется использовать IntelliJIntelliJ
IDEAIDEA (Community или Ultimate).
База данных
В простейшем случае в качестве сервера баз данных
приложений используется встроенный HyperSQLHyperSQL
(http://hsqldb.org), что вполне подходит для исследования
возможностей платформы и прототипирования
приложений. Для создания реальных приложений
рекомендуется установить и использовать в проекте
какую-либо из полноценных СУБД, поддерживаемых
платформой, например PostgreSQLPostgreSQL.
Веб--браузер
Веб-интерфейс приложений, создаваемых на основе
платформы, поддерживает все популярные
современные браузеры, в том числе Google ChromeGoogle Chrome, MozillaMozilla
FirefoxFirefox, SafariSafari, Opera 15+Opera 15+, Internet Explorer 9+Internet Explorer 9+, Microsoft EdgeMicrosoft Edge.
Решение проблем
1. Если по какой-то причине вы установили Java 9 на
macOS, CUBA Studio и приложения перестанут
запускаться. Для того, чтобы восстановить
работоспособность, выполните следующее:
a. Сделайте инсталляцию JDK 9 не используемой по
умолчанию в системе: переименуйте файл
/Library/Java/JavaVirtualMachines/jdk-
9.0.1.jdk/Contents/Info.plist в
Info.plist.disabled (замените jdk-9.0.1.jdk
реальной версией вашего JDK 9).
b. Замените JavaAppletPlugin.plugin, установленный
Java 9 на плагин из Java 8:
Удалите или переименуйте /Library/Internet
Plug-Ins/JavaAppletPlugin.plugin
Установите JDK 8 снова, при этом плагин будет
переустановлен.
c. Убедитесь что вы указали получение JAVA_HOME с
аргументом -v 1.8, как показано выше.
Перелогиньтесь или выполните source
.bash_profile после внесения изменений.
2. Убедитесь, что ваше окружение нe содержит
переменных CATALINA_HOME, CATALINA_BASE и
переменных CATALINA_HOME, CATALINA_BASE и
CLASSPATH. Эти переменные могут вызвать проблемы с
запуском веб-сервера Apache TomcatApache Tomcat, который
используется во время разработки. Перезагрузите
компьютер после удаления переменных.
2.1. 2.1. Установка CUBA Studio CUBA Studio
Окружение
Убедитесь в том, что на компьютере установлен Java SE
Development Kit (JDK) 8, выполнив в консоли команду
java -version
В ответ должно быть выведено сообщение с номером
версии Java, например 1.8.0_152.
Если вы используете OpenJDK на Linux, установите
OpenJFX, например:
sudo apt-get install openjfx
Если для соединения с интернетом используется
прокси-сервер, в JVM, исполняющие Studio и Gradle,
необходимо передавать специальные системные
свойства Java. Они описаны в документе
http://docs.oracle.com/javase/8/docs/technotes/guides/net/pro
xies.html (см. свойства для протоколов HTTP и HTTPS).
Рекомендуется установить нужные свойства в
переменной окружения JAVA_OPTS. Скрипт запуска
Studio передает JAVA_OPTS в java.exe.
Новая установка Studio Studio
1. Загрузите подходящий инсталлятор или ZIP архив со
страницы https://www.cuba-platform.ru/download.
2. Запустите инсталлятор или распакуйте ZIP архив в
локальный каталог, например, c:\work\studio.
3. Запустите установленное приложение или откройте
командную строку, перейдите в подкаталог bin и
запустите studio.bat или studio в зависимости от
запустите studio.bat или studio в зависимости от
вашей операционной системы.
4. В окне CUBA Studio ServerCUBA Studio Server введите следующие
параметры:
Server portServer portпорт, на котором будет запущен сервер
CUBA Studio (по умолчанию 8111).
Remote connectionRemote connection - по умолчанию Studio принимает
соединения только с локального компьютера.
Установите данный флажок, если вам нужна
возможность подключения к этому экземпляру Studio
с удаленного хоста.
Silent startupSilent startup - если выбрано, сервер Studio запускается
в трее и открывает UI в браузере автоматически.
Данная опция доступна только в Windows.
5. Запустите сервер Studio, нажав кнопку StartStart.
Когда запустится веб-сервер, в поле URLURL отобразится
адрес, по которому доступен интерфейс Studio. Нажав
, можно открыть веб-браузер, нажав CopyCopy
скопировать адрес в буфер обмена.
6. Запустите веб-браузер и перейдите по указанному
адресу. В веб-интерфейсе Studio перейдите на вкладку
SettingsSettings и введите следующие параметры:
Java homeJava homeJDK, который будет использоваться для
сборки и запуска проектов. Если вы установили
переменную окружения JAVA_HOME как описано в
начале данной главы, ее значение будет
подставлено в данное поле. В противном случае
Studio попытается самостоятельно найти каталог
Studio попытается самостоятельно найти каталог
установки Java.
Gradle homeGradle home - путь к Gradle. Оставьте поле пустым, в
этом случае при первом запуске будет
автоматически загружен нужный дистрибутив
Gradle.
Если по какой-либо причине Вы хотите использовать
уже установленный на компьютере Gradle, введите в
поле путь к соответствующему каталогу. Текущая
версия системы сборки проектов протестирована на
Gradle 3.4Gradle 3.4.
IDE portIDE portпорт, на котором принимает подключения
плагин IDE (по умолчанию 48561).
OineOine - включить возможность работы без интернет-
соединения при условии, что все необходимые
библиотеки были предварительно загружены из
репозитория.
Check for updatesCheck for updates - проверять наличие новых версий
при старте.
Send anonymous statistics and crash reportsSend anonymous statistics and crash reports - разрешить
Studio отправлять статистику ошибок
разработчикам.
Help languageHelp language - язык встроенной справки.
Logging levelLogging level - уровень логирования: TRACE, DEBUG, INFO,
WARN, ERROR или FATAL. По умолчанию INFO.
7. Нажмите Apply and proceed to projectsApply and proceed to projects.
8. Нажмите Create newCreate new для создания нового проекта, или
ImportImport для добавления имеющегося проекта в список
RecentRecent.
9. Сразу после открытия проекта Studio загружает
исходный код компонентов платформы, на которых
основан проект, и сохраняет его в локальном
каталоге. Перед сборкой приложения рекомендуется
дождаться окончания загрузки и убедиться в том, что
индикатор фоновых задач в левом нижнем углу
экрана Studio погас.
Обновление CUBA Studio CUBA Studio
Если вы обноляете Studio на новую bug-x версию
(например с 6.5.0 на 6.5.1), устанавливайте ее в
существующий каталог, например на Windows это будет
C:\Program Files (x86)\CUBA Studio 6.5. При
установке новой minor или major версии, используйте
отдельный каталог, например CUBA Studio 6.6.
Если Studio установлена из инсталлятора Windows EXE или
из ZIP-архива, она поддерживает авто-обновление на
новые bug-x релизы. Файлы обновлений сохраняются в
каталоге ~/.haulmont/studio/update. В случае каких-
либо проблем с новой версией вы можете просто
удалить файлы обновления и Studio вернется к версии,
установленной ранее вручную.
Автообновление не работает для minor и major релизов, и
если Studio была установлена из macOS DMG. В этом
случае загрузите и запустите новый инсталлятор
вручную.
2.2. 2.2. Интеграция CUBA Studio CUBA Studio с IDE IDE
2.2. 2.2. Интеграция CUBA Studio CUBA Studio с IDE IDE
Для интеграции с IntelliJ IDEAIntelliJ IDEA или EclipseEclipse выполните
следующие шаги:
1. Откройте или создайте новый проект в Studio.
2. Перейдите в секцию Project propertiesProject properties и нажмите кнопку
EditEdit. Выберите нужную Java IDEJava IDE флажками IntelliJ IDEAIntelliJ IDEA или
EclipseEclipse.
3. В главном меню Studio выберите пункт меню Build Build
Create or update <IDE> project lesCreate or update <IDE> project les. В каталоге проекта будут
созданы соответствующие файлы.
4. Для интеграции с IntelliJ IDEA:
a. Запустите IntelliJ IDEA 13+ и установите плагин CUBACUBA
Framework IntegrationFramework Integration, доступный в репозитории
плагинов: File > Settings > Plugins > Browse RepositoriesFile > Settings > Plugins > Browse Repositories.
5. Для интеграции с Eclipse:
a. Запустите Eclipse 4.3+, откройте Help > Install NewHelp > Install New
SoftwareSoftware, добавьте репозиторий http://files.cuba-
platform.com/eclipse-update-site и установите
плагин CUBA PluginCUBA Plugin.
b. В Eclipse в меню Window > PreferencesWindow > Preferences в секции CUBACUBA
установите флажок Studio Integration EnabledStudio Integration Enabled и нажмите
на кнопку OKOK.
Обратите внимание, что в панели статуса Studio
загорелась надпись IDE: on port 48561IDE: on port 48561. Теперь при нажатии
кнопок IDEIDE в Studio соответствующие файлы исходных
кодов будут открываться редактором IDE.
3. 3. Быстрый старт
В данном разделе рассматривается создание приложения
при помощи CUBA Studio. Эта же информация изложена в
видеороликах, доступных по адресу www.cuba-
видеороликах, доступных по адресу www.cuba-
platform.ru/quickstart.
На Вашей рабочей машине уже должно быть установлено
и настроено необходимое программное обеспечение, см.
Установка и настройка инструментария.
Основные задачи, стоящие при разработке нашего
приложения:
1. Разработка модели данных, которая заключается в
создании сущностей предметной области и
соответствующих таблиц базы данных.
2. Разработка экранов пользовательского интерфейса,
позволяющих создавать, просматривать, обновлять и
удалять сущности модели данных.
3.1. 3.1. Описание задачи
Приложение предназначено для ведения сведений о
покупателях и их заказах.
Покупатель имеет следующие характеристики:
Имя
Электронная почта
Характеристики заказа:
Принадлежность покупателю
Дата
Сумма
Пользовательский интерфейс приложения должен
содержать:
содержать:
Окно списка покупателей;
Окно редактирования сведений о покупателе,
содержащее также список заказов данного покупателя;
Окно общего списка заказов;
Окно редактирования заказа.
3.2. 3.2. Создание проекта
1. Запустите CUBA StudioCUBA Studio и откройте ее веб-интерфейс (см.
Установка CUBA Studio).
2. Нажмите на кнопку Create newCreate new.
3. В окне New projectNew project в поле Project nameProject name введите имя
проекта - sales. Имя должно содержать только
латинские буквы, цифры и знак подчеркивания.
Тщательно продумайте имя проекта на данном этапе,
так как в дальнейшем изменить его будет достаточно
сложно.
4. В полях ниже автоматически сгенерируются:
Project pathProject pathпуть к каталогу нового проекта. Каталог
можно выбрать вручную, нажав на кнопку рядом с
полем. Отобразится окно Folder selectFolder select со списком папок
на жестком диске. Вы можете выбрать одну из них или
создать новый каталог, нажав на кнопку ++.
Project namespaceProject namespace - пространство имен, которое будет
использоваться как префикс имен сущностей и
таблиц базы данных. Пространство имен может
состоять только из латинских букв, и должно быть как
можно короче. Например, если имя проекта - sales_2,
то пространство имен может быть sales или sal.
Root packageRoot packageкорневой пакет Java-классов. Может
быть скорректирован позже, однако сгенерированные
на этапе создания классы перемещены не будут.
RepositoryRepository URL и параметры аутентификации
RepositoryRepository URL и параметры аутентификации
репозитория бинарных артефактов.
Platform versionPlatform version - используемая в проекте версия
платформы. Артефакты платформы будут
автоматически загружены из репозитория при сборке
проекта.
5. Нажмите на кнопку OKOK. В указанном каталоге sales
будет создан пустой проект, и откроется главное окно
Studio.
6. Сборка проекта. Выберите пункт главного меню Studio
BuildBuild Assemble projectAssemble project. На этом этапе будут загружены
все необходимые библиотеки, и в подкаталогах build
модулей будут собраны артефакты проекта.
7. Создание базы данных на локальном сервере HyperSQLHyperSQL.
Выберите пункт меню RunRun Create databaseCreate database. Имя БД по
умолчанию совпадает с пространством имен проекта.
8. Выберите пункт меню RunRun DeployDeploy. В подкаталоге
deploy проекта будет установлен сервер TomcatTomcat с
собранным приложением.
9. Выберите пункт меню RunRun Start application serverStart application server. Через
несколько секунд в панели статуса ссылка рядом с
надписью Web applicationWeb application станет доступной, и по ней
можно осуществить переход к приложению
непосредственно из Studio.
непосредственно из Studio.
Логин и пароль пользователяadmin / admin.
Запущенное приложение содержит два главных пункта
меню (AdministrationAdministration и HelpHelp), функциональность
подсистемы безопасности и администрирования
системы.
3.3. 3.3. Создание сущностей
Создадим класс сущности Customer (Покупатель).
Перейдите на вкладку Data ModelData Model на панели навигатора
и нажмите на кнопку NewNew > EntityEntity. Появится диалоговое
окно New entityNew entity.
В поле Class nameClass name введите название класса сущности
Customer.
Нажмите OKOK. В рабочей области откроется страница
дизайнера сущности.
В полях NameName и TableTable автоматически сгенерируются имя
сущности и имя таблицы в базе данных.
В поле Parent classParent class оставьте установленное значение
StandardEntity.
Поле Inheritance strategyInheritance strategy оставьте пустым.
Далее создадим атрибуты сущности. Для этого нажмите
на кнопку NewNew, находящуюся под таблицей AttributesAttributes.
В отобразившемся окне Create attributeCreate attribute в поле NameName
введите название атрибута сущностиname, в списке
Attribute typeAttribute type выберите значение DATATYPE, в поле TypeType
укажите тип атрибута String и далее укажите длину
текстового атрибута в поле LengthLength, равной 100 символам.
Установите флажок MandatoryMandatory. В поле ColumnColumn
автоматически сгенерируется имя колонки таблицы в
базе данных.
Для добавления атрибута нажмите на кнопку AddAdd.
Атрибут email создается таким же образом, за
исключением того, что в поле LengthLength следует указать
значение 50.
После создания атрибутов перейдите на вкладку InstanceInstance
NameName дизайнера сущности для задания Name pattern. В
списке Available attributesAvailable attributes выделите атрибут name и
перенесите его в список Name pattern attributesName pattern attributes, нажав на
кнопку с изображением стрелки вправо.
На этом создание сущности Customer завершено. Нажмите
на кнопку OKOK в верхней панели для сохранения
изменений.
Создадим сущность Order (Заказ). В панели DATA MODELDATA MODEL
нажмите на кнопку NewNew > EntityEntity. В поле Class nameClass name введите
название класса сущностиOrder. Сущность должна
иметь следующие атрибуты:
NameNamecustomer, Attribute typeAttribute type ASSOCIATION, TypeType
Customer, CardinalityCardinalityMANY_TO_ONE.
NameNamedate, Attribute typeAttribute type DATATYPE, TypeTypeDate. Для
атрибута date установите флажок MandatoryMandatory.
NameNameamount, Attribute typeAttribute type DATATYPE, TypeType BigDecimal.
3.4. 3.4. Создание таблиц базы данных
Для создания таблиц базы данных достаточно на вкладке
Data ModelData Model панели навигатора нажать на кнопку Generate DBGenerate DB
scriptsscripts. После этого откроется страница Database ScriptsDatabase Scripts. На
вкладке будут сгенерированы скрипты обновления базы
данных от ее текущего состояния (UPDATE SCRIPTSUPDATE SCRIPTS) и
скрипты создания базы данных с нуля (INIT TABLESINIT TABLES, INITINIT
TABLESTABLES, INIT DATAINIT DATA). Также на вкладке будут доступны уже
выполненные скрипты обновления базы данных, если они
есть.
Чтобы сохранить сгенерированные скрипты, нажмите на
кнопку Save and closeSave and close. Для запуска скриптов обновления
остановите запущенное приложение с помощью команды
RunRun Stop application serverStop application server, затем выполните RunRun UpdateUpdate
databasedatabase.
3.5. 3.5. Создание экранов пользовательского
интерфейса
Создадим экраны приложения, позволяющие управлять
информацией о покупателях и заказах.
3.5.1. 3.5.1. Экраны управления Покупателями
Для создания стандартных экранов просмотра и
редактирования покупателей необходимо выделить
сущность Customer на вкладке Data ModelData Model панели
навигатора и нажать на кнопку NewNew > Generic UI screenGeneric UI screen внизу
панели. После этого на экране отобразится страница
создания стандартных экранов сущности.
В списке доступных шаблонов выберите Entity browser andEntity browser and
editor screenseditor screens.
Все поля этого окна заполнены значениями по умолчанию,
пока не будем их менять. Нажмите на кнопку CreateCreate и
затем CloseClose.
Во вкладке Generic UIGeneric UI панели навигатора в модуле WebWeb
ModuleModule появятся элементы customer-browse.xml и
customer-edit.xml.
3.5.2. 3.5.2. Экраны управления Заказами
Сущность Order (Заказ) имеет следующую особенность:
так как среди прочих атрибутов существует ссылочный
атрибут Order.customer, требуется определить
представление сущности Order, включающее этот
атрибут (стандартное представление _local не включает
ссылочных атрибутов).
Для этого перейдите на вкладку Data ModelData Model на панели
навигатора, выделите сущность Order и выберите NewNew >
ViewView. Отобразится страница дизайнера представлений. В
качестве имени введите order-with-customer, в списке
атрибутов нажмите на атрибут customer и на
отобразившейся справа панели выберите представление
_minimal для сущности Customer.
Нажмите на кнопку OKOK в верхней панели.
Далее выделите сущность Order и выберите NewNew > GenericGeneric
UI screenUI screen.
В отобразившемся окне выберите представление order-
with-customer в полях ViewView для браузера и редактора и
нажмите на кнопку CreateCreate и, затем, CloseClose.
Во вкладке Generic UIGeneric UI панели навигатора в модуле WebWeb
ModuleModule появятся элементы order-edit.xml и order-
browse.xml.
3.5.3. 3.5.3. Меню приложения
3.5.3. 3.5.3. Меню приложения
При создании экраны были добавлены в пункт меню
applicationapplication, имеющийся по умолчанию. Переименуем его.
Для этого перейдите на вкладку Generic UIGeneric UI на панели
навигатора и нажмите на ссылку Open web menuOpen web menu.
Отобразится страница дизайнера меню. Выделите пункт
меню application для просмотра его свойств.
В поле IdId введите новое значение идентификатора меню
shop. После редактирования меню нажмите на кнопку OKOK в
верхней панели.
3.5.4. 3.5.4. Экран редактирования Покупателя со
списком Заказов
Займемся задачей отображения списка заказов в окне
редактирования покупателя.
Перейдите на вкладку Generic UIGeneric UI на панели навигатора.
Выделите экран customer-edit.xml и нажмите на
кнопку EditEdit.
На странице дизайнера экрана перейдите на вкладку
DatasourcesDatasources и нажмите на кнопку NewNew.
DatasourcesDatasources и нажмите на кнопку NewNew.
Выделите только что созданный источник данных в
списке. В правой части страницы отобразятся его
характеристики.
В поле TypeType укажите collectionDatasource.
В списке EntityEntity выберите сущность Order.
В поле IdId будет автоматически заполнено значение
идентификатора источника данныхordersDs.
В списке ViewView выберите представление _local.
В поле QueryQuery введите следующий запрос:
Здесь запрос содержит условие отбора Заказов с
параметром ds$customerDs. Значением параметра с
именем вида ds${datasource_name} будет
идентификатор сущности, установленной в данный
момент в источнике данных datasource_name, в данном
случаеидентификатор редактируемого Покупателя.
select e from sales$Order e where e.customer.id =
:ds$customerDs order by e.date
Нажмите на кнопку ApplyApply для сохранения изменений.
Далее перейдите на вкладку LayoutLayout в дизайнере экрана
и в палитре компонентов найдите компонент Label.
Перетащите этот компонент на панель иерархии
компонентов экрана, между fieldGroup и
windowActions. Перейдите на вкладку PropertiesProperties на
панели свойств. В поле valuevalue введите значение
компонента: Orders.
Если разрабатываемое приложение предполагает
локализацию на несколько языков, используйте
кнопку рядом с полем valuevalue, чтобы создать новое
сообщение msg://orders и задать его значение на
требуемых языках.
Перетащите компонент Table из палитры компонентов
на панель иерархии компонентов между label и
windowActions. Выделите компонент в иерархии и
перейдите на вкладку PropertiesProperties. Задайте размеры
таблицы: в поле widthwidth укажите 100%, в поле heightheight
установите значение 200px. Из списка доступных
источников данных выберите orderDs, после этого в
поле idid с помощью кнопки сгенерируйте
идентификатор таблицы: ordersTable.
Для сохранения изменений в экране редактирования
Покупателя нажмите на кнопку OKOK в верхней панели.
3.6. 3.6. Запуск приложения
Посмотрим, как созданные нами экраны выглядят в
работающем приложении. Для этого выполните RunRun > StartStart
application serverapplication server.
Зайдите в систему, использовав стандартные имя и
пароль в окне логина. Откройте пункт меню ShopShop >
CustomersCustomers:
Рисунок 1. 1. Экран списка Customers Customers
Нажмите на кнопку CreateCreate и создайте нового покупателя:
Рисунок 2. 2. Экран редактирования Customer Customer
Откройте пункт меню ShopShop > OrdersOrders:
Рисунок 3. 3. Экран списка Orders Orders
Нажмите на кнопку CreateCreate и создайте новый заказ, выбрав
в поле CustomerCustomer только что созданного покупателя:
Рисунок 4. 4. Экран редактирования Order Order
В таблице на экране редактирования покупателя теперь
отображается только что созданный заказ:
Рисунок 5. 5. Экран редактирования Customer Customer
4. 4. Сборник рецептов
Данный раздел представляет собой коллекцию
практических рецептов по решению типичных задач,
встающих перед разработчиками при использовании
платформы CUBA. В каждом подразделе информация
организована от простого к сложному, поэтому вы в любой
LIVE DEMOLIVE DEMO
момент можете перейти к другой секции или заняться
кодированием.
Большинство разделов снабжены демо-приложениями.
Вы можете увидеть их в работе онлайн, посмотреть
исходный код на GitHub, или загрузить и запустить
локально. Ссылки на проекты приложений доступны
также на вкладке SamplesSamples в Studio.
Данный раздел находится в работе и будет
постоянно дополняться.
4.1. 4.1. Организация бизнес--логики
Один из первых вопросов, возникающих в начале
процесса разработки на платформе, это "где мне
расположить мою бизнес-логику"? Использование Studio
сильно облегчает создание модели данных и CRUD
экранов, любой реальный проект требует создания логики
помимо загрузки и сохранения данных. Данный раздел
содержит информацию о том, как эффективно
организовать бизнес-логику в зависимости от задачи.
Большинство примеров в данном разделе работают со
следующей моделью:
В этих примерах мы будем рассчитывать скидки для
заказчиков в зависимости от общей суммы их покупок.
4.1.1. 4.1.1. Бизнес--логика в контроллерах
Например, необходимо запускать расчет
LIVE DEMOLIVE DEMO
скидки, когда пользователь нажимает кнопку на экране-
браузере заказчиков. В этом случае, наиболее простое
решение - это разместить логику расчета прямо в
контроллере экрана.
См. кнопку Calculate discountCalculate discount в демо-приложении и
реализацию контроллера: CustomerBrowse.java. Пожалуйста,
имейте в виду, что данная имплементацию расчета не
является оптимальной (см. варианты работы с данными в
разделе Загрузка и сохранение данных).
Данный подход приемлем, если логика вызывается из
одного места и она не слишком сложна, чтобы уместиться
в нескольких коротких методах.
4.1.2. 4.1.2. Использование бинов клиентского уровня
Теперь давайте немного усложним задачу из
предыдущего раздела. Допустим, требуется вызывать
расчет из двух экранов: и из браузера, и из редактора.
Чтобы не дублировать код, нужно извлечь код из
контроллера и поместить в некоторое общедоступное
место. Это может быть управляемый бин клиентского
уровня.
Управляемый бин - это класс с аннотацией @Component. Он
может быть инжектирован в другие бины и контроллеры
экранов, или получен с помощью статического метода
AppBeans.get(). Если класс бина реализует некоторый
интерфейс, то к нему можно обращаться через этот
интерфейс.
Имейте в виду, что для того чтобы бин был доступен для
контроллеров экранов, он должен располагаться в одном
из следующих модулей: globalglobal, guigui или webweb вашего проекта.
В случае globalglobal, бин будет также доступен на среднем
слое.
См. кнопку Calculate discountCalculate discount на экранах браузера и
LIVE DEMOLIVE DEMO
См. кнопку Calculate discountCalculate discount на экранах браузера и
редактора в демо-приложении, и реализацию:
CustomerBrowse.java - контроллер браузера.
CustomerEdit.java - контроллер редактора.
DiscountCalculator.java - бин расчета скидок. Он использует
DataManager для загрузки списка заказов данного
заказчика из базы данных.
4.1.3. 4.1.3. Использование сервисов среднего слоя
В предыдущем разделе мы рассмотрели
инкапсуляцию бизнес-логики в бине клиентского уровня.
Теперь мы пойдем дальше и поместим нашу логику в
наиболее подходящее место: на средний слой. Сделав это,
мы достигнем следующих целей:
Наши бизнес-методы будут доступны клиентам всех
типов, включая Polymer UI.
Мы сможем использовать API, доступный только на
middleware: EntityManager, transactions, и т.п.
Чтобы вызвать бизнес-метод среднего слоя, необходимо
создать сервис. Studio может помочь в создании заготовки
сервиса:
Переключитесь на вкладку MiddlewareMiddleware и нажмите New >New >
ServiceService.
Измените имя интерфейса сервиса на DiscountService.
Имена класса и самого сервиса будут изменены
соответственно. Нажмите OKOK или ApplyApply.
соответственно. Нажмите OKOK или ApplyApply.
Нажмите IDEIDE и откройте интерфейс сервиса в IDE.
Создайте новый метод и реализуйте его в классе
сервиса.
См. пример реализации в демо-приложении:
CustomerBrowse.java and CustomerEdit.java - контроллер
экрана, который вызывает сервис.
DiscountService.java - интерфейс сервиса.
DiscountServiceBean.java - класс реализации сервиса.
DiscountCalculator.java - бин среднего слоя,
рассчитывающий скидки. Разумеется, сервис мог бы
содержать бизнес-логику сам, но мы будем
использовать этот делегат для того чтобы разделять
логику с entity listener и JMX-бином (см. следующие
разделы).
Обратите внимание, что данный бин отличается от
рассмотренного в предыдущем разделе: он расположен
в модуле corecore и использует EntityManager для загрузки
суммы заказов из базы данных.
Теперь давайте сделаем наш бизнес-метод доступным
для внешних клиентов через REST API:
Откройте сервис на редактирование в Studio и
переключитесь на вкладку REST MethodsREST Methods.
Установите для метода флажок REST invocation allowedREST invocation allowed.
LIVE DEMOLIVE DEMO
Studio создаст файл rest-services.xml и зарегистрирует
в нем метод. После перезапуска сервера вы сможете
вызвать метод с помощью HTTP-запросов. Например,
следующий GET-запрос должен работать с нашим онлайн
демо-сервером:
https://demo1.cuba-platform.com/business-
logic/rest/v2/services/sample_DiscountService/calculateDiscount?
customerId=1797f54d-5bec-87a6-4330-d958955743a2
Имейте в виду, что демо-приложение имеет анонимный
доступ. В большинстве реальных сценариев
использования необходимо будет аутентифицироваться,
прежде чем выполнять запросы.
4.1.4. 4.1.4. Использование Entity Listeners Entity Listeners
Entity listeners позволяют выполнять бизнес-
логику каждый раз, когда сущность создается,
изменяется или удаляется в базе данных. Например, мы
можем пересчитывать скидку для заказчика каждый раз,
когда некоторый заказ для него изменяется.
Заготовку для entity listener можно легко создать в Studio:
Перейдите на вкладку MiddlewareMiddleware и нажмите New > EntityNew > Entity
listenerlistener.
Измените имя класса на OrderEntityListener и
включите флажки для интерфейсов
BeforeInsertEntityListener,
BeforeUpdateEntityListener и
BeforeDeleteEntityListener.
Выберите сущность Order в поле Entity typeEntity type.
Нажмите OKOK или ApplyApply и откройте класс listener в IDE.
См. пример в демо-приложении:
LIVE DEMOLIVE DEMO
OrderEntityListener.java - класс entity listener.
DiscountCalculator.java - бин среднего слоя,
рассчитывающий скидки. Entity listener мог бы содержать
бизнес-логику сам, но мы использовуем этот делегат
для того, чтобы разделять логику с сервисом и JMX
бином.
Если вы откроете экран Logic in Entity ListenersLogic in Entity Listeners демо-
приложения, вы увидите две таблицы: заказы и заказчики.
Создайте, измените или удалите заказ, обновите таблицу
заказчиков, и вы увидите, что скидка для
соответствующего заказчика изменилась.
4.1.5. 4.1.5. Использование JMX- JMX-бинов
С помощью JMX-бинов можно предоставить
доступ к некоторой административной
функциональности вашего приложения без создания
пользовательского интерфейса для нее. Данная
функциональность будет также доступна через
встроенную JMX-консоль и через внешние инструменты
JMX, например jconsole.
В нашем примере со скидками пользователь, имеющий
доступ к JMX-консоли, сможет пересчитывать скидки для
всех заказчиков или для заказчика с указанным id.
Studio на данный момент не умеет создавать заготовки
JMX-бинов, поэтому все классы и конфигурационные
элементы придется создавать вручную в IDE.
См. пример реализации в демо-приложении:
LIVE DEMOLIVE DEMO
DiscountsMBean.java - интерфейс JMX-бина.
Discounts.java - реализация JMX-бина.
DiscountCalculator.java - бин среднего слоя, вызываемый
JMX-бином. JMX-бин мог бы содержать бизнес-логику
сам, но мы использовуем этот делегат для того, чтобы
разделять логику с entity listener и JMX бином.
spring.xml - в данном файле JMX-бин регистрируется.
4.1.6. 4.1.6. Запуск кода на старте приложения
Иногда бывает необходимо выполнить некоторый код
сразу после старта приложения в момент, когда все
механизмы гарантированно работоспособны. Для этого
можно воспользоваться слушателем AppContext.Listener.
В данном разделе мы рассмотрим, как
динамически зарегистрировать для сущности entity listener
на старте приложения. Возьмем следующую задачу: в
проекте имеется сущность Employee (сотрудник
компании), которая связана один-к-одному с
платформенной сущностью User (пользователь системы):
Если атрибут name сущности User изменяется, например
через стандартный экран управления пользователями,
необходимо, чтобы изменялся также и атрибут name
связанной сущности Employee. Это обычная задача для
связанной сущности Employee. Это обычная задача для
"денормализованных" данных, и решается она, как
правило, с использованием entity listeners. В данном случае
ситуация осложняется тем, что необходимо отслеживать
изменения не проектной, а платформенной сущности
User, и добавить entity listener с помощью аннотации
@Listeners невозможно. Однако, можно добавить listener
динамически через бин EntityListenerManager, и сделать
это лучше всего на старте приложения.
AppLifecycle.java - бин среднего слоя, реализующий
интерфейс AppContext.Listener, содержащий методы
applicationStarted() и applicationStopped().
UserEntityListener.java - entity listener для сущности User.
В результате сразу после старта блока Middleware будет
вызван метод applicationStarted() данного бина. В этом
методе в качестве entity listener сущности User
регистрируется бин sample_UserEntityListener.
Метод onBeforeUpdate() класса UserEntityListener
будет вызываться перед каждым сохранением изменений
экземпляров User в базу данных. В методе проверяется,
есть ли атрибут name среди измененных, и если да,
загружается связанный экземпляр Employee, и в нем
устанавливается это же значение name.
4.2. 4.2. Моделирование предметной области
В данном разделе приведены рецепты, связанные с
дизайном модели данных и работой с атрибутами
сущностей.
4.2.1. 4.2.1. Присвоение начальных значений
Присвоение начальных значений атрибутам новых
экземпляров сущностей можно производить несколькими
способами.
LIVE DEMOLIVE DEMO
LIVE DEMOLIVE DEMO
способами.
4.2.1.1. 4.2.1.1. Инициализация полей
сущности
Атрибуты простых типов (Boolean, Integer и
т.д.) и перечисления можно инициализировать прямо в
объявлении соответствующего поля класса сущности. См.
например поля active и grade в Customer.java.
Кроме того, в классе сущности можно создать
специальный метод инициализации и добавить ему
аннотацию @PostConstruct. В этом случае в процессе
инициализации можно использовать вызов любых
глобальных интерфейсов инфраструктуры и бинов. См.
метод init() в Customer.java.
4.2.1.2. 4.2.1.2. Инициализация с помощью
CreateActionCreateAction
Если начальное значение атрибута зависит от
данных вызывающего экрана, то можно воспользоваться
методами setInitialValues() или
setInitialValuesSupplier() класса CreateAction.
См. пример работы с сущностями Customer и
CustomerAddress в демо-приложении:
customer-address-browse.xml - дескриптор экрана с двумя
связанными таблицами, одна для заказчиков, другая
для их адресов.
LIVE DEMOLIVE DEMO
для их адресов.
CustomerAddressBrowse.java - контроллер экрана. В его
методе init() вызывается
setInitialValuesSupplier(), который используется
для предоставления начального значения атрибуту
customer создаваемого адреса. Значением будет
заказчик, выбранный в данный момент в первой
таблице.
4.2.1.3. 4.2.1.3. Использование метода
initNewIteminitNewItem
Начальные значения можно также задать в
контроллере экрана создаваемой сущности в методе
initNewItem().
Рассмотрим сущности:
В демо-приложении атрибут info сущности
CustomerDetails редактируется в том же экране, что и
сам Customer. Данный подход требует создания
экземпляра CustomerDetails вместе с владеющим им
Customer.
customer-edit.xml - дескриптор экрана редактирования
заказчика. Он содержит вложенный источник данных
для связанного экземпляра CustomerDetails.
Компонент` infoField` типа TextArea подключен к этому
источнику.
CustomerEdit.java - контроллер экрана. В нем определен
метод initNewItem(), который создает новый
LIVE DEMOLIVE DEMO
экземпляр CustomerDetails и устанавливает его в
новый Customer. Созданный экземпляр будет доступен
через вложенный источник данных и сохранен в базе
данных когда экран будет закоммичен.
4.2.2. 4.2.2. Редактирование композитных сущностей
Платформа CUBA поддерживает два типа связи между
сущностями: ассоциацию и композицию. В интерфейсе
CUBA Studio они названы соответственно ASSOCIATION и
COMPOSITION. Ассоциация - это связь между объектами,
которые могут существовать отдельно друг от друга.
Композиция же используется для связи типа "master-detail"
когда экземпляры detail существуют только в составе
master. Примером композиции может служить связь
аэропорта и терминалов: терминал, не относящийся ни к
какому аэропорту, не имеет смысла.
Как правило, редактирование сущностей, входящих в
состав композиции, удобно осуществлять совместно. То
есть, например, пользователь открывает экран
редактирования аэропорта, видит в нем список
терминалов, может создавать и редактировать их, но все
изменения, как аэропорта, так и терминалов, сохраняются
в базу данных вместе в одной транзакции, и только тогда,
когда пользователь подтвердит сохранение главной
сущности - аэропорта.
4.2.2.1. One-to-Many: 4.2.2.1. One-to-Many: один уровень
вложенности
Рассмотрим реализацию композиции на
примере сущностей Airport и Terminal:
Terminal.java - сущность Terminal содержит
обязательную ссылку на Airport.
В редакторе сущностей Studio установите следующие
свойства для аттрибута airport: Attribute typeAttribute type -
ASSOCIATION, CardinalityCardinality - MANY_TO_ONE, MandatoryMandatory - on.
Airport.java - сущность Airport содержит one-to-many
коллекцию терминалов. Соответствующее поле
аннотировано @Composition для реализации композиции,
и @OnDelete для каскадного мягкого удаления.
В редакторе сущностей Studio установите следующие
свойства для аттрибута terminals: Attribute typeAttribute type -
COMPOSITION, CardinalityCardinality - ONE_TO_MANY, On deleteOn delete - CASCADE.
views.xml - представление airport-terminals экрана
редактирования аэропорта содержит атрибут-
коллекцию terminals. Для этого атрибута используется
представление _local, так как атрибут airport
сущности Terminal устанавливается только во время
создания экземпляра Terminal и никогда не изменяется
после этого, поэтому загружать его не требуется.
airport-edit.xml - XML-дескриптор экрана редактирования
аэропорта определяет источник данных для
экземпляра аэропорта, и вложенный источник для его
терминалов. Кроме того, экран содержит таблицу,
отображающую терминалы.
terminal-edit.xml - стандартный редактор для сущности
Terminal.
В результате редактирование экземпляра аэропорта
работает следующим образом:
В экране редактирования аэропорта отображается
таблица терминалов.
LIVE DEMOLIVE DEMO
таблица терминалов.
Пользователь может выбрать терминал и открыть экран
его редактирования. При нажатии OKOK в экране
редактирования терминала измененный экземпляр
терминала сохраняется не в базу данных, а в источник
данных terminalsDs экрана редактирования аэропорта.
Пользователь может создавать новые или удалять
терминалы - все изменения сохраняются в источнике
данных terminalsDs.
Пользователь нажимает OKOK в экране редактирования
аэропорта, и измененный Airport вместе со всеми
измененными экземплярами Terminal отправляется на
middleware в метод DataManager.commit() и сохраняется в
базе данных в рамках одной транзакции.
4.2.2.2. One-to-Many: 4.2.2.2. One-to-Many: два уровня
вложенности
Композиция может быть более глубокой и
состоять из двух уровней вложенности. Усложним
приведенный выше пример, добавив сущность
MeetingPoint, описывающую место встречи у терминала
аэропорта:
Теперь сущность Terminal содержит атрибут
meetingPoints - коллекцию экземпляров MeetingPoint.
Для того, чтобы все три сущности представляли собой
единую композицию и редактировались совместно, нужно
в дополнение к описанному в предыдущем разделе
в дополнение к описанному в предыдущем разделе
выполнить следующее:
Terminal.java - атрибут meetingPoints класса Terminal
содержит аннотации @Composition и @OnDelete
аналогично атрибуту terminals класса Airport.
views.xml - представление terminal-meetingPoints-view
сущности Terminal содержит атрибут-коллекцию
meetingPoints. Данное представление используется в
представлении airport-terminals-meetingPoints-
view сущности Airport.
airport-edit.xml - дескриптор экрана редактирования
Airport содержит источники данных для экземпляра
Airport и вложенных сущностей на всю глубину
композиции (airportDs > terminalsDs >
meetingPointsDs).
Источник данных meetingPointsDs здесь не связан ни с
какими визуальными компонентами, однако он
необходим для корректной работы совместного
редактирования композиции.
terminal-edit.xml - XML-дескриптор экрана редактирования
терминала в свою очередь определяет вложенный
источник данных и соответствующую таблицу для
коллекции meetingPoints.
В результате измененные эземпляры MeetingPoint, так
же как и экземпляры Terminal, будут сохраняться в базу
данных только вместе с экземпляром Airport в одной
транзакции.
4.2.2.3. One-to-Many: 4.2.2.3. One-to-Many: три уровня
вложенности
Представьте, что вам необходима еще одна сущность,
содержащая некоторые детали места встречи
LIVE DEMOLIVE DEMO
содержащая некоторые детали места встречи
(MeetingPoint). Назовем эту сущность NoteNote. Таким образом,
вся структура будет выглядеть следующим образом:
Airport > Terminal > Meeting Point > NoteAirport > Terminal > Meeting Point > Note.
CUBA может обеспечить работу с композициями с
максимум 2-мя уровнями вложенности. Теперь у
структура с 3-мя уровня, поэтому необходимо ограничить
глубину композиции либо сверху, либо снизу. В данном
разделе мы рассмотрим два различных (с точки зрения
user experience) подхода для исключения из композиции
аэропорта. Оба подхода решают одну и ту же проблему:
так как теперь терминалы сохраняются в базу данных
независимо от аэропорта, невозможно сохранить
терминал для только-что созданного аэропорта пока он
не сохранен в БД.
При первом подходе браузер и редактор
аэропорта выглядят так-же как и раньше, но редактор
имеет дополнительную кнопку SaveSave для сохранения
нового аэропорта не закрывая экрана. Пользователь не
может создавать терминалы, пока новый аэропорт не
сохранен.
airport-edit.xml содержит standalone источник данных для
терминалов вместо вложенного. Этот источник связан с
источником аэропорта, и поэтому загружает терминалы
только для редактируемого аэропорта. Кроме того,
экран содержит фрейм extendedEditWindowActions,
позволяющий пользователю сохранить аэропорт не
закрывая экран.
AirportEdit.java - здесь, в методе postInit() редактора
аэропорта, мы управляем состоянием enabled действия
LIVE DEMOLIVE DEMO
LIVE DEMOLIVE DEMO
аэропорта, мы управляем состоянием enabled действия
создания терминала и передаем текущий экземпляр
аэропорта для инициализации ссылки в создаваемом
терминале.
При втором подходе мы разбиваем браузер
аэропортов на две панели: одна для списка аэропортов,
вторая для зависимого списка терминалов. Т.е. список
терминалов теперь находится вне редактора
аэропорта. Действие создания терминалов недоступно,
если не выбран ни один аэропорт.
airport-browse.xml содержит standalone источник данных
для списка терминалов. Он связан с источником
аэропортов, и загружает терминалы только для
выбранного аэропорта.
AirportBrowse.java - здесь, в методе init() браузера
аэропорта, мы управляем состоянием enabled действия
создания терминала и передаем выбранный экземпляр
аэропорта для инициализации ссылки в создаваемом
терминале.
4.2.2.4. 4.2.2.4. Композиция One-to-One One-to-One
Композиция one-to-one рассматривается на
примере сущностей Customer и CustomerDetails:
Customer.java - сущность Customer содержит
необязательную ссылку на CustomerDetails,
аннотированную как @Composition.
CustomerDetails.java - сущность CustomerDetails.
customer-edit.xml - дескриптор экрана редактирования
заказчика. Он содержит вложенный источник данных
для экземпляра CustomerDetails. Для того, чтобы
загрузить вложенный экземляр, корневой источник
данных использует представление сущности Customer,
включающее атрибут details. Компонент FieldGroup
просто декларирует поле для атрибута details.
В результате редактирование экземпляра Customer
работает следующим образом:
Экран редактирования Customer содержит компонент
PickerField с двумя действиями: OpenAction и ClearAction:
Когда вызывается OpenAction, создается новый
экземпляр CustomerDetails и он отображается в
собственном экране редактирования. При нажатии OKOK в
этом экране, экземпляр CustomerDetails сохраняется
не в БД, а в источнике данных detailsDs редактора
Customer.
Компонент выбора отображает instance name сущности
CustomerDetails:
Когда пользователь нажимает OKOK в редакторе Customer,
измененный экземпляр Customer вместе с экземпляром
CustomerDetails отправляется в метод
DataManager.commit() на средний слой и сохраняется в
БД в одной транзакции.
Если пользователь вызывает ClearAction в поле
выбора, экземпляр CustomerDetails удаляется и ссылка
на него очищается в одной транзакции после коммита
редактора Customer.
4.3. 4.3. Загрузка и сохранение данных
4.3. 4.3. Загрузка и сохранение данных
В данном разделе рассматриваются различные способы
загрузки и сохранения данных в БД.
4.3.1. DataManager vs. EntityManager4.3.1. DataManager vs. EntityManager
И DataManager и EntityManager предназначены для
выполнения операций с сущностями (CRUD). Ниже
приведены различия между этими интерфейсами.
DataManagerDataManager EntityManagerEntityManager
DataManager доступен и на
среднем слое и на
клиентском уровне.
EntityManager доступен только на среднем слое.
DataManager является
синглтон-бином.
Ссылку на EntityManager необходимо получать
через интерфейс Persistence.
DataManager содержит
несколько
высокоуровневых методов
для работы с detached
сущностями: load(),
loadList(),
reload(), commit().
EntityManager в большой степени повторяет
стандартный
javax.persistence.EntityManager.
DataManager на самом деле делегирует выполнение
реализациям DataStore, поэтому особенности DataManager,
перечисленные ниже, актуальны только для наиболее
часто встречающегося случая, когда вы работаете с
сущностями, хранящимися в реляционной базе данных.
DataManagerDataManager EntityManagerEntityManager
DataManager всегда стартует новую
транзакцию внутри.
Для работы с EntityManager необходима
открытая транзакция.
DataManager загружает
частичные
сущности в соответствие с
представлением. Есть некоторые
исключения, см. подробности.
EntityManager всегда загружает все
локальные атрибуты. Если
используется представление, оно
влияет только на загрузку ссылочных
атрибутов. См. подробности.
DataManager выполняет только JPQL EntityManager может выполнять любые
DataManager выполняет только JPQL
запросы. Кроме того, он имеет
отдельные методы для загрузки
сущностей: load(), loadList();
и скалярных и агрегатных значений:
loadValues().
EntityManager может выполнять любые
JPQL или native (SQL) запросы.
DataManager проверяет права доступа,
когда вызывается с клиентского
уровня.
EntityManager не проверяет права
доступа.
DataManagerDataManager EntityManagerEntityManager
При работе на клиентском уровне доступен только
DataManager. На среднем слое, используйте EntityManager
когда необходимо реализовать атомарную логику внутри
транзакции или если его интерфейс лучше подходит для
решения задачи. В противном случае, на среднем слое
можно использовать любой из интерфейсов на выбор.
Если вам нужно обойти ограничения DataManager при
работе на клиентском уровне, создайте свой сервис и
используйте EntityManager для работы с данными. В
сервисе можно проверять права пользователя с помощью
интерфейса Security и возвращать клиенту данные в виде
персистентных или неперсистентных сущностей или
произвольных значений.
4.4. 4.4. Использование REST API REST API
Данный раздел содержит ряд примеров использования
REST API.
Детальная информация о методах REST API описана
согласно спецификации Swagger и доступна по адресу
http://les.cuba-platform.com/swagger/6.7.
4.4.1. 4.4.1. Получение OAuth OAuth токена
OAuth токен необходим для выполнения любого метода
REST API (кроме случая анонимного доступа к REST API).
Получить токен можно выполнив POST запрос по адресу
http://localhost:8080/app/rest/v2/oauth/token
Доступ к данному URL защищен с помощью базовой
аутентификации с использованием идентификатора и
пароля клиента REST API. Обратите внимание, что это не
логин и пароль пользователя приложения.
Идентификатор и пароль клиента REST API заданы в
свойствах приложения cuba.rest.client.id и
cuba.rest.client.secret (значения по умолчанию для этих
свойств: client и secret). Заголовок Authorization запроса
на получение токена должен содеражть логин и пароль
клиента, разделенные символом ":" и закодированные в
base64.
Тип запроса на получение токена должен быть
application/x-www-form-urlencoded, кодировка UTF-8.
Запрос должен содержать следующие параметры:
grant_type - всегда значение password.
username - логин пользователя приложения.
password - пароль пользователя приложения.
Метод возвращает JSON объект:
POST /oauth/token
Authorization: Basic Y2xpZW50OnNlY3JldA==
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=smith&password=qwerty123
{
"access_token": "29bc6b45-83cd-4050-8c7a-
2a8a60adf251",
"token_type": "bearer",
"expires_in": 43198,
Значение токена содержится в поле access_token
4.4.2. 4.4.2. Аутентификация LDAP LDAP в REST API REST API
Для настройки LDAP аутентификации в REST API
используются следующие свойства приложения:
cuba.rest.ldap.enabled - определяет включен ли вход
по LDAP логину/паролю.
cuba.rest.ldap.urls - URL сервера LDAP.
cuba.rest.ldap.base - base DN поиска имен
пользователей.
cuba.rest.ldap.user - distinguished name системного
пользователя, имеющего право на чтение информации
из LDAP.
cuba.rest.ldap.password - пароль системного
пользователя, заданного свойством
cuba.web.ldap.user.
cuba.rest.ldap.userLoginField - название атрибута
пользователя в LDAP, значение которого соответствует
логину пользователя. По умолчанию sAMAccountName
(подходит для Active Directory).
Пример содержимого файла local.app.properties:
"expires_in": 43198,
"scope": "rest-api"
}
cuba.rest.ldap.enabled = true
cuba.rest.ldap.urls = ldap://192.168.1.1:389
cuba.rest.ldap.base = ou=Employees,dc=mycompany,dc=com
cuba.rest.ldap.user = cn=System
User,ou=Employees,dc=mycompany,dc=com
cuba.rest.ldap.password = system_user_password
Получить OAuth токен можно выполнив POST запрос по
адресу:
http://localhost:8080/app/rest/v2/ldap/token
Доступ к данному URL защищен с помощью базовой
аутентификации с использованием идентификатора и
пароля клиента REST API. Обратите внимание, что это не
логин и пароль пользователя приложения.
Идентификатор и пароль клиента REST API заданы в
свойствах приложения cuba.rest.client.id и
cuba.rest.client.secret (значения по умолчанию для этих
свойств: client и secret). Заголовок Authorization
запроса на получение токена должен содержать логин и
пароль клиента, разделенные символом ":" и
закодированные в base64.
Запрос должен содержать следующие параметры
(соответствуют параметрам стандартной
аутентификации):
grant_type - всегда значение password.
username - логин пользователя приложения.
password - пароль пользователя приложения.
Тип запроса на получение токена должен быть
application/x-www-form-urlencoded, кодировка UTF-8.
Стандартная аутентификация может быть отключена при
помощи свойства
cuba.rest.standardAuthenticationEnabled:
4.4.3. 4.4.3. Собственный механизм аутентификации
Различные механизмы аутентификации могут
cuba.rest.standardAuthenticationEnabled = false
Различные механизмы аутентификации могут
предоставлять токен по ключу, по ссылке, по логину и
паролю LDAP и т.д. Стандартный механизм
аутентификации в REST API изменить нельзя, но можно
создать свой механизм. Для этого необходимо создать
REST-контроллер, который предоставит свой URL для входа
в приложение.
В этом примере мы рассмотрим механизм
аутентификации, позволяющий получить OAuth-токен по
промо-коду. За основу возьмём приложение, содержащее
сущность Coupon (Купон) с атрибутом code (промо-код).
Значение этого атрибута мы будем передавать в качестве
параметра аутентификации в GET-запросе.
1. Создайте сущность Coupon и добавьте ей атрибут code:
2. Создайте нового пользователя с логином promo-userpromo-user, от
лица которого будет выполняться аутентификация по
промо-коду.
3. В корневом каталоге модуля webweb (com.company.demo)
создайте новый файл конфигурации Spring rest-
dispatcher-spring.xml со следующим содержанием:
@Column(name = "CODE", unique = true, length = 4)
protected String code;
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance"
xmlns:context="http://www.springframework.org/schema
/context"
xsi:schemaLocation="http://www.springframework.org/s
4. Ссылку на этот файл укажите в свойстве приложения
cuba.restSpringContextConfig в файле
modules/web/src/web-app.properties:
5. Создайте пакет rest в корневом каталоге модуля webweb, а
в нём - свой контроллер Spring MVC. В контроллере
используйте бин OAuthTokenIssuer, который позволяет
сгенерировать и выдать REST API токен после
аутентификации:
xsi:schemaLocation="http://www.springframework.org/s
chema/beans
http://www.springframework.org/schema/beans/spring-
beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring
-context-4.3.xsd">
<context:component-scan base-
package="com.company.demo.web.rest"/>
</beans>
cuba.restSpringContextConfig =
+com/company/demo/rest-dispatcher-spring.xml
@RestController
@RequestMapping("auth-code")
public class AuthCodeController {
@Inject
private OAuthTokenIssuer oAuthTokenIssuer;
@Inject
private LoginService loginService;
@Inject
private Configuration configuration;
private Configuration configuration;
@Inject
private DataManager dataManager;
@Inject
private MessageTools messageTools;
// here we check secret code and issue token
using OAuthTokenIssuer
@RequestMapping(method = RequestMethod.GET)
public ResponseEntity get(@RequestParam("code")
String authCode) {
// obtain system session to be able to call
middleware services
WebAuthConfig webAuthConfig =
configuration.getConfig(WebAuthConfig.class);
UserSession systemSession;
try {
systemSession =
loginService.getSystemSession(webAuthConfig.getTrust
edClientPassword());
} catch (LoginException e) {
throw new RuntimeException("Error
during system auth");
}
// set security context
AppContext.setSecurityContext(new
SecurityContext(systemSession));
try {
// find coupon with code
LoadContext<Coupon> loadContext =
LoadContext.create(Coupon.class)
.setQuery(LoadContext.createQuery("select c from
demo$Coupon c where c.code = :code")
.setParameter("code",
authCode));
if (dataManager.load(loadContext) ==
null) {
// if coupon is not found - code is
incorrect
return new ResponseEntity<>(new
ErrorInfo("invalid_grant", "Bad credentials"),
HttpStatus.BAD_REQUEST);
}
// generate token for "promo-user"
OAuthTokenIssuer.OAuth2AccessTokenResult tokenResult
=
oAuthTokenIssuer.issueToken("promo-user",
messageTools.getDefaultLocale(),
Collections.emptyMap());
OAuth2AccessToken accessToken =
tokenResult.getAccessToken();
// set security HTTP headers to prevent
browser caching of security token
HttpHeaders headers = new
HttpHeaders();
headers.set(HttpHeaders.CACHE_CONTROL,
"no-store");
headers.set(HttpHeaders.PRAGMA, "no-
cache");
return new ResponseEntity<>
(accessToken, headers, HttpStatus.OK);
} finally {
// clean up security context
AppContext.setSecurityContext(null);
}
}
6. Исключите пакет rest из сканирования в модулях
web/coreweb/core: это необходимо, так как бин OAuthTokenIssuer
доступен только внутри контекста REST API, и
сканирование его в контексте приложения будет
вызывать ошибку.
7. Теперь пользователи могут получать код доступа
// POJO for JSON error messages
public static class ErrorInfo {
private String error;
private String error_description;
public ErrorInfo(String error, String
error_description) {
this.error = error;
this.error_description =
error_description;
}
public String getError() {
return error;
}
public String getError_description() {
return error_description;
}
}
}
<context:component-scan base-
package="com.company.demo">
<context:exclude-filter type="regex"
expression="com\.company\.demo\.web\.rest\..*"/>
</context:component-scan>
7. Теперь пользователи могут получать код доступа
OAuth2 через обычный запрос GET HTTP, передавая
значение промо-кода в параметре code:
http://localhost:8080/app/rest/auth-code?code=A325
Результат:
{"access_token":"74202587-6c2b-4d74-bcf2-
0d687ea85dca","token_type":"bearer","expires_in":43199,"scope":"rest-api"}
Теперь полученный access token нужно передавать в
REST API, как описано в общей документации.
4.4.3.1. Social Login 4.4.3.1. Social Login в REST API REST API
Механизм авторизации через социальные сети, или social
login, также можно использовать в REST API. Исходный код
приложения, описанного в этом примере, доступен на
GitHub, а сам пример описан в разделе Social Login. Ниже
приведены ключевые моменты этого примера,
позволяющие получить OAuth-токен через аккаунт
Facebook.
1. Создайте пакет restapi в корневом каталоге модуля
webweb и поместите в него собственный контроллер Spring
MVC. Контроллер должен содержать два основных
метода: get(), возвращающий ResponseEntity, и
login(), в котором мы будем получать OAuth-токен.
FacebookAuthenticationController.javaFacebookAuthenticationController.java
@RequestMapping(method = RequestMethod.GET)
public ResponseEntity get() {
String loginUrl = getAsPrivilegedUser(() ->
facebookService.getLoginUrl(getAppUrl(),
OAuth2ResponseType.CODE_TOKEN)
);
Здесь мы проверяем переданный код Facebook,
получаем код доступа и издаём токен с помощью
OAuthTokenIssuer:
FacebookAuthenticationController.javaFacebookAuthenticationController.java
);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.LOCATION, loginUrl);
return new ResponseEntity<>(headers,
HttpStatus.FOUND);
}
@RequestMapping(method = RequestMethod.POST, value =
"login")
public ResponseEntity<OAuth2AccessToken>
login(@RequestParam("code") String code) {
User user = getAsPrivilegedUser(() -> {
FacebookUserData userData =
facebookService.getUserData(getAppUrl(), code);
return
socialRegistrationService.findOrRegisterUser(
userData.getId(), userData.getEmail(),
userData.getName());
});
OAuth2AccessTokenResult tokenResult =
oAuthTokenIssuer.issueToken(user.getLogin(),
messageTools.getDefaultLocale(),
Collections.emptyMap());
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.CACHE_CONTROL, "no-
store");
headers.set(HttpHeaders.PRAGMA, "no-cache");
2. Исключите пакет restapi из сканирования в модулях
web/coreweb/core: это необходимо, так как бин OAuthTokenIssuer
доступен только внутри контекста REST API, и
сканирование его в контексте приложения будет
вызывать ошибку.
3. Создайте файл facebook-login-demo.html в каталоге
проекта modules/web/web/VAADIN. Он будет содержать
JavaScript-код, выполняющийся на HTML-странице:
facebook-login-demo.htmlfacebook-login-demo.html
headers.set(HttpHeaders.PRAGMA, "no-cache");
return new ResponseEntity<>
(tokenResult.getAccessToken(), headers,
HttpStatus.OK);
}
<context:component-scan base-
package="com.company.demo">
<context:exclude-filter type="regex"
expression="com\.company\.demo\.restapi\..*"/>
</context:component-scan>
<html>
<head>
<title>Facebook login demo with REST-API</title>
<script src="jquery-3.2.1.min.js"></script>
<style type="text/css">
#users { display: none; }
</style>
</head>
<body>
<h1>Facebook login demo with REST-API</h1>
<script type="application/javascript"...>
В этом скрипте мы попробуем залогиниться через
Facebook. Сначала удаляем лишний код из URL, затем
передаём код в REST API для получения OAuth-токена, и в
случае успешной аутентификации мы сможем
загружать и сохранять данные как обычно:
facebook-login-demo.htmlfacebook-login-demo.html
<script type="application/javascript"...>
</script>
<a id="fbLink" href="/app/rest/facebook">Login with
Facebook</a>
<div id="users">
You are logged in!
<h1>Users</h1>
<div id="usersList">
</div>
</div>
</body>
</html>
var oauth2Token = null;
function tryToLoginWithFacebook() {
var urlHash = window.location.hash;
if (urlHash && urlHash.indexOf('&code=') >= 0)
{
console.log("Try to login to CUBA REST-
API!");
var urlCode =
var urlCode =
urlHash.substring(urlHash.indexOf('&code=') +
'&code='.length);
console.log("Facebook code: " + urlCode);
history.pushState("", document.title,
window.location.pathname);
$.post({
url: '/app/rest/facebook/login',
headers: {
'Content-Type': 'application/x-www-
form-urlencoded'
},
dataType: 'json',
data: {code: urlCode},
success: function (data) {
oauth2Token = data.access_token;
loadUsers();
}
})
}
}
function loadUsers() {
$.get({
url: '/app/rest/v2/entities/sec$User?
view=_local',
headers: {
'Authorization': 'Bearer ' +
oauth2Token,
'Content-Type': 'application/x-www-
form-urlencoded'
},
success: function (data) {
$('#fbLink').hide();
Другой пример использования JavaScript-кода в
приложениях CUBA вы можете найти в разделе Пример
использования из JavaScript.
4.4.4. 4.4.4. Получение списка экземпляров сущности
Предполжим, в системе имеется сущность sales$Order, и
необходимо получить список экземляров этой сущности.
При этом, необходимо получить не все записи, а 50
записей, начиная с сотой (для отображения третьей
странице в каком-либо списке клиентского приложения).
Кроме простых атрибутов сущности sales$Order
результат должен содержать данные о клиенте (поле
customer). Заказы должны быть отсортированы по дате.
Базовый URL для получения списка экземпляров сущности
sales$Order:
http://localhost:8080/app/rest/v2/entities/sales$Order
Для выполнения описанных выше условий необходимо
задать параметры запроса:
viewview - представление, с которым должны быть
загружены сущности. В нашем примере представление
order-edit-view содержит ссылку на customer.
$('#users').show();
$.each(data, function (i, user) {
$('#usersList').append("<li>" +
user.name + " (" + user.email + ")</li>");
});
}
});
}
tryToLoginWithFacebook();
order-edit-view содержит ссылку на customer.
limitlimit - количество возвращаемыех экземпляров.
offsetoffset - позиция первого извлеченного элемента.
sortsort - имя атрибута сущности, по которому будет
произведена сортировка.
OAuth-токен должен быть передан в заголовке запроса
Authorization с типом BearerBearer:
Authorization: Bearer 29bc6b45-83cd-4050-8c7a-2a8a60adf251
В итоге получаем следующий GETGET http-запрос:
http://localhost:8080/app/rest/v2/entities/sales$Order?view=order-edit-
view&limit=50&offset=100&sort=date
Ответ будет выглядеть следующим образом:
[
{
"_entityName": "sales$Order",
"_instanceName": "00001",
"id": "46322d73-2374-1d65-a5f2-160461da22bf",
"date": "2016-10-31",
"description": "Vacation order",
"number": "00001",
"items": [
{
"_entityName": "sales$OrderItem",
"_instanceName": "Beach umbrella",
"id": "95a04f46-af7a-a307-de4e-f2d73cfc74f7",
"price": 23,
"name": "Beach umbrella"
},
{
"_entityName": "sales$OrderItem",
"_instanceName": "Sun lotion",
"id": "a2129675-d158-9e3a-5496-41bf1a315917",
"price": 9.9,
"name": "Sun lotion"
}
],
"customer": {
"_entityName": "sales$Customer",
"_instanceName": "Toby Burns",
"id": "4aa9a9d8-01df-c8df-34c8-c385b566ea05",
"firstName": "Toby",
"lastName": "Burns"
}
},
{
"_entityName": "sales$Order",
"_instanceName": "00002",
"id": "b2ad3059-384c-3e03-b62d-b8c76621b4a8",
"date": "2016-12-31",
"description": "New Year party set",
"number": "00002",
"items": [
{
"_entityName": "sales$OrderItem",
"_instanceName": "Jack Daniels",
"id": "0c566c9d-7078-4567-a85b-c67a44f9d5fe",
"price": 50.7,
"name": "Jack Daniels"
},
{
"_entityName": "sales$OrderItem",
"_instanceName": "Hennessy X.O",
"id": "c01be87b-3f91-7a86-50b5-30f2f0a49127",
"price": 79.9,
"name": "Hennessy X.O"
}
],
"customer": {
"_entityName": "sales$Customer",
"_instanceName": "Morgan Collins",
Обратите внимание, что для каждой сущности
загружаются атрибуты _entityName с именем сущности и
_instanceName, содержащий результат вычисления
короткого имени для сущности.
4.4.5. 4.4.5. Создание экземпляра сущности
Для создания нового экземпляра сущности sales$Order
необходимо выполнить POSTPOST запрос по адресу:
http://localhost:8080/app/rest/v2/entities/sales$Order
OAuth-токен должен быть передан в заголовке запроса
Authorization с типом BearerBearer.
Тело запроса должно содержать JSON объект,
описывающий новый экземпляр, например:
"_instanceName": "Morgan Collins",
"id": "5d111245-2ed0-abec-3bee-1a196da92e3e",
"firstName": "Morgan",
"lastName": "Collins"
}
}
]
{
"number": "00017",
"date": "2016-09-01",
"description": "Back to school",
"items": [
{
"_entityName": "sales$OrderItem",
"price": 100,
"name": "School bag"
},
{
"_entityName": "sales$OrderItem",
В теле запроса передается коллекция позиций заказа
items и ссылка на клиента customer. Рассмотрим, как
будут обработаны эти атрибуты.
Саначала посмотрим на класс Order:
"_entityName": "sales$OrderItem",
"price": 9.90,
"name": "Pencils"
}
],
"customer": {
"id": "4aa9a9d8-01df-c8df-34c8-c385b566ea05"
}
}
package com.company.sales.entity;
import
com.haulmont.chile.core.annotations.Composition;
import
com.haulmont.chile.core.annotations.NamePattern;
import com.haulmont.cuba.core.entity.StandardEntity;
import
com.haulmont.cuba.core.entity.annotation.OnDelete;
import com.haulmont.cuba.core.global.DeletePolicy;
import javax.persistence.*;
import java.util.Date;
import java.util.Set;
@NamePattern("%s|number")
@Table(name = "SALES_ORDER")
@Entity(name = "sales$Order")
public class Order extends StandardEntity {
private static final long serialVersionUID =
7565070704618724997L;
В сущности Order коллекция items аннотирована
@Composition. Методы создания и обновления сущности
REST API создают новые экземпляры для всех элементов
таких коллекций. Т.е. вместе с заказом (Order) будет
создано две позиции заказа (OrderItem).
Ссылка customer не имеет аннотации @Composition,
поэтому метод REST API попытается найти клиента с
переданным идентификатором и проставить его в поле
customer. Если клиент не будет найден, заказ не будет
создан и метод вернет ошибку.
В случае успеха возвращается полный граф созданной
сущности:
7565070704618724997L;
@Column(name = "NUMBER_")
protected String number;
@Temporal(TemporalType.DATE)
@Column(name = "DATE_")
protected Date date;
@Column(name = "DESCRIPTION")
protected String description;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CUSTOMER_ID")
protected Customer customer;
@Composition
@OnDelete(DeletePolicy.CASCADE)
@OneToMany(mappedBy = "order")
protected Set<OrderItem> items;
//getters and setters omitted
}
сущности:
{
"_entityName": "sales$Order",
"id": "5d7ff8e3-7828-ba94-d6ba-155c5c4f2a50",
"date": "2016-09-01",
"description": "Back to school",
"version": 1,
"number": "00017",
"createdBy": "admin",
"createTs": "2016-10-13 18:12:21.047",
"updateTs": "2016-10-13 18:12:21.047",
"items": [
{
"_entityName": "sales$OrderItem",
"id": "3158b8ed-7b7a-568e-aec5-0822c3ebbc24",
"createdBy": "admin",
"price": 9.9,
"name": "Pencils",
"createTs": "2016-10-13 18:12:21.047",
"version": 1,
"updateTs": "2016-10-13 18:12:21.047",
"order": {
"_entityName": "sales$Order",
"id": "5d7ff8e3-7828-ba94-d6ba-155c5c4f2a50"
}
},
{
"_entityName": "sales$OrderItem",
"id": "72774b8b-4fea-6403-7b52-4a6a749215fc",
"createdBy": "admin",
"price": 100,
"name": "School bag",
"createTs": "2016-10-13 18:12:21.047",
"version": 1,
"updateTs": "2016-10-13 18:12:21.047",
"order": {
4.4.6. 4.4.6. Изменение существующего экземпляра
сущности
Для изменения экземпляра сущности sales$Order
необходимо выполнить PUTPUT запрос по адресу:
http://localhost:8080/app/rest/v2/entities/sales$Order/5d7ff8e3-7828-ba94-
d6ba-155c5c4f2a50
Последняя часть запроса здесь - это идентификатор
изменяемой сущности.
OAuth-токен должен быть передан в заголовке запроса
Authorization с типом BearerBearer.
В теле запроса необходимо передать JSON объект,
содержащий только поля, которые мы хотим изменить,
например:
"order": {
"_entityName": "sales$Order",
"id": "5d7ff8e3-7828-ba94-d6ba-155c5c4f2a50"
}
}
],
"customer": {
"_entityName": "sales$Customer",
"id": "4aa9a9d8-01df-c8df-34c8-c385b566ea05",
"firstName": "Toby",
"lastName": "Burns",
"createdBy": "admin",
"createTs": "2016-10-13 15:32:01.657",
"version": 1,
"updateTs": "2016-10-13 15:32:01.657"
}
}
{
В теле ответа будет возвращена измененная сущность:
4.4.7. 4.4.7. Выполнение JPQL- JPQL-запроса (GET) (GET)
Перед выполнением запроса с помощью REST API
{
"date": "2017-10-01",
"customer" : {
"id" : "5d111245-2ed0-abec-3bee-1a196da92e3e"
}
}
{
"_entityName": "sales$Order",
"id": "5d7ff8e3-7828-ba94-d6ba-155c5c4f2a50",
"date": "2017-10-01",
"updatedBy": "admin",
"description": "Back to school",
"version": 2,
"number": "00017",
"createdBy": "admin",
"createTs": "2016-10-13 18:12:21.047",
"updateTs": "2016-10-13 19:13:02.656",
"customer": {
"_entityName": "sales$Customer",
"id": "5d111245-2ed0-abec-3bee-1a196da92e3e",
"firstName": "Morgan",
"lastName": "Collins",
"createdBy": "admin",
"createTs": "2016-10-13 15:31:27.821",
"version": 1,
"updateTs": "2016-10-13 15:31:27.821",
"email": "collins@gmail.com"
}
}
Перед выполнением запроса с помощью REST API
необходимо описать его в конфигурационном файле. В
корневом пакете модуля webweb (например,
com.company.sales) необходимо создать файл rest-
queries.xml. Затем этот файл объявляется в файле
свойств приложения модуля webweb (web-app.properties):
Содержимое файла rest-queries.xml:
Для выполнения JPQL запроса, необходимо выполнить GETGET
http-запрос к REST API:
http://localhost:8080/app/rest/v2/queries/sales$Order/ordersAfterDate?
startDate=2016-11-01&endDate=2017-11-01
Части URL:
cuba.rest.queriesConfig = +com/company/sales/rest-
queries.xml
<?xml version="1.0"?>
<queries xmlns="http://schemas.haulmont.com/cuba/rest-
queries.xsd">
<query name="ordersAfterDate" entity="sales$Order"
view="order-edit-view">
<jpql><![CDATA[select o from sales$Order o
where o.date >= :startDate and o.date <=
:endDate]]></jpql>
<params>
<param name="startDate"
type="java.util.Date"/>
<param name="endDate"
type="java.util.Date"/>
</params>
</query>
</queries>
sales$Order - имя извлекаемой сущности.
ordersAfterDate - имя запроса из конфигурационного
файла.
startDate и endDate - параметры запроса со
значениями.
OAuth-токен должен быть передан в заголовке запроса
Authorization с типом BearerBearer.
Метод возвращает JSON-массив со списком извлеченных
экземпляров сущности:
[
{
"_entityName": "sales$Order",
"_instanceName": "00002",
"id": "b2ad3059-384c-3e03-b62d-b8c76621b4a8",
"date": "2016-12-31",
"description": "New Year party set",
"number": "00002",
"items": [
{
"_entityName": "sales$OrderItem",
"_instanceName": "Jack Daniels",
"id": "0c566c9d-7078-4567-a85b-c67a44f9d5fe",
"price": 50.7,
"name": "Jack Daniels"
},
{
"_entityName": "sales$OrderItem",
"_instanceName": "Hennessy X.O",
"id": "c01be87b-3f91-7a86-50b5-30f2f0a49127",
"price": 79.9,
"name": "Hennessy X.O"
}
],
"customer": {
Список других возможных параметров для метода
выполнения запросов можно посмотреть в Swagger
документации.
4.4.8. 4.4.8. Выполнение JPQL- JPQL-запроса (POST) (POST)
JPQL-запрос также может быть выполнен с помощью POST-
запроса. Это необходимо для случая, когда параметр
JPQL-запроса является коллекцией. В файле
конфигурации JPQL-запросов для REST API тип параметра-
коллекции должен заканчиваться символами []:
java.lang.String[], java.util.UUID[] и т.п.
"customer": {
"_entityName": "sales$Customer",
"_instanceName": "Morgan Collins",
"id": "5d111245-2ed0-abec-3bee-1a196da92e3e",
"firstName": "Morgan",
"lastName": "Collins"
}
}
]
<?xml version="1.0"?>
<queries xmlns="http://schemas.haulmont.com/cuba/rest-
queries.xsd">
<query name="ordersByIds" entity="sales$Order"
view="order-edit-view">
<jpql><![CDATA[select o from sales$Order o
where o.id in :ids and o.status = :status]]></jpql>
<params>
<param name="ids"
type="java.util.UUID[]"/>
<param name="status"
type="java.lang.String"/>
</params>
Параметры JPQL-запроса должны быть переданы в теле
HTTP-запроса в JSON map:
URL POST-запроса:
http://localhost:8080/app/rest/v2/queries/sales$Order/ordersByIds?
returnCount=true
4.4.9. 4.4.9. Вызов метода сервиса (GET) (GET)
Предположим, в системе имеется сервис OrderService,
реализация которого выглядит следующим образом:
</params>
</query>
</queries>
{
"ids": ["c273fca1-33c2-0229-2a0c-78bc6d09110a",
"e6c04c18-c8a1-b741-7363-a2d58589d800", "d268a4e1-
f316-a7c8-7a96-87ba06afbbbd"],
"status": "ready"
}
package com.company.sales.service;
import com.haulmont.cuba.core.EntityManager;
import com.haulmont.cuba.core.Persistence;
import com.haulmont.cuba.core.Transaction;
import org.springframework.stereotype.Service;
import javax.inject.Inject;
import java.math.BigDecimal;
@Service(OrderService.NAME)
public class OrderServiceBean implements OrderService
{
Перед выполнением метода сервиса с помощью REST API
необходимо разрешить его вызов в конфигурационном
файле. В корневом пакете модуля webweb (например,
com.company.sales) необходимо создать файл rest-
services.xml. Затем этот файл объявляется в файле
свойств приложения модуля webweb (web-app.properties):
@Inject
private Persistence persistence;
@Override
public BigDecimal calculatePrice(String
orderNumber) {
BigDecimal orderPrice = null;
try (Transaction tx =
persistence.createTransaction()) {
EntityManager em =
persistence.getEntityManager();
orderPrice = (BigDecimal)
em.createQuery("select sum(oi.price) from
sales$OrderItem oi where oi.order.number =
:orderNumber")
.setParameter("orderNumber",
orderNumber)
.getSingleResult();
tx.commit();
}
return orderPrice;
}
}
cuba.rest.servicesConfig = +com/company/sales/rest-
services.xml
Содержимое файла rest-services.xml:
Для вызова метода сервиса, необходимо выполнить GETGET
http-запрос к REST API вида:
http://localhost:8080/app/rest/v2/services/sales_OrderService/calculatePrice?
orderNumber=00001
Части URL:
sales_OrderService - имя сервиса
calculatePrice - имя метода сервиса
orderNumber - аргумент метода со значением
OAuth-токен должен быть передан в заголовке запроса
Authorization с типом BearerBearer.
Метод сервиса может вернуть как простой тип данных,
так и сущность, коллекцию сущностей или произвольный
POJO. В нашем случае метод возвращает BigDecimal,
поэтому в теле ответа нам вернется число:
39.2
4.4.10. 4.4.10. Вызов метода сервиса (POST) (POST)
REST API позволяет выполнять методы сервисов,
<?xml version="1.0" encoding="UTF-8"?>
<services
xmlns="http://schemas.haulmont.com/cuba/rest-services-
v2.xsd">
<service name="sales_OrderService">
<method name="calculatePrice">
<param name="orderNumber"/>
</method>
</service>
</services>
REST API позволяет выполнять методы сервисов,
аргументами которых являются не только простые типы,
но также:
сущности
коллекции сущностей
произвольные POJO
Небольшой пример. Предположим, в сервис OrderService,
созданный в предыдущем разделе, добавлен следующий
метод:
Класс OrderValidationResult выглядит следующим
образом:
@Override
public OrderValidationResult validateOrder(Order
order, Date validationDate){
OrderValidationResult result=new
OrderValidationResult();
result.setSuccess(false);
result.setErrorMessage("Validation of order
"+order.getNumber()+" failed. validationDate parameter
is: "+validationDate);
return result;
}
package com.company.sales.service;
import java.io.Serializable;
public class OrderValidationResult implements
Serializable {
private boolean success;
private String errorMessage;
Новый метод сервиса принимает сущность Order в
качестве первого аргумента и возвращает POJO.
Перед вызовом данного метода с помощью REST API
необходимо разрешить его, добавив запись в
конфигурационный файл rest-services.xml (его
создание было рассмотрено в Вызов метода сервиса
(GET)):
private String errorMessage;
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<services
xmlns="http://schemas.haulmont.com/cuba/rest-services-
v2.xsd">
<service name="sales_OrderService">
<method name="calculatePrice">
<param name="orderNumber"/>
</method>
<method name="validateOrder">
Метод validateOrder сервиса вызвается POSTPOST запросом по
адресу:
http://localhost:8080/app/rest/v2/services/sales_OrderService/validateOrder
Параметры в случае POST передаются в теле запроса в
JSON объекте. Каждое поле JSON объекта соответствует
аргументу метода сервиса:
OAuth-токен должен быть передан в заголовке запроса
Authorization с типом BearerBearer.
Метод вернет сериализованный POJO:
<method name="validateOrder">
<param name="order"/>
<param name="validationDate"/>
</method>
</service>
</services>
{
"order" : {
"number": "00050",
"date" : "2016-01-01"
},
"validationDate": "2016-10-01"
}
{
"success": false,
"errorMessage": "Validation of order 00050 failed.
validationDate parameter is: 2016-10-01"
}
4.4.11. 4.4.11. Скачивание файлов
При скачивании файла передавать токен в заголовке
запроса часто оказывается неудобно. Хочется иметь URL
для скачивания, который можно подставить например в
атрибут srcsrc тега imgimg. В этом случае передача OAuth-токена
возможна в самом запросе в параметре с именем
access_tokenaccess_token.
Например, в систему загружено изображение.
Идентификатор соответствующего FileDescriptor -
44809679-e81c-e5ae-dd81-f56f223761d6.
В этом случае URL для загрузки изображения будет
выглядеть так:
http://localhost:8080/app/rest/v2/files/44809679-e81c-e5ae-dd81-f56f223761d6?
access_token=a2f0bb4e-773f-6b59-3450-3934cbf0a2d6
4.4.12. 4.4.12. Загрузка файлов
Для загрузки файлов вам нужно получить OAuth-токен,
который понадобится в дальнейших запросах.
Используем простую форму для загрузки:
Далее используем jQuery, чтобы получить JSON с объектом
data, который, по сути, представляет собой новый
FileDescriptor. Использовать загруженный файл мы
<form id="fileForm">
<h2>Select a file:</h2>
<input type="file" name="file" id="fileUpload"/>
<br/>
<button type="submit">Upload</button>
</form>
<h2>Result:</h2>
<img id="uploadedFile" src="" style="display: none"/>
FileDescriptor. Использовать загруженный файл мы
можем по идентификатору созданного экземпляра
FileDescriptor с токеном в качестве параметра:
$('#fileForm').submit(function (e) {
e.preventDefault();
var file = $('#fileUpload')[0].files[0];
var url =
'http://localhost:8080/app/rest/v2/files?name=' +
file.name;
// send file name as parameter
$.ajax({
type: 'POST',
url: url,
headers: {
'Authorization': 'Bearer ' + oauthToken
// add header with access token
},
processData: false,
contentType: false,
dataType: 'json',
data: file,
success: function (data) {
alert('Upload successful');
$('#uploadedFile').att