Merge from trunk 10656

This commit is contained in:
Yuriy Artamonov 2013-03-28 13:53:01 +00:00
parent 32bbf4d4ee
commit 345b2185a5
12 changed files with 813 additions and 54 deletions

View File

@ -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> -&gt; <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> будет иметь значение &quot;Unknown JMX interface&quot;. Значение данного поля можно произвольно изменять. </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 &quot;domain_name\user_name&quot;: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=&quot;$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&quot;
CATALINA_OPTS=&quot;$CATALINA_OPTS -Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password -Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access&quot;</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>

View File

@ -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: &apos;cuba&apos;)</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 = &quot;${globalModule.projectDir}/src/persistence.xml&quot;
metadataXml = &quot;${globalModule.projectDir}/src/metadata.xml&quot;
}</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 = &apos;app-core&apos;
jarNames = [&apos;cuba-global&apos;, &apos;cuba-core&apos;, &apos;app-global&apos;, &apos;app-core&apos;]
}</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 = &apos;app-core&apos;
}</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: &apos;Creates local database&apos;, type: CubaDbCreation) {
dbms = &apos;postgres&apos;
dbName = &apos;sales&apos;
dbUser = &apos;cuba&apos;
dbPassword = &apos;cuba&apos;
}</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>. Пример рассмотрен в главе &quot;Быстрый старт&quot; данного руководства: <xref linkend="qs_studio_setup"/></para>
<para>Если применение <application>CUBA Studio</application> невозможно, для создания нового проекта необходимо выполнить следующее:<itemizedlist>
<listitem>
<para>Загрузить шаблон проекта, описанного в <xref linkend="qs_setup"/> главы &quot;Быстрый старт&quot;, и распаковать его в локальный каталог.</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 = &apos;bookstore&apos;</programlisting></para>
<para>Если модуль <structname>portal</structname> не нужен, удалить из объявления <code>include</code> элемент <code>&apos;:app-portal&apos;</code>, и удалить соответствующее объявление <code>project(&apos;:app-portal&apos;).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 = &apos;com.sample.bookstore&apos;
...</programlisting></para>
</listitem>
<listitem>
<para>В секции конфигурации модуля <structname>core</structname> указать для задач <code>createDb</code> и <code>updateDb</code> желаемое имя базы данных. Например:<programlisting>configure(coreModule) {
...
task createDb(dependsOn: assemble, description: &apos;Creates local database&apos;, type: CubaDbCreation) {
dbms = &apos;postgres&apos;
dbName = &apos;bookstore&apos;
dbUser = &apos;cuba&apos;
dbPassword = &apos;cuba&apos;
}
task updateDb(dependsOn: assemble, description: &apos;Updates local database&apos;, type: CubaDbUpdate) {
dbms = &apos;postgres&apos;
dbName = &apos;bookstore&apos;
dbUser = &apos;cuba&apos;
dbPassword = &apos;cuba&apos;
}
...</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>&lt;Context&gt;
&lt;!-- PostgreSQL connection --&gt;
&lt;Resource
...
username=&quot;cuba&quot;
password=&quot;cuba&quot;
url=&quot;jdbc:postgresql://localhost/bookstore&quot;/&gt;
...</programlisting></para>
<para>В файле <filename>
<link linkend="spring.xml">spring.xml</link>
</filename> изменить базовый пакет поиска аннотированных бинов:<programlisting>&lt;context:component-scan base-package=&quot;com.sample.bookstore&quot;/&gt;</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>&lt;servlet&gt;
&lt;servlet-name&gt;app_servlet&lt;/servlet-name&gt;
&lt;servlet-class&gt;com.haulmont.cuba.web.sys.CubaApplicationServlet&lt;/servlet-class&gt;
&lt;init-param&gt;
&lt;param-name&gt;application&lt;/param-name&gt;
&lt;param-value&gt;com.sample.bookstore.web.App&lt;/param-value&gt;
&lt;/init-param&gt;
...</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(&quot;itemsTable.add&quot;)
protected AddAction addAction;
@Override
public void init(Map&lt;String, Object&gt; params) {
addAction.setWindowId(&quot;sales$Product.lookup&quot;);
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.&lt;String, Object&gt;emptyMap());
editor.setItem(new Order());
new Verifications() {
{
addAction.setWindowId(&quot;sales$Product.lookup&quot;);
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(&quot;org.postgresql.Driver&quot;);
TestDataSource ds = new TestDataSource(&quot;jdbc:postgresql://localhost/sales_test&quot;, &quot;cuba&quot;, &quot;cuba&quot;);
TestContext.getInstance().bind(&quot;java:comp/env/jdbc/CubaDS&quot;, ds);
}
@Override
protected List&lt;String&gt; getTestAppProperties() {
String[] files = {
&quot;cuba-app.properties&quot;,
&quot;app.properties&quot;,
&quot;test-app.properties&quot;,
};
return Arrays.asList(files);
}
}</programlisting></para>
<para>В качестве базы данных рекомендуется использовать отдельную тестовую БД, которую можно создавать, например, следующей задачей в <filename>build.gradle</filename>: <programlisting>configure(coreModule) {
...
task createTestDb(dependsOn: assemble, description: &apos;Creates local Postgres database for tests&apos;, type: CubaDbCreation) {
dbms = &apos;postgres&apos;
dbName = &apos;sales_test&apos;
dbUser = &apos;cuba&apos;
dbPassword = &apos;cuba&apos;
}</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(&quot;testCustomer&quot;);
em.persist(customer);
}
});
}
@Override
public void tearDown() throws Exception {
deleteRecord(&quot;SALES_CUSTOMER&quot;, customerId);
super.tearDown();
}
public void test() {
Transaction tx = persistence.createTransaction();
try {
EntityManager em = persistence.getEntityManager();
TypedQuery&lt;Customer&gt; query = em.createQuery(
&quot;select c from sales$Customer c&quot;, Customer.class);
List&lt;Customer&gt; list = query.getResultList();
tx.commit();
assertTrue(list.size() &gt; 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(&quot;com.haulmont.cuba.security.entity&quot;);
addEntityPackage(&quot;com.haulmont.cuba.core.entity&quot;);
addEntityPackage(&quot;com.haulmont.cuba.gui.data.impl.testmodel1&quot;);
setViewConfig(&quot;/com/haulmont/cuba/gui/data/impl/testmodel1/test-views.xml&quot;);
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>

View File

@ -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&lt;String, Object&gt; 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>Рассмотрим две первые задачи на примере добавления поля &quot;Адрес&quot; в сущность <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 = &quot;sales$User&quot;)
@Extends(User.class)
public class ExtUser extends User {
@Column(name = &quot;ADDRESS&quot;, 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>&lt;class&gt;com.sample.sales.entity.ExtUser&lt;/class&gt;</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=&quot;http://schemas.haulmont.com/cuba/4.0/window-ext.xsd&quot;</code></para>
</listitem>
<listitem>
<para>добавить в расширяющий элемент атрибут <sgmltag>ext:index</sgmltag> с желаемым индексом, например: <code>ext:index=&quot;0&quot;.</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>&lt;appender name=&quot;FILE&quot; ...
&lt;param name=&quot;Threshold&quot; value=&quot;TRACE&quot;/&gt;
...
&lt;category name=&quot;com.haulmont.cuba.gui.xml.XmlInheritanceProcessor&quot;&gt;
&lt;priority value=&quot;TRACE&quot;/&gt;
&lt;/category&gt;</programlisting></para>
<para>Пример XML-дескриптора экрана браузера сущностей <code>ExtUser</code>:<programlisting>&lt;window xmlns=&quot;http://schemas.haulmont.com/cuba/4.0/window.xsd&quot;
xmlns:ext=&quot;http://schemas.haulmont.com/cuba/4.0/window-ext.xsd&quot;
extends=&quot;/com/haulmont/cuba/gui/app/security/user/browse/user-browse.xml&quot;&gt;
&lt;layout&gt;
&lt;groupTable id=&quot;usersTable&quot;&gt;
&lt;columns&gt;
&lt;column id=&quot;address&quot; ext:index=&quot;2&quot;/&gt;
&lt;/columns&gt;
&lt;/groupTable&gt;
&lt;/layout&gt;
&lt;/window&gt;</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>&lt;screen id=&quot;sec$User.browse&quot;
template=&quot;com/sample/sales/gui/extuser/extuser-browse.xml&quot;/&gt;
&lt;screen id=&quot;sec$User.lookup&quot;
template=&quot;com/sample/sales/gui/extuser/extuser-browse.xml&quot;/&gt;</programlisting></para>
<para>Аналогично создаем экран редактирования:<programlisting>&lt;window xmlns=&quot;http://schemas.haulmont.com/cuba/4.0/window.xsd&quot;
xmlns:ext=&quot;http://schemas.haulmont.com/cuba/4.0/window-ext.xsd&quot;
extends=&quot;/com/haulmont/cuba/gui/app/security/user/edit/user-edit.xml&quot;&gt;
&lt;layout&gt;
&lt;fieldGroup id=&quot;fieldGroup&quot;&gt;
&lt;column id=&quot;fieldGroupColumn2&quot;&gt;
&lt;field id=&quot;address&quot; ext:index=&quot;4&quot;/&gt;
&lt;/column&gt;
&lt;/fieldGroup&gt;
&lt;/layout&gt;
&lt;/window&gt;</programlisting></para>
<para>Регистрируем его в <filename>screens.xml</filename> с идентификатором базового экрана:<programlisting>&lt;screen id=&quot;sec$User.edit&quot;
template=&quot;com/sample/sales/gui/extuser/extuser-edit.xml&quot;/&gt;</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>&lt;bean id=&quot;cuba_PersistenceTools&quot; class=&quot;com.sample.sales.core.ExtPersistenceTools&quot;/&gt;</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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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>

View File

@ -74,4 +74,8 @@
</div>
</xsl:template>
<xsl:output method="html"
encoding="UTF-8"
indent="yes"/>
</xsl:stylesheet>

View File

@ -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);

View File

@ -259,6 +259,7 @@ class MappingFileCreator {
private String xml;
private Type(int order, String xml) {
this.order = order;
this.xml = xml;
}

View 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>

View File

@ -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>

View File

@ -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) {