mirror of
https://gitee.com/jmix/cuba.git
synced 2024-12-03 19:57:36 +08:00
Merge from trunk 10656
This commit is contained in:
parent
32bbf4d4ee
commit
345b2185a5
@ -90,13 +90,110 @@
|
||||
</property>. В стандартном варианте развертывания в <application>Tomcat</application> это подкаталог <filename>WEB-INF/db</filename> каталога веб-приложения среднего слоя, например <filename>tomcat/webapps/app-core/WEB-INF/db</filename>.</para>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<title>Варианты запуска Tomcat</title>
|
||||
<para>TODO</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Использование инструментов JMX</title>
|
||||
<para>TODO</para>
|
||||
<section>
|
||||
<title>Встроенная JMX консоль</title>
|
||||
<para>Модуль Web Client базового проекта <structname>cuba</structname> платформы содержит средство просмотра и редактирования JMX объектов. Точкой входа в этот инструмент является экран <filename>com/haulmont/cuba/web/app/ui/jmxcontrol/browse/display-mbeans.xml</filename>, зарегистрированный под идентификатором <code>jmxcontrol$DisplayMbeans</code> и в стандартном меню доступный через пункт <guimenu>Администрирование</guimenu> -> <guimenuitem>Консоль JMX</guimenuitem>.</para>
|
||||
<para>Без дополнительной настройки консоль отображает все JMX объекты, зарегистрированные в JVM, на которой работает блок Web Client, к которому в данный момент подключен пользователь. Соответственно, в простейшем случае развертывания всех блоков приложения в одном экземпляре веб-контейнера консоль имеет доступ к JMX бинам всех уровней, а также к JMX объектам самой JVM и веб-контейнера. </para>
|
||||
<para>Имена бинов приложения имеют префикс, соответсвующий имени веб-приложения, их содержащего. Например, бин <code>app-core.cuba:type=CachingFacade</code> загружен веб-приложением <structname>app-core</structname>, реализующим блок Middleware, а бин <code>app.cuba:type=CachingFacade</code> загружен веб-приложением <structname>app</structname>, реализующим блок Web Client.</para>
|
||||
<para>Консоль JMX может также работать с JMX объектами произвольной удаленной JVM. Это актуально в случае развертывания блоков приложения на нескольких экземплярах веб-контейнера, например отдельно Web Client и Middleware. </para>
|
||||
<para>Для подключения к удаленной JVM необходимо в поле <guilabel>Соединение JMX</guilabel> консоли выбрать созданное ранее соединение, либо вызвать экран создания нового соединения:</para>
|
||||
<figure>
|
||||
<title>Редактирование JMX соединения</title>
|
||||
<mediaobject>
|
||||
<imageobject>
|
||||
<imagedata contentwidth="100%" align="center" fileref="img/jmx-connection-edit.png"/>
|
||||
</imageobject>
|
||||
</mediaobject>
|
||||
</figure>
|
||||
<para>Для соединения указывается JMX хост и порт, логин и пароль. Имеется также поле <guilabel>Имя узла</guilabel>, которое заполняется автоматически, если по указанному адресу обнаружен какой-либо блок CUBA-приложения. В этом случае значением этого поля становится комбинация свойств <property>
|
||||
<link linkend="cuba.webHostName">cuba.webHostName</link>
|
||||
</property> и <property>
|
||||
<link linkend="cuba.webPort">cuba.webPort</link>
|
||||
</property> данного блока, что позволяет идентифицировать содержащий его сервер. Если подключение произведено к постороннему JMX интерфейсу, то поле <guilabel>Имя узла</guilabel> будет иметь значение "Unknown JMX interface". Значение данного поля можно произвольно изменять. </para>
|
||||
<para>Для подключения удаленной JVM она должна быть соответствующим образом настроена - см. ниже.</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Настройка удаленного доступа к JMX</title>
|
||||
<para>В данном разделе рассматривается настройка запуска сервера <application>Tomcat</application>, необходимая для удаленного подключения к нему инструментов JMX.</para>
|
||||
<section>
|
||||
<title>Tomcat JMX под Windows</title>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>Отредактировать файл <filename>bin/setenv.bat</filename> следующим образом:<programlisting>set CATALINA_OPTS=%CATALINA_OPTS% ^
|
||||
-Dcom.sun.management.jmxremote ^
|
||||
-Djava.rmi.server.hostname=192.168.10.10 ^
|
||||
-Dcom.sun.management.jmxremote.ssl=false ^
|
||||
-Dcom.sun.management.jmxremote.port=7777 ^
|
||||
-Dcom.sun.management.jmxremote.authenticate=true ^
|
||||
-Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password ^
|
||||
-Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access</programlisting></para>
|
||||
<para>Здесь в параметре <code>java.rmi.server.hostname</code> необходимо указать реальный IP адрес или DNS имя компьютера, на котором запущен сервер, в параметре <code>com.sun.management.jmxremote.port</code> - порт для подключения инструментов JMX.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Отредактировать файл <filename>conf/jmxremote.access</filename>. Он должен содержать имена пользователей, которые будут подключаться к JMX, и их уровень доступа. Например:<programlisting>admin readwrite</programlisting></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Отредактировать файл <filename>conf/jmxremote.password</filename>. Он должен содержать пароли пользователей JMX, например:<programlisting>admin admin</programlisting></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Файл паролей должен иметь разрешение на чтение только для пользователя, от имени которого работает сервер <application>Tomcat</application>. Настроить права можно следующим образом:<itemizedlist>
|
||||
<listitem>
|
||||
<para>Открыть командную строку и перейти в каталог <filename>conf</filename>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Выполнить команду:</para>
|
||||
<para><prompt>cacls jmxremote.password /P "domain_name\user_name":R</prompt>
|
||||
|
||||
</para>
|
||||
<para>где <code>domain_name\user_name</code> - домен и имя пользователя.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>После выполнения данной команды файл в <application>Проводнике</application> будет отмечен изображением замка.</para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Если <application>Tomcat</application> установлен как служба Windows, то для службы должен быть задан вход в систему с учетной записью, имеющей права на файл <filename>jmxremote.password</filename>. Кроме того, следует иметь в виду, что в этом случае файл <filename>bin/setenv.bat</filename> не используется, и соответствующие параметры запуска JVM должны быть заданы в приложении, настраивающем службу.</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
<section>
|
||||
<title>Tomcat JMX под Linux</title>
|
||||
<para><itemizedlist>
|
||||
<listitem>
|
||||
<para>Отредактировать файл <filename>bin/setenv.sh</filename> следующим образом:<programlisting>CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote \
|
||||
-Djava.rmi.server.hostname=192.168.10.10 \
|
||||
-Dcom.sun.management.jmxremote.port=7777 \
|
||||
-Dcom.sun.management.jmxremote.ssl=false \
|
||||
-Dcom.sun.management.jmxremote.authenticate=true"
|
||||
|
||||
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password -Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access"</programlisting></para>
|
||||
<para>Здесь в параметре <code>java.rmi.server.hostname</code> необходимо указать реальный IP адрес или DNS имя компьютера, на котором запущен сервер, в параметре <code>com.sun.management.jmxremote.port</code> - порт для подключения инструментов JMX.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Отредактировать файл <filename>conf/jmxremote.access</filename>. Он должен содержать имена пользователей, которые будут подключаться к JMX, и их уровень доступа. Например:<programlisting>admin readwrite</programlisting></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Отредактировать файл <filename>conf/jmxremote.password</filename>. Он должен содержать пароли пользователей JMX, например:<programlisting>admin admin</programlisting></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Файл паролей должен иметь разрешение на чтение только для пользователя, от имени которого работает сервер <application>Tomcat</application>. Настроить права для текущего пользователя можно следующим образом:<itemizedlist>
|
||||
<listitem>
|
||||
<para>Открыть командную строку и перейти в каталог <filename>conf</filename>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Выполнить команду:</para>
|
||||
<para><prompt>chmod go-rwx jmxremote.password</prompt>
|
||||
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<title>Отказоустойчивость и балансировка нагрузки</title>
|
||||
@ -115,8 +212,4 @@
|
||||
</code>.</para>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<title>Мониторинг работы приложения</title>
|
||||
<para>TODO</para>
|
||||
</section>
|
||||
</chapter>
|
||||
|
@ -210,18 +210,414 @@
|
||||
</section>
|
||||
<section>
|
||||
<title>Описание скриптов сборки</title>
|
||||
<para>TODO</para>
|
||||
<para>Для сборки проектов на основе платформы используется <application>
|
||||
<ulink url="http://www.gradle.org">Gradle</ulink>
|
||||
</application>. Скрипты сборки представляют собой два файла в корневом каталоге проекта: <itemizedlist>
|
||||
<listitem>
|
||||
<para><filename>settings.gradle</filename> - задает название и состав <link linkend="app_modules">модулей</link> проекта</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><filename>build.gradle</filename> - определяет конфигурацию сборки.</para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
<section>
|
||||
<title>Структура build.gradle</title>
|
||||
<para>Секция <code>allprojects</code> задает группу и версию собираемых <link linkend="artifact">артефактов</link> проекта. Имена артефактов формируются на основе имен модулей, заданных в <filename>settings.gradle</filename>. Если свойство <code>ext.isSnapshot</code> равно <code>true</code>, то в именах артефактов будет присутствовать суффикс <code>SNAPSHOT</code>. Свойство <code>ext.tomcatDir</code> задает расположение каталога установки <application>Tomcat</application>.</para>
|
||||
<para>Основная часть секции <code>buildscript</code> - объявить зависимость от плагина <structname>cuba-plugin</structname>, в котором сосредоточена специфика сборки проектов на платформе. Плагин подключается далее в конфигурацию сборки модулей с помощью метода<programlisting>apply(plugin: 'cuba')</programlisting></para>
|
||||
<para>Кроме того, секция <code>buildscript</code> задает параметры подключения к репозиторию артефактов, из которого будут загружены зависимости. Имя и пароль для подключения могут быть как заданы явно, так и получены из переменных окружения - как правило это переменные <code>HAULMONT_REPOSITORY_USER</code> и <code>HAULMONT_REPOSITORY_PASSWORD</code>. </para>
|
||||
<para>В этой секции также могут быть объявлены свойства <code>ext.cubaVersion</code> и <code>ext.cubaPluginVersion</code>, задающие версию базовых проектов и плагина сборки. Если эти переменные отсутствуют, то версии задаются непосредственно при объявлении зависимостей.</para>
|
||||
<para>Далее в секциях <code>configure</code> определяются параметры сборки модулей приложения.</para>
|
||||
<para>Исполняемыми единицами в Gradle являются <firstterm>задачи</firstterm> (tasks). Они задаются как внутри плагинов, так и в самом скрипте сборки. Рассмотрим задачи, параметры которых могут быть сконфигурированы в <filename>build.gradle</filename>.<itemizedlist>
|
||||
<listitem>
|
||||
<para><code>enhance</code> - задача типа <code>CubaEnhancing</code>, выполняющая bytecode enhancement классов сущностей. Объявляется в <link linkend="app_modules">модуле</link> <structname>global</structname>. Параметры:<itemizedlist>
|
||||
<listitem>
|
||||
<para><code>persistenceXml</code> - путь к файлу <link linkend="persistence.xml">
|
||||
<filename>persistence.xml</filename>
|
||||
</link> проекта.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>metadataXml</code> - путь к файлу <filename>
|
||||
<link linkend="metadata.xml">metadata.xml</link>
|
||||
</filename> проекта.</para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
<para>Например:<programlisting>task enhance(type: CubaEnhancing) {
|
||||
persistenceXml = "${globalModule.projectDir}/src/persistence.xml"
|
||||
metadataXml = "${globalModule.projectDir}/src/metadata.xml"
|
||||
}</programlisting></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>deploy</code> - задача типа <code>CubaDeployment</code>, выполняющая развертывание модуля в <application>Tomcat</application>. Объявляется в модулях <structname>core</structname>, <structname>web</structname>, <structname>portal</structname>. Параметры:<itemizedlist>
|
||||
<listitem>
|
||||
<para><code>appName</code> - имя веб-приложения, которое будет создано из модуля. Фактически это имя подкаталога внутри <filename>tomcat/webapps</filename>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>jarNames</code> - список имен JAR файлов (без версии), получающихся в результате сборки модуля, которые надо поместить в каталог <filename>WEB-INF/lib</filename> веб-приложения. Все остальные артефакты модуля и зависимостей будут записаны в <filename>tomcat/shared/lib</filename>.</para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
<para>Например:<programlisting>task deploy(dependsOn: assemble, type: CubaDeployment) {
|
||||
appName = 'app-core'
|
||||
jarNames = ['cuba-global', 'cuba-core', 'app-global', 'app-core']
|
||||
}</programlisting></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>buildWar</code> - задача типа <code>CubaWarBuilding</code>, выполняющая сборку модуля в WAR файл. Может быть объявлена в модулях <structname>core</structname>, <structname>web</structname>, <structname>portal</structname>, если требуется развертывание приложения в контейнер, отличный от стандартного Tomcat. </para>
|
||||
<para>Параметр <code>appName</code> - имя результирующего WAR.</para>
|
||||
<para>Например:<programlisting>task buildWar(dependsOn: assemble, type: CubaWarBuilding) {
|
||||
appName = 'app-core'
|
||||
}</programlisting></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>createDb</code> - задача типа <code>CubaDbCreation</code>, выполняющая создание базы данных приложения и выполнение соответствующих <link linkend="db_scripts">скриптов</link>. Объявляется в модуле <structname>core</structname>. Параметры:<itemizedlist>
|
||||
<listitem>
|
||||
<para><code>dbms</code> - тип СУБД, задается строкой <code>postgres</code> или <code>mssql</code>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>dbName</code> - имя базы данных. </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>dbUser</code> - имя пользователя СУБД.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>dbPassword</code> - пароль пользователя СУБД.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>host</code> - имя хоста СУБД. Если не задан, используется <code>localhost</code>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>dropDbSql</code> - команда SQL для удаления БД. Если не задана, используется значение по умолчанию, зависящее от типа СУБД. </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>createDbSql</code> - команда SQL для создания БД. Если не задана, используется значение по умолчанию, зависящее от типа СУБД. </para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
<para>Например:<programlisting>task createDb(dependsOn: assemble, description: 'Creates local database', type: CubaDbCreation) {
|
||||
dbms = 'postgres'
|
||||
dbName = 'sales'
|
||||
dbUser = 'cuba'
|
||||
dbPassword = 'cuba'
|
||||
}</programlisting></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>updateDb</code> - задача типа <code>CubaDbUpdate</code>, выполняющая обновление базы данных приложения путем выполнения соответствующих <link linkend="db_scripts">скриптов</link>. Аналогична задаче <code>createDb</code>, за исключением отсутствия параметров <code>dropDbSql</code> и <code>createDbSql</code>.</para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Типичные задачи сборки</title>
|
||||
<para>Задачи (tasks) Gradle запускаются из командной строки и выполняют различные действия по сборке проекта. Например, чтобы выполнить компиляцию Java файлов и сборку JAR файлов артефактов проекта, необходимо запустить следующую команду:</para>
|
||||
<para><prompt>gradle assemble</prompt></para>
|
||||
<para>Для ускорения повторной сборки желательно вызывать <application>Gradle</application> в режиме демона (его процесс будет оставаться в памяти):</para>
|
||||
<para><prompt>gradle --daemon assemble</prompt></para>
|
||||
<para>Для удаления демона из памяти используется следующая команда:</para>
|
||||
<para><prompt>gradle --stop</prompt></para>
|
||||
<para>Рассмотрим типичные задачи сборки в обычном порядке их использования.</para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para><code>idea</code> - создать проектные файлы <application>IntelliJ IDEA</application>. При выполнении этой задачи из репозитория артефактов в локальный кэш <application>Gradle</application> загружаются зависимости вместе со своими исходными кодами.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>cleanIdea</code> - удалить проектные файлы <application>IntelliJ IDEA</application>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>assemble</code> - выполнить компиляцию Java файлов и сборку JAR файлов артефактов проекта в подкаталогах <filename>build</filename> модулей.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>clean</code> - удалить подкаталоги <filename>build</filename> всех модулей проекта.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>setupTomcat</code> - установить сервер <application>Tomcat</application> в путь, заданный свойством <code>ext.tomcatDir</code> скрипта <filename>build.gradle</filename>. Если данное свойство не задано, установка производится в каталог <filename>tomcat</filename> на одном уровне с каталогом проекта, т.е. в <filename>${rootDir}/../tomcat</filename>.</para>
|
||||
<para>Версия <application>Tomcat</application> задается в плагине сборки <structname>cuba-plugin</structname>. В процессе установки конфигурация Tomcat модифицируется для поддержки стандартного механизма развертывания приложений платформы. В частности, добавляются командные файлы <filename>setenv.bat</filename> и <filename>setenv.sh</filename>, а также конфигурационный файл журнала <filename>conf/log4j.xml</filename>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>deploy</code> - развертывание приложения на сервере <application>Tomcat</application>, предварительно установленном задачей <code>setupTomcat</code>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>createDb</code> - создание базы данных приложения и выполнение соответствующих <link linkend="db_scripts">скриптов</link>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>updateDb</code> - обновление существующей базы данных приложения путем выполнения соответствующих <link linkend="db_scripts">скриптов</link>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>start</code> - запуск сервера <application>Tomcat</application>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>stop</code> - остановка запущенного сервера <application>Tomcat</application>. </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>restart</code> - последовательное выполнение задач <code>stop</code>, <code>deploy</code>, <code>start</code>. </para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<title>Создание проекта</title>
|
||||
<para>TODO</para>
|
||||
<para>Рекомендуемый способ создания нового проекта - использование <application>CUBA Studio</application>. Пример рассмотрен в главе "Быстрый старт" данного руководства: <xref linkend="qs_studio_setup"/></para>
|
||||
<para>Если применение <application>CUBA Studio</application> невозможно, для создания нового проекта необходимо выполнить следующее:<itemizedlist>
|
||||
<listitem>
|
||||
<para>Загрузить шаблон проекта, описанного в <xref linkend="qs_setup"/> главы "Быстрый старт", и распаковать его в локальный каталог.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Выбрать название проекта. Оно должно состоять только из латинских букв в нижнем регистре, и быть не слишком длинным. Далее для примера используется название <code>bookstore</code>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Выбрать корневой Java пакет. Далее для примера используется <code>com.sample.bookstore</code>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>В файле <filename>settings.gradle</filename> изменить строку с именем проекта:<programlisting>rootProject.name = 'bookstore'</programlisting></para>
|
||||
<para>Если модуль <structname>portal</structname> не нужен, удалить из объявления <code>include</code> элемент <code>':app-portal'</code>, и удалить соответствующее объявление <code>project(':app-portal').projectDir</code>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>В файле <filename>build.gradle</filename> произвести следующие изменения:<itemizedlist>
|
||||
<listitem>
|
||||
<para>В переменной <code>ext.artifactGroup</code> указать желаемое название группы для артефактов. Этот параметр играет роль только при выгрузке собранных артефактов приложения в <link linkend="artifact_repository">репозиторий</link>. Например:<programlisting>allprojects {
|
||||
ext.artifactGroup = 'com.sample.bookstore'
|
||||
...</programlisting></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>В секции конфигурации модуля <structname>core</structname> указать для задач <code>createDb</code> и <code>updateDb</code> желаемое имя базы данных. Например:<programlisting>configure(coreModule) {
|
||||
...
|
||||
task createDb(dependsOn: assemble, description: 'Creates local database', type: CubaDbCreation) {
|
||||
dbms = 'postgres'
|
||||
dbName = 'bookstore'
|
||||
dbUser = 'cuba'
|
||||
dbPassword = 'cuba'
|
||||
}
|
||||
|
||||
task updateDb(dependsOn: assemble, description: 'Updates local database', type: CubaDbUpdate) {
|
||||
dbms = 'postgres'
|
||||
dbName = 'bookstore'
|
||||
dbUser = 'cuba'
|
||||
dbPassword = 'cuba'
|
||||
}
|
||||
...</programlisting></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Если модуль <structname>portal</structname> не используется, удалить объявление переменной <code>portalModule</code>, ее использование в списке <code>configure([...])</code>, и всю секцию <code>configure(portalModule)</code>.</para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>В модуле <structname>core</structname>: </para>
|
||||
<para>В файле <link linkend="context.xml">
|
||||
<filename>context.xml</filename>
|
||||
</link> изменить URL подключения к базе данных:<programlisting><Context>
|
||||
<!-- PostgreSQL connection -->
|
||||
<Resource
|
||||
...
|
||||
username="cuba"
|
||||
password="cuba"
|
||||
url="jdbc:postgresql://localhost/bookstore"/>
|
||||
...</programlisting></para>
|
||||
<para>В файле <filename>
|
||||
<link linkend="spring.xml">spring.xml</link>
|
||||
</filename> изменить базовый пакет поиска аннотированных бинов:<programlisting><context:component-scan base-package="com.sample.bookstore"/></programlisting></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>В модуле <structname>web</structname>: </para>
|
||||
<para>Перенести класс <code>App</code> и <link linkend="main_message_pack">файлы локализованных сообщений</link> в пакет <code>com.sample.bookstore.web</code>, и в файле <filename>web/WEB-INF/<link linkend="web.xml">web.xml</link></filename> указать новое расположение этого класса для сервлета <code>app_servlet</code>:<programlisting><servlet>
|
||||
<servlet-name>app_servlet</servlet-name>
|
||||
<servlet-class>com.haulmont.cuba.web.sys.CubaApplicationServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>application</param-name>
|
||||
<param-value>com.sample.bookstore.web.App</param-value>
|
||||
</init-param>
|
||||
...</programlisting></para>
|
||||
<para>Изменить значение свойства приложения <property>
|
||||
<link linkend="cuba.mainMessagePack">cuba.mainMessagePack</link>
|
||||
</property> в <filename>web-app.properties</filename>:<programlisting>cuba.mainMessagePack=com.haulmont.cuba.web com.sample.bookstore.web</programlisting></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Если используется модуль <structname>portal</structname>, изменить в нем соответственно пакет расположения классов контроллеров и ссылку на него в <filename>portal-dispatcher-spring.xml</filename>.</para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
<para>После выполнения вышеописанных действий можно собрать новый проект:</para>
|
||||
<para><prompt>gradle idea assemble setupTomcat deploy</prompt></para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Отладка и тестирование</title>
|
||||
<para>TODO</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Интеграция со сторонними приложениями</title>
|
||||
<para>TODO</para>
|
||||
<section>
|
||||
<title>Подключение отладчика</title>
|
||||
<para>Запустить сервер Tomcat в режиме отладки можно либо выполнением команды сборки</para>
|
||||
<para><prompt>gradle start</prompt></para>
|
||||
<para>либо запуском командного файла <filename>bin/debug.*</filename> установленного <application>Tomcat</application>.</para>
|
||||
<para>После этого сервер будет принимать подключения отладчика на порту 8787. Порт можно изменить в файле <filename>bin/setenv.*</filename> в переменной <code>JPDA_OPTS</code>.</para>
|
||||
<para>Для пошаговой отладки в <application>Intellij IDEA</application> необходимо в проекте приложения создать новый элемент <guilabel>Run/Debug Configuration</guilabel> типа <guilabel>Remote</guilabel>, и в его поле <guilabel>Port</guilabel> указать 8787.</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Тестирование</title>
|
||||
<section>
|
||||
<title>Модульные тесты</title>
|
||||
<para>Модульные тесты (unit tests) можно создавать и выполнять и на <link linkend="app_tiers">уровне</link> Middleware, и на клиентском уровне. Для этого платформа включает в себя фреймворки <ulink url="http://junit.org">JUnit</ulink> и <ulink url="http://code.google.com/p/jmockit">JMockit</ulink>.</para>
|
||||
<para>Допустим, имеется следующий контроллер экрана:<programlisting>public class OrderEditor extends AbstractEditor {
|
||||
|
||||
@Named("itemsTable.add")
|
||||
protected AddAction addAction;
|
||||
|
||||
@Override
|
||||
public void init(Map<String, Object> params) {
|
||||
addAction.setWindowId("sales$Product.lookup");
|
||||
addAction.setHandler(new Lookup.Handler() {
|
||||
@Override
|
||||
public void handleLookup(Collection items) {
|
||||
// some code
|
||||
}
|
||||
});
|
||||
}
|
||||
}</programlisting></para>
|
||||
<para>Тогда можно написать следющий тест, проверяющий работу метода <code>init()</code>:<programlisting>public class OrderEditorTest {
|
||||
|
||||
OrderEditor editor;
|
||||
|
||||
@Mocked
|
||||
Window.Editor frame;
|
||||
|
||||
@Mocked
|
||||
AddAction addAction;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
editor = new OrderEditor();
|
||||
editor.setWrappedFrame(frame);
|
||||
editor.addAction = addAction;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInit() {
|
||||
editor.init(Collections.<String, Object>emptyMap());
|
||||
editor.setItem(new Order());
|
||||
|
||||
new Verifications() {
|
||||
{
|
||||
addAction.setWindowId("sales$Product.lookup");
|
||||
addAction.setHandler(withInstanceOf(Window.Lookup.Handler.class));
|
||||
}
|
||||
};
|
||||
}
|
||||
}</programlisting></para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Интеграционные тесты Middleware</title>
|
||||
<para>На уровне Middleware можно создавать интеграционные тесты, которые выполняются в полнофункциональном контейнере <application>Spring</application> с подключением к базе данных. В тестах такого типа можно выполнять код любого слоя внутри Middleware - от сервисов до ORM. </para>
|
||||
<para>Для создания интеграционных тестов в модуле <structname>core</structname> проекта приложения должен быть создан базовый класс - наследник <code>CubaTestCase</code>. В этом классе должны быть переопределены методы инициализации доступа к данным и получения списка файлов конфигурации. Например: <programlisting>public class SalesTestCase extends CubaTestCase {
|
||||
|
||||
@Override
|
||||
protected void initDataSources() throws Exception {
|
||||
Class.forName("org.postgresql.Driver");
|
||||
TestDataSource ds = new TestDataSource("jdbc:postgresql://localhost/sales_test", "cuba", "cuba");
|
||||
TestContext.getInstance().bind("java:comp/env/jdbc/CubaDS", ds);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getTestAppProperties() {
|
||||
String[] files = {
|
||||
"cuba-app.properties",
|
||||
"app.properties",
|
||||
"test-app.properties",
|
||||
};
|
||||
return Arrays.asList(files);
|
||||
}
|
||||
}</programlisting></para>
|
||||
<para>В качестве базы данных рекомендуется использовать отдельную тестовую БД, которую можно создавать, например, следующей задачей в <filename>build.gradle</filename>: <programlisting>configure(coreModule) {
|
||||
...
|
||||
task createTestDb(dependsOn: assemble, description: 'Creates local Postgres database for tests', type: CubaDbCreation) {
|
||||
dbms = 'postgres'
|
||||
dbName = 'sales_test'
|
||||
dbUser = 'cuba'
|
||||
dbPassword = 'cuba'
|
||||
}</programlisting></para>
|
||||
<para>Класс <code>CubaTestCase</code> содержит следующие поля и методы, которые можно использовать в коде тестов:<itemizedlist>
|
||||
<listitem>
|
||||
<para><code>persistence</code> - ссылка на интерфейс <code>
|
||||
<link linkend="persistence">Persistence</link>
|
||||
</code></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>metadata</code> - ссылка на интерфейс <code>
|
||||
<link linkend="metadata">Metadata</link>
|
||||
</code></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><code>deleteRecord()</code> - метод, который удобно использовать в <code>tearDown()</code> для удаления тестовых объектов из БД.</para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
<para>Пример теста, проверяющего чтение сущностей из базы данных:<programlisting>public class CustomerLoadTest extends SalesTestCase {
|
||||
|
||||
private UUID customerId;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
persistence.createTransaction().execute(new Transaction.Runnable() {
|
||||
@Override
|
||||
public void run(EntityManager em) {
|
||||
Customer customer = new Customer();
|
||||
customerId = customer.getId();
|
||||
customer.setName("testCustomer");
|
||||
em.persist(customer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
deleteRecord("SALES_CUSTOMER", customerId);
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void test() {
|
||||
Transaction tx = persistence.createTransaction();
|
||||
try {
|
||||
EntityManager em = persistence.getEntityManager();
|
||||
TypedQuery<Customer> query = em.createQuery(
|
||||
"select c from sales$Customer c", Customer.class);
|
||||
List<Customer> list = query.getResultList();
|
||||
tx.commit();
|
||||
assertTrue(list.size() > 0);
|
||||
} finally {
|
||||
tx.end();
|
||||
}
|
||||
}
|
||||
}</programlisting></para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Интеграционные тесты клиентского уровня</title>
|
||||
<para>Интеграционные тесты на клиентском уровне реализуются с применением фреймворка <application>
|
||||
<ulink url="http://code.google.com/p/jmockit">JMockit</ulink>
|
||||
</application>. С его помощью тест изолируется от Middleware, а также создаются необходимые объекты инфраструктуры. </para>
|
||||
<para>Класс клиентского интеграционного теста должен быть унаследован от <code>CubaClientTestCase</code>. В методе <code>@Before</code> необходимо вызвать унаследованные методы <code>addEntityPackage()</code>, <code>setViewConfig()</code> и затем <code>setupInfrastructure()</code> для создания объектов <code>
|
||||
<link linkend="metadata">Metadata</link>
|
||||
</code> и <code>
|
||||
<link linkend="configuration">Configuration</link>
|
||||
</code> и развертывания метаданных по выбранным сущностям. Далее в методе <code>@Before</code> можно дополнить инфраструктуру необходимыми мок-объектами с помощью конструкции <code>Expectations</code> или <code>NonStrictExpectations</code>. </para>
|
||||
<para>Пример инициализирующего метода <code>@Before</code> одного из тестов платформы:<programlisting>@Before
|
||||
public void setUp() throws Exception {
|
||||
addEntityPackage("com.haulmont.cuba.security.entity");
|
||||
addEntityPackage("com.haulmont.cuba.core.entity");
|
||||
addEntityPackage("com.haulmont.cuba.gui.data.impl.testmodel1");
|
||||
setViewConfig("/com/haulmont/cuba/gui/data/impl/testmodel1/test-views.xml");
|
||||
setupInfrastructure();
|
||||
|
||||
metadataSession = metadata.getSession();
|
||||
dataService = new TestDataSupplier();
|
||||
|
||||
dataService.commitCount = 0;
|
||||
|
||||
new NonStrictExpectations() {
|
||||
@Mocked ClientConfig clientConfig;
|
||||
@Mocked PersistenceHelper persistenceHelper;
|
||||
{
|
||||
configuration.getConfig(ClientConfig.class); result = clientConfig;
|
||||
|
||||
clientConfig.getCollectionDatasourceDbSortEnabled(); result = true;
|
||||
|
||||
persistenceManager.getMaxFetchUI(anyString); result = 10000;
|
||||
|
||||
PersistenceHelper.isNew(any); result = false;
|
||||
}
|
||||
};
|
||||
}</programlisting></para>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
</chapter>
|
||||
|
@ -289,7 +289,7 @@
|
||||
<section>
|
||||
<title>Аннотации класса</title>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<varlistentry id="entity_annotation">
|
||||
<term>
|
||||
<code>
|
||||
<ulink url="http://docs.oracle.com/javaee/5/api/javax/persistence/Entity.html">@javax.persistence.Entity</ulink>
|
||||
@ -315,7 +315,7 @@
|
||||
<para>Определяет, что данный класс является предком некоторых сущностей, и его атрибуты должны быть использованы в составе сущностей-наследников. Такой класс не сопоставляется никакой отдельной таблице БД.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<varlistentry id="table_annotation">
|
||||
<term>
|
||||
<code>@<ulink url="http://docs.oracle.com/javaee/5/api/javax/persistence/Table.html">javax.persistence.Table</ulink></code>
|
||||
</term>
|
||||
@ -342,7 +342,7 @@
|
||||
</link>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<varlistentry id="inheritance_annotation">
|
||||
<term>
|
||||
<code>
|
||||
<ulink url="http://docs.oracle.com/javaee/5/api/javax/persistence/Inheritance.html">@javax.persistence.Inheritance</ulink>
|
||||
@ -1697,7 +1697,7 @@ msg://com.abc.sales.web.customer/someMessage</programlisting></para>
|
||||
<para><code>getMessageRef()</code> - формирует для <link linkend="metaProperty">мета-свойства</link> <link linkend="messageTools.loadString">ссылку на сообщение</link>, по которой можно получить локализованное название атрибута сущности</para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
<para>Для расширения набора вспомогательных методов в конкретном приложении бин <code>MessageTools</code> можно <link linkend="extension">переопределить</link>. Примеры работы с расширенным интерфейсом:<programlisting>MyMessageTools tools = messages.getTools();
|
||||
<para>Для расширения набора вспомогательных методов в конкретном приложении бин <code>MessageTools</code> можно <link linkend="bean_extension">переопределить</link>. Примеры работы с расширенным интерфейсом:<programlisting>MyMessageTools tools = messages.getTools();
|
||||
tools.foo();</programlisting><programlisting>((MyMessageTools) messages.getTools()).foo();</programlisting> </para>
|
||||
</section>
|
||||
</section>
|
||||
@ -1758,8 +1758,8 @@ tools.foo();</programlisting><programlisting>((MyMessageTools) messages.getTools
|
||||
</code>.</para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
<para><para>Для расширения набора вспомогательных методов в конкретном приложении бин <code>MetadataTools</code> можно <link linkend="extension">переопределить</link>. Примеры работы с расширенным интерфейсом:<programlisting>MyMetadataTools tools = metadata.getTools();
|
||||
tools.foo();</programlisting><programlisting>((MyMetadataTools) metadata.getTools()).foo();</programlisting></para></para>
|
||||
<para>Для расширения набора вспомогательных методов в конкретном приложении бин <code>MetadataTools</code> можно <link linkend="bean_extension">переопределить</link>. Примеры работы с расширенным интерфейсом:<programlisting>MyMetadataTools tools = metadata.getTools();
|
||||
tools.foo();</programlisting><programlisting>((MyMetadataTools) metadata.getTools()).foo();</programlisting></para>
|
||||
</section>
|
||||
</section>
|
||||
<section id="persistence">
|
||||
@ -1803,7 +1803,7 @@ if (persistence.getDbDialect() instanceof PostgresDbDialect)
|
||||
<para><code>getTools()</code> - возвращает экземпляр интерфейса <code>PersistenceTools</code> (см. ниже).</para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
<section>
|
||||
<section id="persistenceTools">
|
||||
<title>PersistenceTools</title>
|
||||
<para><link linkend="managed_beans">ManagedBean</link>, содержащий вспомогательные методы работы с хранилищем данных. Интерфейс <code>PersistenceTools</code> можно получить либо методом <code>Persistence.getTools()</code>, либо как любой другой бин - инжекцией или через класс <code>AppBeans</code>.</para>
|
||||
<para>Методы <code>PersistenceTools</code>:<itemizedlist>
|
||||
@ -1822,8 +1822,9 @@ if (persistence.getDbDialect() instanceof PostgresDbDialect)
|
||||
<listitem>
|
||||
<para><code>reloadEntity()</code> - перезагрузить экземпляр сущности с указанным <link linkend="views">представлением</link>. Данный метод должен вызываться внутри активной <link linkend="transactions">транзакции</link>.</para>
|
||||
</listitem>
|
||||
</itemizedlist><para><para>Для расширения набора вспомогательных методов в конкретном приложении бин <code>PersistenceTools</code> можно <link linkend="extension">переопределить</link>. Примеры работы с расширенным интерфейсом:<programlisting>MyPersistenceTools tools = persistence.getTools();
|
||||
tools.foo();</programlisting><programlisting>((MyPersistenceTools) persistence.getTools()).foo();</programlisting></para></para></para>
|
||||
</itemizedlist></para>
|
||||
<para>Для расширения набора вспомогательных методов в конкретном приложении бин <code>PersistenceTools</code> можно <link linkend="bean_extension">переопределить</link>. Примеры работы с расширенным интерфейсом:<programlisting>MyPersistenceTools tools = persistence.getTools();
|
||||
tools.foo();</programlisting><programlisting>((MyPersistenceTools) persistence.getTools()).foo();</programlisting></para>
|
||||
</section>
|
||||
<section id="persistenceHelper">
|
||||
<title>PersistenceHelper</title>
|
||||
@ -10477,10 +10478,175 @@ public void init(Map<String, Object> params) {
|
||||
</section>
|
||||
<section id="extension">
|
||||
<title>Расширение функциональности </title>
|
||||
<para>TODO</para>
|
||||
<para>Платформа позволяет расширять и переопределять свою функциональность в приложениях в следующих аспектах:
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>расширение набора атрибутов сущностей</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>расширение функциональности экранов</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>расширение и переопределение бизнес-логики, сосредоточенной в бинах <application>Spring</application> </para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
<para>Рассмотрим две первые задачи на примере добавления поля "Адрес" в сущность <code>User</code> подсистемы безопасности платформы. </para>
|
||||
<section id="entity_extension">
|
||||
<title>Расширение сущностей</title>
|
||||
<para>TODO</para>
|
||||
<title>Расширение сущности</title>
|
||||
<para>Создадим в проекте приложения класс сущности, унаследованный от <code>com.haulmont.cuba.security.entity.User</code> и добавим в него требуемый атрибут с соответствующими методами доступа: <programlisting>@Entity(name = "sales$User")
|
||||
@Extends(User.class)
|
||||
public class ExtUser extends User {
|
||||
|
||||
@Column(name = "ADDRESS", length = 100)
|
||||
private String address;
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
}</programlisting></para>
|
||||
<para>В аннотации <code>
|
||||
<link linkend="entity_annotation">@Entity</link>
|
||||
</code> должно быть указано новое имя сущности. Так как базовая сущность не объявляет <link linkend="inheritance_annotation">стратегию наследования</link>, то по умолчанию это <code>SINGLE_TABLE</code>. Это означает, что унаследованная сущность будет хранится в той же таблице, что и базовая, и аннотация <code>
|
||||
<link linkend="table_annotation">@Table</link>
|
||||
</code> не требуется. Другие аннотации базовой сущности - <code>
|
||||
<link linkend="namePattern">@NamePattern</link>
|
||||
</code>, <code>
|
||||
<link linkend="listeners_annotation">@Listeners</link>
|
||||
</code> и прочие - автоматически применяются к расширяющей сущности, но могут быть переопределены в ее классе. </para>
|
||||
<para>Важным элементом класса новой сущности является аннотация <code>@Extends</code> с базовым классом в качестве параметра. Она позволяет сформировать реестр расширяющих сущностей, и заставить механизмы платформы использовать их повсеместно вместо базовых. Реестр реализуется классом <code>ExtendedEntities</code>, который является бином <application>Spring</application> с именем <code>cuba_ExtendedEntities</code>, и доступен также через интерфейс <code>
|
||||
<link linkend="metadata">Metadata</link>
|
||||
</code>.</para>
|
||||
<para>Добавим локализованное название нового атрибута в <link linkend="message_packs">пакет</link> <code>com.sample.sales.entity</code>:</para>
|
||||
<para><filename>messages.properties</filename><programlisting>ExtUser.address=Address</programlisting></para>
|
||||
<para><filename>messages_ru.properties</filename><programlisting>ExtUser.address=Адрес</programlisting></para>
|
||||
<para>Зарегистрируем новую сущность в файле <filename>
|
||||
<link linkend="persistence.xml">persistence.xml</link>
|
||||
</filename> проекта:<programlisting><class>com.sample.sales.entity.ExtUser</class></programlisting></para>
|
||||
<para>Добавим в <link linkend="db_scripts">скрипты создания и обновления базы данных</link> команду модификации соответствующей таблицы:<programlisting>alter table SEC_USER add ADDRESS varchar(100)</programlisting></para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Расширение экранов</title>
|
||||
<para>Платформа позволяет создавать новые <link linkend="screen_xml">XML-дескрипторы</link> экранов путем наследования от существующих.</para>
|
||||
<para>Наследование XML выполняется путем указания в корневом элементе <sgmltag>window</sgmltag> атрибута <sgmltag>extends</sgmltag>, содержащего путь к базовому дескриптору.</para>
|
||||
<para>Правила переопределения элементов XML экрана:<itemizedlist>
|
||||
<listitem>
|
||||
<para>Если в расширяющем дескрипторе указан некоторый элемент, в базовом дескрипторе будет произведен поиск соответствующего элемента по следующему алгоритму:<itemizedlist>
|
||||
<listitem>
|
||||
<para>Если переопределяющий элемент - <sgmltag>view</sgmltag>, то ищется соответствующий элемент по атрибутам <sgmltag>name</sgmltag>, <sgmltag>class</sgmltag>, <sgmltag>entity</sgmltag>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Если переопределяющий элемент - <sgmltag>property</sgmltag>, то ищется соответствующий элемент по атрибуту <sgmltag>name</sgmltag>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>В других случаях, если в переопределяющем элементе указан атрибут <sgmltag>id</sgmltag>, ищется соответствующий элемент с таким же <sgmltag>id</sgmltag>. </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para role="bold">Если поиск дал результат, то найденный элемент <emphasis role="bold">переопределяется</emphasis>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Если поиск не дал результата, то определяется сколько в базовом дескрипторе элементов по данному пути и с данным именем. Если ровно один - он <emphasis role="bold">переопределяется</emphasis>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Если поиск не дал результата, и в базовом дескрипторе по данному пути с данным именем нет элементов либо их больше одного, <emphasis role="bold">добавляется</emphasis> новый элемент.</para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>В переопределяемом либо добавляемом элементе устанавливается текст из расширяющего элемента. </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>В переопределяемый либо добавляемый элемент копируются все атрибуты из расширяющего элемента. При совпадении имени атрибута значение берется из расширяющего элемента.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Добавление нового элемента по умолчанию производится в конец списка соседних элементов. Чтобы добавить новый элемент в начало или с произвольным индексом, необходимо выполнить следующее: <itemizedlist>
|
||||
<listitem>
|
||||
<para>определить в расширяющем дескрипторе дополнительный namespace: <code>xmlns:ext="http://schemas.haulmont.com/cuba/4.0/window-ext.xsd"</code></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>добавить в расширяющий элемент атрибут <sgmltag>ext:index</sgmltag> с желаемым индексом, например: <code>ext:index="0".</code></para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
<para>Для отладки преобразования дескрипторов можно включить вывод в журнал сервера результирующего XML. Делается это путем указания уровня <code>TRACE</code> для логгера <code>com.haulmont.cuba.gui.xml.XmlInheritanceProcessor</code> в файле конфигурации <application>Log4j</application>:<programlisting><appender name="FILE" ...
|
||||
<param name="Threshold" value="TRACE"/>
|
||||
...
|
||||
<category name="com.haulmont.cuba.gui.xml.XmlInheritanceProcessor">
|
||||
<priority value="TRACE"/>
|
||||
</category></programlisting></para>
|
||||
<para>Пример XML-дескриптора экрана браузера сущностей <code>ExtUser</code>:<programlisting><window xmlns="http://schemas.haulmont.com/cuba/4.0/window.xsd"
|
||||
xmlns:ext="http://schemas.haulmont.com/cuba/4.0/window-ext.xsd"
|
||||
extends="/com/haulmont/cuba/gui/app/security/user/browse/user-browse.xml">
|
||||
<layout>
|
||||
<groupTable id="usersTable">
|
||||
<columns>
|
||||
<column id="address" ext:index="2"/>
|
||||
</columns>
|
||||
</groupTable>
|
||||
</layout>
|
||||
</window></programlisting></para>
|
||||
<para>В данном примере дескриптор унаследован от стандартного браузера сущностей <code>User</code> платформы, и в таблицу добавлена колонка <code>address</code> с индексом <code>2</code>, т.е. отображающаяся после <code>login</code> и <code>name</code>.</para>
|
||||
<para>Зарегистрируем новый экран в <filename>
|
||||
<link linkend="screens.xml">screens.xml</link>
|
||||
</filename> с теми же идентификаторами, которые использовались для базового экрана. После этого новый экран будет повсеместно вызываться взамен старого.<programlisting><screen id="sec$User.browse"
|
||||
template="com/sample/sales/gui/extuser/extuser-browse.xml"/>
|
||||
<screen id="sec$User.lookup"
|
||||
template="com/sample/sales/gui/extuser/extuser-browse.xml"/></programlisting></para>
|
||||
<para>Аналогично создаем экран редактирования:<programlisting><window xmlns="http://schemas.haulmont.com/cuba/4.0/window.xsd"
|
||||
xmlns:ext="http://schemas.haulmont.com/cuba/4.0/window-ext.xsd"
|
||||
extends="/com/haulmont/cuba/gui/app/security/user/edit/user-edit.xml">
|
||||
<layout>
|
||||
<fieldGroup id="fieldGroup">
|
||||
<column id="fieldGroupColumn2">
|
||||
<field id="address" ext:index="4"/>
|
||||
</column>
|
||||
</fieldGroup>
|
||||
</layout>
|
||||
</window></programlisting></para>
|
||||
<para>Регистрируем его в <filename>screens.xml</filename> с идентификатором базового экрана:<programlisting><screen id="sec$User.edit"
|
||||
template="com/sample/sales/gui/extuser/extuser-edit.xml"/></programlisting></para>
|
||||
<para>После выполнения описанных выше действий в приложении вместо платформенной сущности <code>User</code> будет использоваться <code>ExtUser</code> с соответствующими экранами.</para>
|
||||
<para>Контроллер экрана может быть расширен путем создания нового класса, унаследованного от контроллера базового экрана. Имя класса указывается в атрибуте <sgmltag>class</sgmltag> корневого элемента расширяющего XML дескриптора, при этом выполняются обычные правила наследования XML, описанные выше. </para>
|
||||
</section>
|
||||
<section id="bean_extension">
|
||||
<title>Расширение бизнес-логики</title>
|
||||
<para>Основная часть бизнес-логики платформы сосредоточена в бинах <application>Spring</application>, что позволяет легко расширить или переопределить ее в приложении.</para>
|
||||
<para>Для подмены реализации бина достаточно создать свой класс, реализующий интерфейс или расширяющий базовый класс платформы, и зарегистрировать его в <link linkend="spring.xml">
|
||||
<filename>spring.xml</filename>
|
||||
</link> приложения. Аннотацию <code>@ManagedBean</code> в расширяющем классе применять нельзя, переопределение бинов возможно только с помощью конфигурации в XML.</para>
|
||||
<para>Рассмотрим пример добавления метода в бин <link linkend="persistenceTools">
|
||||
<code>PersistenceTools</code>
|
||||
</link>.</para>
|
||||
<para>Создаем класс с нужным методом:<programlisting>public class ExtPersistenceTools extends PersistenceTools {
|
||||
|
||||
public Entity reloadStartingTransaction(Entity entity, String... viewNames) {
|
||||
Transaction tx = persistence.createTransaction();
|
||||
try {
|
||||
Entity reloadedEntity = reloadEntity(entity, viewNames);
|
||||
tx.commit();
|
||||
return reloadedEntity;
|
||||
} finally {
|
||||
tx.end();
|
||||
}
|
||||
}
|
||||
}</programlisting></para>
|
||||
<para>Регистрируем класс в <filename>spring.xml</filename> модуля <structname>core</structname> проекта с тем же идентификатором, что и бин платформы:<programlisting><bean id="cuba_PersistenceTools" class="com.sample.sales.core.ExtPersistenceTools"/></programlisting></para>
|
||||
<para>После этого контекст <application>Spring</application> вместо экземпляра базового класса <code>PersistenceTools</code> будет всегда возвращать <code>ExtPersistenceTools</code>, например:<programlisting>Persistence persistence;
|
||||
PersistenceTools tools;
|
||||
|
||||
persistence = AppBeans.get(Persistence.class);
|
||||
tools = persistence.getTools();
|
||||
assertTrue(tools instanceof ExtPersistenceTools);
|
||||
|
||||
tools = AppBeans.get(PersistenceTools.class);
|
||||
assertTrue(tools instanceof ExtPersistenceTools);
|
||||
|
||||
tools = AppBeans.get(PersistenceTools.NAME);
|
||||
assertTrue(tools instanceof ExtPersistenceTools);</programlisting></para>
|
||||
</section>
|
||||
</section>
|
||||
</chapter>
|
||||
|
@ -61,9 +61,6 @@
|
||||
<orderedlist>
|
||||
<listitem>
|
||||
<para>Создайте рабочую папку для проекта, например, <filename>c:/work/sales</filename>.</para>
|
||||
<warning>
|
||||
<para>Путь не должен содержать пробелов!</para>
|
||||
</warning>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Загрузите архив с шаблоном проекта <ulink url="http://docs.haulmont.com/cuba/4.0/samples/sales.zip">http://docs.haulmont.com/cuba/4.0/samples/sales.zip</ulink> и распакуйте содержимое архива в рабочий каталог таким образом, чтобы в каталоге <filename>c:/work/sales</filename> появился подкаталог <filename>modules</filename> и файлы <filename>build.gradle</filename> и <filename>settings.gradle</filename></para>
|
||||
@ -390,7 +387,7 @@ menu-config.sales$Customer.lookup=Покупатели</programlisting>
|
||||
</section>
|
||||
<section>
|
||||
<title>Создание приложения в CUBA Studio</title>
|
||||
<section>
|
||||
<section id="qs_studio_setup">
|
||||
<title>Создание проекта</title>
|
||||
<orderedlist>
|
||||
<listitem>
|
||||
|
@ -112,7 +112,7 @@ h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visit
|
||||
*/
|
||||
|
||||
.book .toc > dl > dt {
|
||||
font-weight:normal;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@ -120,19 +120,11 @@ h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visit
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.toc p{
|
||||
.toc p {
|
||||
font-size: 1.8em;
|
||||
color: #0b6ec7;
|
||||
}
|
||||
|
||||
.toc > dl > dt {
|
||||
margin-top: 1.2em;
|
||||
}
|
||||
|
||||
.toc > dl > dd > dl > dt {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
/*
|
||||
* Docbook content
|
||||
*/
|
||||
@ -503,4 +495,54 @@ table .footnote {
|
||||
.errorname {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
/* ToC panel */
|
||||
|
||||
a.toc-btn{
|
||||
position: fixed;
|
||||
background-color: #404040;
|
||||
opacity:0.5;
|
||||
filter:alpha(opacity=50);
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
letter-spacing: -1px;
|
||||
font-family: verdana, helvetica, arial, sans-serif;
|
||||
color: #fff;
|
||||
padding: 4px 12px 6px 12px;
|
||||
font-weight: bold;
|
||||
z-index: 2;
|
||||
right: 0;
|
||||
top: 45%;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-top-left-radius: 5px;
|
||||
}
|
||||
|
||||
a.toc-btn:hover {
|
||||
opacity: 1.0;
|
||||
filter:alpha(opacity=100);
|
||||
}
|
||||
|
||||
a.toc-btn.active {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toc-panel {
|
||||
position: fixed;
|
||||
display: none;
|
||||
background-color: #404040;
|
||||
color: #c0c0c0;
|
||||
width: 450px;
|
||||
height: 85%;
|
||||
z-index:1;
|
||||
overflow: auto;
|
||||
right: 0;
|
||||
top: 5%;
|
||||
padding: 20px 20px 20px 20px;
|
||||
border-bottom-left-radius: 15px;
|
||||
border-top-left-radius: 15px;
|
||||
}
|
||||
|
||||
div#toc-panel a, div#toc-panel p {
|
||||
color: #c0c0c0;
|
||||
}
|
||||
|
@ -13,5 +13,44 @@
|
||||
<xsl:template match="figure" mode="label.markup">
|
||||
<xsl:number format="1" from="book" level="any" />
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<xsl:variable name="headercode">
|
||||
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
|
||||
$('div.book').add('<a href="#" id="toc-btn" class="toc-btn">. . .</a>').appendTo(document.body);
|
||||
$('div.book').add('<div id="toc-panel" class="toc-panel"></div>').appendTo(document.body);
|
||||
|
||||
$('div.toc').clone().appendTo('#toc-panel');
|
||||
|
||||
var panel = $('#toc-panel');
|
||||
var button = $('#toc-btn');
|
||||
|
||||
button.attr( 'href', 'javascript:void(0)' ).mousedown(function() {
|
||||
panel.toggle('fast');
|
||||
button.toggleClass('active');
|
||||
return false;
|
||||
});
|
||||
|
||||
$(document).bind('mousedown', function() {
|
||||
panel.hide('fast');
|
||||
button.removeClass('active');
|
||||
});
|
||||
|
||||
panel.bind('mousedown', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</xsl:variable>
|
||||
|
||||
<xsl:template name="user.header.content">
|
||||
<xsl:copy-of select="$headercode"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:output method="html"
|
||||
encoding="UTF-8"
|
||||
indent="yes"/>
|
||||
|
||||
</xsl:stylesheet>
|
@ -74,4 +74,8 @@
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:output method="html"
|
||||
encoding="UTF-8"
|
||||
indent="yes"/>
|
||||
|
||||
</xsl:stylesheet>
|
@ -155,6 +155,8 @@ public class FoldersServiceBean implements FoldersService {
|
||||
protected void loadFolderQuantity(Binding binding, AppFolder folder) {
|
||||
try {
|
||||
if (!StringUtils.isBlank(folder.getQuantityScript())) {
|
||||
binding.setVariable("persistence", persistence);
|
||||
binding.setVariable("metadata", metadata);
|
||||
String variable = "style";
|
||||
binding.setVariable("folder", folder);
|
||||
binding.setVariable(variable, null);
|
||||
|
@ -259,6 +259,7 @@ class MappingFileCreator {
|
||||
private String xml;
|
||||
|
||||
private Type(int order, String xml) {
|
||||
this.order = order;
|
||||
this.xml = xml;
|
||||
}
|
||||
|
||||
|
12
modules/gui/src/com/haulmont/cuba/gui/window-ext.xsd
Normal file
12
modules/gui/src/com/haulmont/cuba/gui/window-ext.xsd
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema
|
||||
targetNamespace="http://schemas.haulmont.com/cuba/4.0/window-ext.xsd"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.haulmont.com/cuba/4.0/window-ext.xsd"
|
||||
elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified"
|
||||
>
|
||||
|
||||
<xs:attribute name="index" type="xs:int"/>
|
||||
|
||||
</xs:schema>
|
@ -153,10 +153,12 @@
|
||||
|
||||
<xs:attributeGroup name="hasId">
|
||||
<xs:attribute name="id" type="xs:string" use="optional"/>
|
||||
<xs:anyAttribute namespace="##other" processContents="lax"/>
|
||||
</xs:attributeGroup>
|
||||
|
||||
<xs:attributeGroup name="requiresId">
|
||||
<xs:attribute name="id" type="xs:string" use="required"/>
|
||||
<xs:anyAttribute namespace="##other" processContents="lax"/>
|
||||
</xs:attributeGroup>
|
||||
|
||||
<xs:attributeGroup name="hasSize">
|
||||
@ -847,6 +849,7 @@
|
||||
<xs:sequence>
|
||||
<xs:element name="field" minOccurs="1" maxOccurs="unbounded" type="fieldGroupField"/>
|
||||
</xs:sequence>
|
||||
<xs:attributeGroup ref="hasId"/>
|
||||
<xs:attribute name="width" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="flex" type="xs:string" use="optional"/>
|
||||
</xs:complexType>
|
||||
|
@ -1,17 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2010 Haulmont Technology Ltd. All Rights Reserved.
|
||||
* Copyright (c) 2013 Haulmont Technology Ltd. All Rights Reserved.
|
||||
* Haulmont Technology proprietary and confidential.
|
||||
* Use is subject to license terms.
|
||||
|
||||
* Author: Konstantin Krivopustov
|
||||
* Created: 04.03.2010 11:11:58
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
package com.haulmont.cuba.gui.xml;
|
||||
|
||||
import com.haulmont.bali.util.Dom4j;
|
||||
import com.haulmont.cuba.core.global.ScriptingProvider;
|
||||
import com.haulmont.cuba.core.global.AppBeans;
|
||||
import com.haulmont.cuba.core.global.Resources;
|
||||
import com.haulmont.cuba.gui.xml.layout.LayoutLoader;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
@ -23,6 +19,12 @@ import java.io.InputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Provides inheritance of screen XML descriptors.
|
||||
*
|
||||
* @author krivopustov
|
||||
* @version $Id$
|
||||
*/
|
||||
public class XmlInheritanceProcessor {
|
||||
|
||||
private static Log log = LogFactory.getLog(XmlInheritanceProcessor.class);
|
||||
@ -31,13 +33,15 @@ public class XmlInheritanceProcessor {
|
||||
private Namespace extNs;
|
||||
private Map<String, Object> params;
|
||||
|
||||
private List<ElementTargetLocator> targetLocators = new ArrayList<ElementTargetLocator>();
|
||||
private List<ElementTargetLocator> targetLocators = new ArrayList<>();
|
||||
|
||||
protected Resources resources = AppBeans.get(Resources.class);
|
||||
|
||||
public XmlInheritanceProcessor(Document document, Map<String, Object> params) {
|
||||
this.document = document;
|
||||
this.params = params;
|
||||
|
||||
extNs = new Namespace("ext", "http://www.haulmont.com/schema/cuba/gui/window-ext.xsd");
|
||||
extNs = document.getRootElement().getNamespaceForPrefix("ext");
|
||||
|
||||
targetLocators.add(new ViewPropertyElementTargetLocator());
|
||||
targetLocators.add(new ViewElementTargetLocator());
|
||||
@ -51,7 +55,7 @@ public class XmlInheritanceProcessor {
|
||||
Element root = document.getRootElement();
|
||||
String ancestorTemplate = root.attributeValue("extends");
|
||||
if (!StringUtils.isBlank(ancestorTemplate)) {
|
||||
InputStream ancestorStream = ScriptingProvider.getResourceAsStream(ancestorTemplate);
|
||||
InputStream ancestorStream = resources.getResourceAsStream(ancestorTemplate);
|
||||
if (ancestorStream == null) {
|
||||
ancestorStream = getClass().getResourceAsStream(ancestorTemplate);
|
||||
if (ancestorStream == null) {
|
||||
|
Loading…
Reference in New Issue
Block a user