Показаны сообщения с ярлыком service builder. Показать все сообщения
Показаны сообщения с ярлыком service builder. Показать все сообщения

Обновление базы данных

Liferay сервисы поддерживают версионность базы данных. Другими словами, в зависиммости от описанных в service.xml сущностей, будут созданы соответствующие таблицы в базе данных и будет сохранена версия этих таблиц на момент создания. При последующем изменениии сущностей, пересборке и развертывании сервисов произойдет автоматическое обновления соответствующих таблиц. Это удобный и легкий способ создания и поддержки слоя для работы с базой данных, но также существуют и некоторые недостатки.

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

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

Также могут возникнуть проблемы, если структуру базы надо обновить на этапе сборки проекта. В этом случае будут недоступны изменения, которые автоматически появятся после развертывания. Как выход можно отключить автообновление структуры. Для этого надо создать файл service-ext.properties в той же директории что и service.properties файл, и переопределить следующий параметр:
build.auto.upgrade=false

Mocking Liferay Services

Если вы пишете под Liferay, используете Service Builder и не знаете как протестировать свою логику, тогда этот пост для вас.

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

Пока нет ничего сложного. С подходом определились, теперь необходимо найти инструмент, который позволит создавать заглушки на любой сгенерированный сервис. Такой инструмент есть — Mockito. В качестве льтернативы могу предложить Easymock, но примеры будут на Mockito.

Для примера возьмем сущность Status и опишем ее в service.xml:
<entity name="Status" table="status" local-service="true" remote-service="false">
    <column name="id" db-name="status_id" type="long" primary="true" id-type="increment"/>
    <column name="name" db-name="name" type="String"/>
</entity>

Соберем сервиса, после чего получим ряд классов, который все вместе образуют DAO слой для этой сущности. Обычно в бизнес логике используются классы *LocalServiceUtil, в нашем случае это класс StatusLocalServiceUtil. Допустим что у нас есть некий класс Controller, который вызывает метод getStatus класса StatusLocalServiceUtil и выполняет некую логику с результатом:
public class Controller {
    public void doAction(long statusId) throws SystemException, PortalException {
        // Получаем статус по его id
        Status status = StatusLocalServiceUtil.getStatus(statusId);
        // и выводим его в консоль
        System.out.println("Status = " + status);
    }
}

Целью является создание заглушки, которая будет эмулировать работу метода getStatus. А сложность в том, что этот метод статический и подменить его сложно, но возможно. Далее следует пример юнит теста, в котором происходит создание заглушки:
public class ControllerTest {
    private StatusLocalService service;
    private Controller controller;

    @BeforeMethod
    public void beforeMethod() {
        // Создаем мок сервиса используя библиотеку mockito
        service = mock(StatusLocalService.class);
        // После этой строчки класс StatusLocalServiceUtil 
        // будет использовать зуглушку или мок сервиса
        new StatusLocalServiceUtil().setService(service);
        // А это контроллер, который работает со
        // сгенерированными сервисами и ничего не знает
        // ни о каких моках
        controller = new Controller();
    }

    @Test
    public void testGetStatus()
            throws ServletException, SystemException, PortalException {
        // Конфигурируем сервис таким образом, что при вызове
        // метода с параметром 7 вернется уже другой мок —
        // мок объекта Status
        when(service.getStatus(eq(7L))).thenReturn(mock(Status.class));

        // Вот он самый, вызов метода, который тестируется
        controller.doAction(7);

        // А здесь проверяем был ли вызов метода getStatus
        // именно с параметром 7
        verify(service).getStatus(eq(7L));
    }
}

На самом деле дела обстоят сложнее и требуют более глубоких объяснений иерархии генерируемых сервисов, но для тестирования этого достаточно.

Транзакции в Liferay

Те, кто активно используют Service Builder понимают, что поддержка транзакций в сгенерированных сервисах просто необходима. Более того, это даже предусмотрено разработчиками портала. Но есть загвоздка — это не работает. По крайней мере с версией 6.0.5.

На странице http://issues.liferay.com/browse/LPS-15904 описана причина этого дефекта.

Как правило, портлеты создают новую SessionFactory для загрузки собственных hbm файлов. Также известно, что портал и портлеты используют один и тот же менеджер для создания транзакций, который в свою очередь использует SessionFactory портала. Однако портлеты должны использовать свои собственные SessionFactory, иначе портлетные сессии не будут использоваться в портальных транзакциях. Результат — транзакции в портлетах вообще не работают.

