From 345b2185a51934ce9632d98faccdf4a00c992e74 Mon Sep 17 00:00:00 2001 From: Yuriy Artamonov Date: Thu, 28 Mar 2013 13:53:01 +0000 Subject: [PATCH] Merge from trunk 10656 --- doc/content/manual/ru/chapter_deployment.xml | 111 ++++- doc/content/manual/ru/chapter_development.xml | 410 +++++++++++++++++- doc/content/manual/ru/chapter_framework.xml | 190 +++++++- doc/content/manual/ru/chapter_quick_start.xml | 5 +- doc/tools/css/style.css | 64 ++- doc/tools/xsl/html-single.xsl | 41 +- doc/tools/xsl/html.xsl | 4 + .../cuba/core/app/FoldersServiceBean.java | 2 + .../sys/persistence/MappingFileCreator.java | 1 + .../src/com/haulmont/cuba/gui/window-ext.xsd | 12 + .../gui/src/com/haulmont/cuba/gui/window.xsd | 3 + .../cuba/gui/xml/XmlInheritanceProcessor.java | 24 +- 12 files changed, 813 insertions(+), 54 deletions(-) create mode 100644 modules/gui/src/com/haulmont/cuba/gui/window-ext.xsd diff --git a/doc/content/manual/ru/chapter_deployment.xml b/doc/content/manual/ru/chapter_deployment.xml index 3671b4d38c..6f8b3175c9 100644 --- a/doc/content/manual/ru/chapter_deployment.xml +++ b/doc/content/manual/ru/chapter_deployment.xml @@ -90,13 +90,110 @@ . В стандартном варианте развертывания в Tomcat это подкаталог WEB-INF/db каталога веб-приложения среднего слоя, например tomcat/webapps/app-core/WEB-INF/db. -
- Варианты запуска Tomcat - TODO -
Использование инструментов JMX - TODO +
+ Встроенная JMX консоль + Модуль Web Client базового проекта cuba платформы содержит средство просмотра и редактирования JMX объектов. Точкой входа в этот инструмент является экран com/haulmont/cuba/web/app/ui/jmxcontrol/browse/display-mbeans.xml, зарегистрированный под идентификатором jmxcontrol$DisplayMbeans и в стандартном меню доступный через пункт Администрирование -> Консоль JMX. + Без дополнительной настройки консоль отображает все JMX объекты, зарегистрированные в JVM, на которой работает блок Web Client, к которому в данный момент подключен пользователь. Соответственно, в простейшем случае развертывания всех блоков приложения в одном экземпляре веб-контейнера консоль имеет доступ к JMX бинам всех уровней, а также к JMX объектам самой JVM и веб-контейнера. + Имена бинов приложения имеют префикс, соответсвующий имени веб-приложения, их содержащего. Например, бин app-core.cuba:type=CachingFacade загружен веб-приложением app-core, реализующим блок Middleware, а бин app.cuba:type=CachingFacade загружен веб-приложением app, реализующим блок Web Client. + Консоль JMX может также работать с JMX объектами произвольной удаленной JVM. Это актуально в случае развертывания блоков приложения на нескольких экземплярах веб-контейнера, например отдельно Web Client и Middleware. + Для подключения к удаленной JVM необходимо в поле Соединение JMX консоли выбрать созданное ранее соединение, либо вызвать экран создания нового соединения: +
+ Редактирование JMX соединения + + + + + +
+ Для соединения указывается JMX хост и порт, логин и пароль. Имеется также поле Имя узла, которое заполняется автоматически, если по указанному адресу обнаружен какой-либо блок CUBA-приложения. В этом случае значением этого поля становится комбинация свойств + cuba.webHostName + и + cuba.webPort + данного блока, что позволяет идентифицировать содержащий его сервер. Если подключение произведено к постороннему JMX интерфейсу, то поле Имя узла будет иметь значение "Unknown JMX interface". Значение данного поля можно произвольно изменять. + Для подключения удаленной JVM она должна быть соответствующим образом настроена - см. ниже. +
+
+ Настройка удаленного доступа к JMX + В данном разделе рассматривается настройка запуска сервера Tomcat, необходимая для удаленного подключения к нему инструментов JMX. +
+ Tomcat JMX под Windows + + + Отредактировать файл bin/setenv.bat следующим образом: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 + Здесь в параметре java.rmi.server.hostname необходимо указать реальный IP адрес или DNS имя компьютера, на котором запущен сервер, в параметре com.sun.management.jmxremote.port - порт для подключения инструментов JMX. + + + Отредактировать файл conf/jmxremote.access. Он должен содержать имена пользователей, которые будут подключаться к JMX, и их уровень доступа. Например:admin readwrite + + + Отредактировать файл conf/jmxremote.password. Он должен содержать пароли пользователей JMX, например:admin admin + + + Файл паролей должен иметь разрешение на чтение только для пользователя, от имени которого работает сервер Tomcat. Настроить права можно следующим образом: + + Открыть командную строку и перейти в каталог conf. + + + Выполнить команду: + cacls jmxremote.password /P "domain_name\user_name":R + + + где domain_name\user_name - домен и имя пользователя. + + + После выполнения данной команды файл в Проводнике будет отмечен изображением замка. + + + + + Если Tomcat установлен как служба Windows, то для службы должен быть задан вход в систему с учетной записью, имеющей права на файл jmxremote.password. Кроме того, следует иметь в виду, что в этом случае файл bin/setenv.bat не используется, и соответствующие параметры запуска JVM должны быть заданы в приложении, настраивающем службу. + + +
+
+ Tomcat JMX под Linux + + + Отредактировать файл bin/setenv.sh следующим образом: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" + Здесь в параметре java.rmi.server.hostname необходимо указать реальный IP адрес или DNS имя компьютера, на котором запущен сервер, в параметре com.sun.management.jmxremote.port - порт для подключения инструментов JMX. + + + Отредактировать файл conf/jmxremote.access. Он должен содержать имена пользователей, которые будут подключаться к JMX, и их уровень доступа. Например:admin readwrite + + + Отредактировать файл conf/jmxremote.password. Он должен содержать пароли пользователей JMX, например:admin admin + + + Файл паролей должен иметь разрешение на чтение только для пользователя, от имени которого работает сервер Tomcat. Настроить права для текущего пользователя можно следующим образом: + + Открыть командную строку и перейти в каталог conf. + + + Выполнить команду: + chmod go-rwx jmxremote.password + + + + + + +
+
Отказоустойчивость и балансировка нагрузки @@ -115,8 +212,4 @@ .
-
- Мониторинг работы приложения - TODO -
diff --git a/doc/content/manual/ru/chapter_development.xml b/doc/content/manual/ru/chapter_development.xml index 765264adcc..9ba682d3b1 100644 --- a/doc/content/manual/ru/chapter_development.xml +++ b/doc/content/manual/ru/chapter_development.xml @@ -210,18 +210,414 @@
Описание скриптов сборки - TODO + Для сборки проектов на основе платформы используется + Gradle + . Скрипты сборки представляют собой два файла в корневом каталоге проекта: + + settings.gradle - задает название и состав модулей проекта + + + build.gradle - определяет конфигурацию сборки. + + +
+ Структура build.gradle + Секция allprojects задает группу и версию собираемых артефактов проекта. Имена артефактов формируются на основе имен модулей, заданных в settings.gradle. Если свойство ext.isSnapshot равно true, то в именах артефактов будет присутствовать суффикс SNAPSHOT. Свойство ext.tomcatDir задает расположение каталога установки Tomcat. + Основная часть секции buildscript - объявить зависимость от плагина cuba-plugin, в котором сосредоточена специфика сборки проектов на платформе. Плагин подключается далее в конфигурацию сборки модулей с помощью методаapply(plugin: 'cuba') + Кроме того, секция buildscript задает параметры подключения к репозиторию артефактов, из которого будут загружены зависимости. Имя и пароль для подключения могут быть как заданы явно, так и получены из переменных окружения - как правило это переменные HAULMONT_REPOSITORY_USER и HAULMONT_REPOSITORY_PASSWORD. + В этой секции также могут быть объявлены свойства ext.cubaVersion и ext.cubaPluginVersion, задающие версию базовых проектов и плагина сборки. Если эти переменные отсутствуют, то версии задаются непосредственно при объявлении зависимостей. + Далее в секциях configure определяются параметры сборки модулей приложения. + Исполняемыми единицами в Gradle являются задачи (tasks). Они задаются как внутри плагинов, так и в самом скрипте сборки. Рассмотрим задачи, параметры которых могут быть сконфигурированы в build.gradle. + + enhance - задача типа CubaEnhancing, выполняющая bytecode enhancement классов сущностей. Объявляется в модуле global. Параметры: + + persistenceXml - путь к файлу + persistence.xml + проекта. + + + metadataXml - путь к файлу + metadata.xml + проекта. + + + Например:task enhance(type: CubaEnhancing) { + persistenceXml = "${globalModule.projectDir}/src/persistence.xml" + metadataXml = "${globalModule.projectDir}/src/metadata.xml" +} + + + deploy - задача типа CubaDeployment, выполняющая развертывание модуля в Tomcat. Объявляется в модулях core, web, portal. Параметры: + + appName - имя веб-приложения, которое будет создано из модуля. Фактически это имя подкаталога внутри tomcat/webapps. + + + jarNames - список имен JAR файлов (без версии), получающихся в результате сборки модуля, которые надо поместить в каталог WEB-INF/lib веб-приложения. Все остальные артефакты модуля и зависимостей будут записаны в tomcat/shared/lib. + + + Например:task deploy(dependsOn: assemble, type: CubaDeployment) { + appName = 'app-core' + jarNames = ['cuba-global', 'cuba-core', 'app-global', 'app-core'] +} + + + buildWar - задача типа CubaWarBuilding, выполняющая сборку модуля в WAR файл. Может быть объявлена в модулях core, web, portal, если требуется развертывание приложения в контейнер, отличный от стандартного Tomcat. + Параметр appName - имя результирующего WAR. + Например:task buildWar(dependsOn: assemble, type: CubaWarBuilding) { + appName = 'app-core' +} + + + createDb - задача типа CubaDbCreation, выполняющая создание базы данных приложения и выполнение соответствующих скриптов. Объявляется в модуле core. Параметры: + + dbms - тип СУБД, задается строкой postgres или mssql. + + + dbName - имя базы данных. + + + dbUser - имя пользователя СУБД. + + + dbPassword - пароль пользователя СУБД. + + + host - имя хоста СУБД. Если не задан, используется localhost. + + + dropDbSql - команда SQL для удаления БД. Если не задана, используется значение по умолчанию, зависящее от типа СУБД. + + + createDbSql - команда SQL для создания БД. Если не задана, используется значение по умолчанию, зависящее от типа СУБД. + + + Например:task createDb(dependsOn: assemble, description: 'Creates local database', type: CubaDbCreation) { + dbms = 'postgres' + dbName = 'sales' + dbUser = 'cuba' + dbPassword = 'cuba' +} + + + updateDb - задача типа CubaDbUpdate, выполняющая обновление базы данных приложения путем выполнения соответствующих скриптов. Аналогична задаче createDb, за исключением отсутствия параметров dropDbSql и createDbSql. + + +
+
+ Типичные задачи сборки + Задачи (tasks) Gradle запускаются из командной строки и выполняют различные действия по сборке проекта. Например, чтобы выполнить компиляцию Java файлов и сборку JAR файлов артефактов проекта, необходимо запустить следующую команду: + gradle assemble + Для ускорения повторной сборки желательно вызывать Gradle в режиме демона (его процесс будет оставаться в памяти): + gradle --daemon assemble + Для удаления демона из памяти используется следующая команда: + gradle --stop + Рассмотрим типичные задачи сборки в обычном порядке их использования. + + + idea - создать проектные файлы IntelliJ IDEA. При выполнении этой задачи из репозитория артефактов в локальный кэш Gradle загружаются зависимости вместе со своими исходными кодами. + + + cleanIdea - удалить проектные файлы IntelliJ IDEA. + + + assemble - выполнить компиляцию Java файлов и сборку JAR файлов артефактов проекта в подкаталогах build модулей. + + + clean - удалить подкаталоги build всех модулей проекта. + + + setupTomcat - установить сервер Tomcat в путь, заданный свойством ext.tomcatDir скрипта build.gradle. Если данное свойство не задано, установка производится в каталог tomcat на одном уровне с каталогом проекта, т.е. в ${rootDir}/../tomcat. + Версия Tomcat задается в плагине сборки cuba-plugin. В процессе установки конфигурация Tomcat модифицируется для поддержки стандартного механизма развертывания приложений платформы. В частности, добавляются командные файлы setenv.bat и setenv.sh, а также конфигурационный файл журнала conf/log4j.xml. + + + deploy - развертывание приложения на сервере Tomcat, предварительно установленном задачей setupTomcat. + + + createDb - создание базы данных приложения и выполнение соответствующих скриптов. + + + updateDb - обновление существующей базы данных приложения путем выполнения соответствующих скриптов. + + + start - запуск сервера Tomcat. + + + stop - остановка запущенного сервера Tomcat. + + + restart - последовательное выполнение задач stop, deploy, start. + + +
Создание проекта - TODO + Рекомендуемый способ создания нового проекта - использование CUBA Studio. Пример рассмотрен в главе "Быстрый старт" данного руководства: + Если применение CUBA Studio невозможно, для создания нового проекта необходимо выполнить следующее: + + Загрузить шаблон проекта, описанного в главы "Быстрый старт", и распаковать его в локальный каталог. + + + Выбрать название проекта. Оно должно состоять только из латинских букв в нижнем регистре, и быть не слишком длинным. Далее для примера используется название bookstore. + + + Выбрать корневой Java пакет. Далее для примера используется com.sample.bookstore. + + + В файле settings.gradle изменить строку с именем проекта:rootProject.name = 'bookstore' + Если модуль portal не нужен, удалить из объявления include элемент ':app-portal', и удалить соответствующее объявление project(':app-portal').projectDir. + + + В файле build.gradle произвести следующие изменения: + + В переменной ext.artifactGroup указать желаемое название группы для артефактов. Этот параметр играет роль только при выгрузке собранных артефактов приложения в репозиторий. Например:allprojects { + ext.artifactGroup = 'com.sample.bookstore' +... + + + В секции конфигурации модуля core указать для задач createDb и updateDb желаемое имя базы данных. Например: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' + } +... + + + Если модуль portal не используется, удалить объявление переменной portalModule, ее использование в списке configure([...]), и всю секцию configure(portalModule). + + + + + В модуле core: + В файле + context.xml + изменить URL подключения к базе данных:<Context> + <!-- PostgreSQL connection --> + <Resource +... + username="cuba" + password="cuba" + url="jdbc:postgresql://localhost/bookstore"/> +... + В файле + spring.xml + изменить базовый пакет поиска аннотированных бинов:<context:component-scan base-package="com.sample.bookstore"/> + + + В модуле web: + Перенести класс App и файлы локализованных сообщений в пакет com.sample.bookstore.web, и в файле web/WEB-INF/web.xml указать новое расположение этого класса для сервлета app_servlet:<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> +... + Изменить значение свойства приложения + cuba.mainMessagePack + в web-app.properties:cuba.mainMessagePack=com.haulmont.cuba.web com.sample.bookstore.web + + + Если используется модуль portal, изменить в нем соответственно пакет расположения классов контроллеров и ссылку на него в portal-dispatcher-spring.xml. + + + После выполнения вышеописанных действий можно собрать новый проект: + gradle idea assemble setupTomcat deploy
Отладка и тестирование - TODO -
-
- Интеграция со сторонними приложениями - TODO +
+ Подключение отладчика + Запустить сервер Tomcat в режиме отладки можно либо выполнением команды сборки + gradle start + либо запуском командного файла bin/debug.* установленного Tomcat. + После этого сервер будет принимать подключения отладчика на порту 8787. Порт можно изменить в файле bin/setenv.* в переменной JPDA_OPTS. + Для пошаговой отладки в Intellij IDEA необходимо в проекте приложения создать новый элемент Run/Debug Configuration типа Remote, и в его поле Port указать 8787. +
+
+ Тестирование +
+ Модульные тесты + Модульные тесты (unit tests) можно создавать и выполнять и на уровне Middleware, и на клиентском уровне. Для этого платформа включает в себя фреймворки JUnit и JMockit. + Допустим, имеется следующий контроллер экрана: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 + } + }); + } +} + Тогда можно написать следющий тест, проверяющий работу метода init():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)); + } + }; + } +} +
+
+ Интеграционные тесты Middleware + На уровне Middleware можно создавать интеграционные тесты, которые выполняются в полнофункциональном контейнере Spring с подключением к базе данных. В тестах такого типа можно выполнять код любого слоя внутри Middleware - от сервисов до ORM. + Для создания интеграционных тестов в модуле core проекта приложения должен быть создан базовый класс - наследник CubaTestCase. В этом классе должны быть переопределены методы инициализации доступа к данным и получения списка файлов конфигурации. Например: 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); + } +} + В качестве базы данных рекомендуется использовать отдельную тестовую БД, которую можно создавать, например, следующей задачей в build.gradle: configure(coreModule) { +... + task createTestDb(dependsOn: assemble, description: 'Creates local Postgres database for tests', type: CubaDbCreation) { + dbms = 'postgres' + dbName = 'sales_test' + dbUser = 'cuba' + dbPassword = 'cuba' + } + Класс CubaTestCase содержит следующие поля и методы, которые можно использовать в коде тестов: + + persistence - ссылка на интерфейс + Persistence + + + + metadata - ссылка на интерфейс + Metadata + + + + deleteRecord() - метод, который удобно использовать в tearDown() для удаления тестовых объектов из БД. + + + Пример теста, проверяющего чтение сущностей из базы данных: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(); + } + } +} +
+
+ Интеграционные тесты клиентского уровня + Интеграционные тесты на клиентском уровне реализуются с применением фреймворка + JMockit + . С его помощью тест изолируется от Middleware, а также создаются необходимые объекты инфраструктуры. + Класс клиентского интеграционного теста должен быть унаследован от CubaClientTestCase. В методе @Before необходимо вызвать унаследованные методы addEntityPackage(), setViewConfig() и затем setupInfrastructure() для создания объектов + Metadata + и + Configuration + и развертывания метаданных по выбранным сущностям. Далее в методе @Before можно дополнить инфраструктуру необходимыми мок-объектами с помощью конструкции Expectations или NonStrictExpectations. + Пример инициализирующего метода @Before одного из тестов платформы:@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; + } + }; +} +
+
diff --git a/doc/content/manual/ru/chapter_framework.xml b/doc/content/manual/ru/chapter_framework.xml index ebad38ec83..490ccb5fb0 100644 --- a/doc/content/manual/ru/chapter_framework.xml +++ b/doc/content/manual/ru/chapter_framework.xml @@ -289,7 +289,7 @@
Аннотации класса - + @javax.persistence.Entity @@ -315,7 +315,7 @@ Определяет, что данный класс является предком некоторых сущностей, и его атрибуты должны быть использованы в составе сущностей-наследников. Такой класс не сопоставляется никакой отдельной таблице БД. - + @javax.persistence.Table @@ -342,7 +342,7 @@ . - + @javax.persistence.Inheritance @@ -1697,7 +1697,7 @@ msg://com.abc.sales.web.customer/someMessage getMessageRef() - формирует для мета-свойства ссылку на сообщение, по которой можно получить локализованное название атрибута сущности - Для расширения набора вспомогательных методов в конкретном приложении бин MessageTools можно переопределить. Примеры работы с расширенным интерфейсом:MyMessageTools tools = messages.getTools(); + Для расширения набора вспомогательных методов в конкретном приложении бин MessageTools можно переопределить. Примеры работы с расширенным интерфейсом:MyMessageTools tools = messages.getTools(); tools.foo();((MyMessageTools) messages.getTools()).foo();
@@ -1758,8 +1758,8 @@ tools.foo();((MyMessageTools) messages.getTools . - Для расширения набора вспомогательных методов в конкретном приложении бин MetadataTools можно переопределить. Примеры работы с расширенным интерфейсом:MyMetadataTools tools = metadata.getTools(); -tools.foo();((MyMetadataTools) metadata.getTools()).foo(); + Для расширения набора вспомогательных методов в конкретном приложении бин MetadataTools можно переопределить. Примеры работы с расширенным интерфейсом:MyMetadataTools tools = metadata.getTools(); +tools.foo();((MyMetadataTools) metadata.getTools()).foo();
@@ -1803,7 +1803,7 @@ if (persistence.getDbDialect() instanceof PostgresDbDialect) getTools() - возвращает экземпляр интерфейса PersistenceTools (см. ниже). -
+
PersistenceTools ManagedBean, содержащий вспомогательные методы работы с хранилищем данных. Интерфейс PersistenceTools можно получить либо методом Persistence.getTools(), либо как любой другой бин - инжекцией или через класс AppBeans. Методы PersistenceTools: @@ -1822,8 +1822,9 @@ if (persistence.getDbDialect() instanceof PostgresDbDialect) reloadEntity() - перезагрузить экземпляр сущности с указанным представлением. Данный метод должен вызываться внутри активной транзакции. - Для расширения набора вспомогательных методов в конкретном приложении бин PersistenceTools можно переопределить. Примеры работы с расширенным интерфейсом:MyPersistenceTools tools = persistence.getTools(); -tools.foo();((MyPersistenceTools) persistence.getTools()).foo(); + + Для расширения набора вспомогательных методов в конкретном приложении бин PersistenceTools можно переопределить. Примеры работы с расширенным интерфейсом:MyPersistenceTools tools = persistence.getTools(); +tools.foo();((MyPersistenceTools) persistence.getTools()).foo();
PersistenceHelper @@ -10477,10 +10478,175 @@ public void init(Map<String, Object> params) {
Расширение функциональности - TODO + Платформа позволяет расширять и переопределять свою функциональность в приложениях в следующих аспектах: + + + расширение набора атрибутов сущностей + + + расширение функциональности экранов + + + расширение и переопределение бизнес-логики, сосредоточенной в бинах Spring + + + Рассмотрим две первые задачи на примере добавления поля "Адрес" в сущность User подсистемы безопасности платформы.
- Расширение сущностей - TODO + Расширение сущности + Создадим в проекте приложения класс сущности, унаследованный от com.haulmont.cuba.security.entity.User и добавим в него требуемый атрибут с соответствующими методами доступа: @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; + } +} + В аннотации + @Entity + должно быть указано новое имя сущности. Так как базовая сущность не объявляет стратегию наследования, то по умолчанию это SINGLE_TABLE. Это означает, что унаследованная сущность будет хранится в той же таблице, что и базовая, и аннотация + @Table + не требуется. Другие аннотации базовой сущности - + @NamePattern + , + @Listeners + и прочие - автоматически применяются к расширяющей сущности, но могут быть переопределены в ее классе. + Важным элементом класса новой сущности является аннотация @Extends с базовым классом в качестве параметра. Она позволяет сформировать реестр расширяющих сущностей, и заставить механизмы платформы использовать их повсеместно вместо базовых. Реестр реализуется классом ExtendedEntities, который является бином Spring с именем cuba_ExtendedEntities, и доступен также через интерфейс + Metadata + . + Добавим локализованное название нового атрибута в пакет com.sample.sales.entity: + messages.propertiesExtUser.address=Address + messages_ru.propertiesExtUser.address=Адрес + Зарегистрируем новую сущность в файле + persistence.xml + проекта:<class>com.sample.sales.entity.ExtUser</class> + Добавим в скрипты создания и обновления базы данных команду модификации соответствующей таблицы:alter table SEC_USER add ADDRESS varchar(100) +
+
+ Расширение экранов + Платформа позволяет создавать новые XML-дескрипторы экранов путем наследования от существующих. + Наследование XML выполняется путем указания в корневом элементе window атрибута extends, содержащего путь к базовому дескриптору. + Правила переопределения элементов XML экрана: + + Если в расширяющем дескрипторе указан некоторый элемент, в базовом дескрипторе будет произведен поиск соответствующего элемента по следующему алгоритму: + + Если переопределяющий элемент - view, то ищется соответствующий элемент по атрибутам name, class, entity. + + + Если переопределяющий элемент - property, то ищется соответствующий элемент по атрибуту name. + + + В других случаях, если в переопределяющем элементе указан атрибут id, ищется соответствующий элемент с таким же id. + + + Если поиск дал результат, то найденный элемент переопределяется. + + + Если поиск не дал результата, то определяется сколько в базовом дескрипторе элементов по данному пути и с данным именем. Если ровно один - он переопределяется. + + + Если поиск не дал результата, и в базовом дескрипторе по данному пути с данным именем нет элементов либо их больше одного, добавляется новый элемент. + + + + + В переопределяемом либо добавляемом элементе устанавливается текст из расширяющего элемента. + + + В переопределяемый либо добавляемый элемент копируются все атрибуты из расширяющего элемента. При совпадении имени атрибута значение берется из расширяющего элемента. + + + Добавление нового элемента по умолчанию производится в конец списка соседних элементов. Чтобы добавить новый элемент в начало или с произвольным индексом, необходимо выполнить следующее: + + определить в расширяющем дескрипторе дополнительный namespace: xmlns:ext="http://schemas.haulmont.com/cuba/4.0/window-ext.xsd" + + + добавить в расширяющий элемент атрибут ext:index с желаемым индексом, например: ext:index="0". + + + + + Для отладки преобразования дескрипторов можно включить вывод в журнал сервера результирующего XML. Делается это путем указания уровня TRACE для логгера com.haulmont.cuba.gui.xml.XmlInheritanceProcessor в файле конфигурации Log4j:<appender name="FILE" ... + <param name="Threshold" value="TRACE"/> +... +<category name="com.haulmont.cuba.gui.xml.XmlInheritanceProcessor"> + <priority value="TRACE"/> +</category> + Пример XML-дескриптора экрана браузера сущностей ExtUser:<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> + В данном примере дескриптор унаследован от стандартного браузера сущностей User платформы, и в таблицу добавлена колонка address с индексом 2, т.е. отображающаяся после login и name. + Зарегистрируем новый экран в + screens.xml + с теми же идентификаторами, которые использовались для базового экрана. После этого новый экран будет повсеместно вызываться взамен старого.<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"/> + Аналогично создаем экран редактирования:<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> + Регистрируем его в screens.xml с идентификатором базового экрана:<screen id="sec$User.edit" + template="com/sample/sales/gui/extuser/extuser-edit.xml"/> + После выполнения описанных выше действий в приложении вместо платформенной сущности User будет использоваться ExtUser с соответствующими экранами. + Контроллер экрана может быть расширен путем создания нового класса, унаследованного от контроллера базового экрана. Имя класса указывается в атрибуте class корневого элемента расширяющего XML дескриптора, при этом выполняются обычные правила наследования XML, описанные выше. +
+
+ Расширение бизнес-логики + Основная часть бизнес-логики платформы сосредоточена в бинах Spring, что позволяет легко расширить или переопределить ее в приложении. + Для подмены реализации бина достаточно создать свой класс, реализующий интерфейс или расширяющий базовый класс платформы, и зарегистрировать его в + spring.xml + приложения. Аннотацию @ManagedBean в расширяющем классе применять нельзя, переопределение бинов возможно только с помощью конфигурации в XML. + Рассмотрим пример добавления метода в бин + PersistenceTools + . + Создаем класс с нужным методом: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(); + } + } +} + Регистрируем класс в spring.xml модуля core проекта с тем же идентификатором, что и бин платформы:<bean id="cuba_PersistenceTools" class="com.sample.sales.core.ExtPersistenceTools"/> + После этого контекст Spring вместо экземпляра базового класса PersistenceTools будет всегда возвращать ExtPersistenceTools, например: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);
diff --git a/doc/content/manual/ru/chapter_quick_start.xml b/doc/content/manual/ru/chapter_quick_start.xml index 4be7bd6600..9c3c62fdff 100644 --- a/doc/content/manual/ru/chapter_quick_start.xml +++ b/doc/content/manual/ru/chapter_quick_start.xml @@ -61,9 +61,6 @@ Создайте рабочую папку для проекта, например, c:/work/sales. - - Путь не должен содержать пробелов! - Загрузите архив с шаблоном проекта http://docs.haulmont.com/cuba/4.0/samples/sales.zip и распакуйте содержимое архива в рабочий каталог таким образом, чтобы в каталоге c:/work/sales появился подкаталог modules и файлы build.gradle и settings.gradle @@ -390,7 +387,7 @@ menu-config.sales$Customer.lookup=Покупатели
Создание приложения в CUBA Studio -
+
Создание проекта diff --git a/doc/tools/css/style.css b/doc/tools/css/style.css index 788d687820..28a2c3a446 100644 --- a/doc/tools/css/style.css +++ b/doc/tools/css/style.css @@ -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; -} \ No newline at end of file +} + +/* 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; +} diff --git a/doc/tools/xsl/html-single.xsl b/doc/tools/xsl/html-single.xsl index 4197578c25..dbf14f5918 100644 --- a/doc/tools/xsl/html-single.xsl +++ b/doc/tools/xsl/html-single.xsl @@ -13,5 +13,44 @@ - + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/tools/xsl/html.xsl b/doc/tools/xsl/html.xsl index f2abcb3658..1d2aaf107c 100644 --- a/doc/tools/xsl/html.xsl +++ b/doc/tools/xsl/html.xsl @@ -74,4 +74,8 @@ + + \ No newline at end of file diff --git a/modules/core/src/com/haulmont/cuba/core/app/FoldersServiceBean.java b/modules/core/src/com/haulmont/cuba/core/app/FoldersServiceBean.java index aa7b3747b2..03572f9ce2 100644 --- a/modules/core/src/com/haulmont/cuba/core/app/FoldersServiceBean.java +++ b/modules/core/src/com/haulmont/cuba/core/app/FoldersServiceBean.java @@ -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); diff --git a/modules/core/src/com/haulmont/cuba/core/sys/persistence/MappingFileCreator.java b/modules/core/src/com/haulmont/cuba/core/sys/persistence/MappingFileCreator.java index ee415440f7..7b337fb503 100644 --- a/modules/core/src/com/haulmont/cuba/core/sys/persistence/MappingFileCreator.java +++ b/modules/core/src/com/haulmont/cuba/core/sys/persistence/MappingFileCreator.java @@ -259,6 +259,7 @@ class MappingFileCreator { private String xml; private Type(int order, String xml) { + this.order = order; this.xml = xml; } diff --git a/modules/gui/src/com/haulmont/cuba/gui/window-ext.xsd b/modules/gui/src/com/haulmont/cuba/gui/window-ext.xsd new file mode 100644 index 0000000000..677cef0ac0 --- /dev/null +++ b/modules/gui/src/com/haulmont/cuba/gui/window-ext.xsd @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/modules/gui/src/com/haulmont/cuba/gui/window.xsd b/modules/gui/src/com/haulmont/cuba/gui/window.xsd index 4b67a351b8..70b4ea701e 100644 --- a/modules/gui/src/com/haulmont/cuba/gui/window.xsd +++ b/modules/gui/src/com/haulmont/cuba/gui/window.xsd @@ -153,10 +153,12 @@ + + @@ -847,6 +849,7 @@ + diff --git a/modules/gui/src/com/haulmont/cuba/gui/xml/XmlInheritanceProcessor.java b/modules/gui/src/com/haulmont/cuba/gui/xml/XmlInheritanceProcessor.java index e8a7461420..b6c507c57a 100644 --- a/modules/gui/src/com/haulmont/cuba/gui/xml/XmlInheritanceProcessor.java +++ b/modules/gui/src/com/haulmont/cuba/gui/xml/XmlInheritanceProcessor.java @@ -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 params; - private List targetLocators = new ArrayList(); + private List targetLocators = new ArrayList<>(); + + protected Resources resources = AppBeans.get(Resources.class); public XmlInheritanceProcessor(Document document, Map 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) {