Как вариант решения можно использовать JTA transaction manager. Eсть две библиотеки на выбор: JOTM или Atomikos. На сайте Liferay есть даже статья, которая описывает как настроить портал для каждой из библитек — http://www.liferay.com/community/wiki/-/wiki/Main/JTA-XA+on+Tomcat. Но к сожалению статья не помогла. Только после продолжительного поиска удалось найти работающее решение.

Настройка портала с библиотекой JOTM:
  1. Скачиваем библиотеку с сайта http://forge.ow2.org/projects/jotm, отдельно обновляем библиотеку xapool до последней версии, даже если это бета-версия;
  2. Добавляем необходимые jar-файлы в {tomcat}/lib:
  3. Удаляем commons-logging.jar из {tomcat}/lib/ext и log4j.jar из {tomcat}/webapps/ROOT/WEB-INF/lib а также из каждого портлета;
  4. Находим файл {tomcat}/conf/context.xml и добавляем следующее в конец секции Context:
  5. <Resource 
       auth="Container" 
       name="UserTransaction" 
       type="javax.transaction.UserTransaction" />
        
    <Resource 
       auth="Container" 
       driverclassname="com.mysql.jdbc.Driver" 
       factory="org.objectweb.jotm.datasource.DataSourceFactory" 
       max-connections="100" 
       maxactive="20" 
       name="jdbc/LiferayPool" 
       username="root" 
       password="" 
       type="javax.sql.DataSource" 
       url="jdbc:mysql://localhost/lportal?useUnicode=true&amp;characterEncoding=UTF-8
          &amp;useFastDateParsing=false" /> 
        
    <Transaction 
      factory="org.objectweb.jotm.UserTransactionFactory" 
      jotm.timeout="60" />
    
    
  6. Комментируем все jdbc.default.* свойства в portal-ext.properties, а затем добавляем следующее:
  7. transaction.manager.impl=org.springframework.transaction.jta.JtaTransactionManager
    transaction.manager.property.allowCustomIsolationLevels=true
    transaction.manager.property.globalRollbackOnParticipationFailure=true
    jdbc.default.jndi.name=jdbc/LiferayPool
    
Это собственно и все. Гарантий никаких, в моем случае это заработало, в любом другом может и не заработать — это Liferay.

Share plugins services

Представьте два портлета: «CLP» и «CLP Consumer». Первый содержит сервис-проект (service-plugin), сущность MyObject и соответственно набор сгенерированных классов, а второй нуждается в доступе к этому слою сервисов. Для обеспечения такого доступа необходимо следующее:
  1. Выполнить сборку сервиса в «CLP» портлете и в результате появится файл CLP-portlet-service.jar в директории {clp-portlet}/docroot/WEB-INF/lib;
  2. Сделать CLP-portlet-service.jar доступным для «CLP Consumer» портлета. Это можно сделать двумя способами:
    1. Скопировать jar файл в {clp-consumer-portlet}/docroot/WEB-INF/lib;
    2. Скопировать jar файл в глобальную директорию библиотек сервера приложений — {tomcatHome}/lib/ext но при этом удалить из всех других мест;
  3. Теперь можно вызвать метод сервиса (который находится в «CLP» портлете) из «CLP Consumer» портлета. Например:
  4. com.liferay.clp.service.MyObjectServiceUtil.testMethod();
    
Используя Tomcat 6.0 с этим были проблемы, но они вроде как решены в Liferay Portal 6.0.1 RC. Первоисточник

Service Builder, размер поля

Liferay Service Builder, по умолчанию, задает размер поля типа String равным 75 символов. Свой размер поля можно задать в файле portlet-model-hints.xml.

Пример:
  1. Описываем произвольную сущность в service.xml:
  2. <entity local-service="true" name="Note" remote-service="false" table="note">
        <column id-type="increment" name="id" primary="true" type="long"/>
        <column name="noteText" type="String"/>
    </entity>
  3. Собираем сервис;
  4. Открываем файл /docroot/WEB-INF/src/META-INF/portlet-model-hints.xml;
  5. Задаем размер поля noteText = 512:
  6. <model name="mypackage.model.Note">
        <field name="id" type="long"/>
        <field name="noteText" type="String">
            <hint name="max-length">512</hint>
        </field>
    </model>
  7. Еще раз собираем сервис.
Результат можно посмотреть в файле /docroot/WEB-INF/sql/tables.sql, в котором длина поля noteText поменялась с 75 на 512:
create table note (id LONG not null primary key, noteText VARCHAR(512) null);

Источник: http://issues.liferay.com/browse/LEP-7406