пятница, 29 ноября 2013 г.

Плавающая ошибка из-за PersistenceContextType

Сдали проект. Запустили в эксплуатацию. Через 3-4 дня приходит первый алёрт от Заказчика об ошибке, блокирующей работу админки. Перезагрузка или переустановка приложения проблемного модуля восстанавливает работоспособность админки. Приложение работает с использованием Spring 3.0 + Hibernate 4.1 по спецификации JPA 2.0.

Недели 2 переписки с Заказчиком и выяснения обстоятельств проявления ошибки + 2 недели активного поиска причины ошибки. Ошибка плавающая, проявляется с периодичностью 3-5 дней только при сохранении @Entity объектов. В stacktrace рутовая ошибка разная, но чаще всего:
Caused by: org.hibernate.HibernateException: Flush during cascade is dangerous

Экспериментальным путем было выявлено, что ошибка проявляется только при одновременной интенсивной работе нескольких редакторов. Важная деталь: другой модуль этого же приложения такой ошибки не проявляет. Похоже на проблему конфигурации или модели данных конкретного модуля?

Манипуляции с исходным кодом админки, DAO и модели в результате привели к интересной ошибке:
java.util.ConcurrentModificationException
 at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)

Это навело на мысль, что ошибка возникает в условиях одновременного сохранения в БД достаточно большого числа разных потоков. Нашли в документации Spring упоминание специфики использования DAO на базе JPA, и в частности подключения EntityManager к DAO с PersistenceContextType.EXTENDED:
The alternative, PersistenceContextType.EXTENDED, is a completely different affair: This results in a so-called "extended EntityManager", which is not thread-safe and hence must not be used in a concurrently accessed component such as a Spring-managed singleton bean.

Проверили: так и есть, один из предыдущих разработчиков модуля включил в аннотацию @PersistanceContext атрибут PersistenceContextType.EXTENDED. Включение этого атрибута позволило ему упростить код в отношении получения коллекций связанных сущностей по LazyFetch, однако в результате привело к плавающей ошибке!

вторник, 19 ноября 2013 г.

Постраничная навигация AssetPublisher

В Liferay Portal 6.1.1 GA2 недокументированная особенность метода AssetEntryServiceUtil.getEntriesCount

Если настроить Публикатор по одному типу ресурса (например, "Сетевой контент" подтип "Новость"), без фильтрации и группировки по категориям и тегам, включить постраничную навигацию и отметить галочку "Включить права", то подсчет найденных ассетов никогда не превысит 200! В результате если у вас наберется новостей скажем 398, то при количестве элементов на страницу 20 выдасться всего 10 страниц. Игра с настройками не помогает - похоже какое-то внутрисистемное ограничение на максимальное количество ассетов выбираемых для проверки разрешений, типа asset.filter.search.limit

Быстрым решением было получение общего количества ассетов удовлетворяющих условию через AssetEntryLocalServiceUtil и а-ля аппроксимация в сторону убывания к правильному значению. Пример модификации /jsp/html/portlet/asset_publisher/view_dynamic_list.jspf (начиная со строки 93):
 else if (!groupByClass) {
  assetEntryQuery.setClassNameIds(classNameIds);

  /* total = AssetEntryServiceUtil.getEntriesCount(assetEntryQuery); */
  total = AssetEntryLocalServiceUtil.getEntriesCount(assetEntryQuery);
  if(total > delta){
   do{
    assetEntryQuery.setEnd(total);
    assetEntryQuery.setStart(total - delta);
    results = AssetEntryServiceUtil.getEntries(assetEntryQuery);
    if(results.size() < delta){
     total = total - delta + results.size();
    }
   } while (results.size() == 0);  
  }else{
   total = AssetEntryServiceUtil.getEntriesCount(assetEntryQuery);
  }

  searchContainer.setTotal(total);

среда, 15 мая 2013 г.

Liferay вложенные портлеты 3

Последние грабли с runtime портлетов внутри портлета, с которыми я столкнулся, это runtime под Liferay 6.1.1 GA2. Бьёт ошибку: PortalUtil.renderPortlet throws exception "javax.servlet.ServletException: File &quot;/html/common/themes/portlet.jsp&quot; not found". Подробности http://issues.liferay.com/browse/LPS-31508

Если же отключить обрамление и дефолтный шаблон для включаемого портлета, как в принципе и требовалось мне, то runtime отрабатывает. Итого, для моего кастомного портлета, в portlet.xml:
    <portlet>
        <portlet-name>MultimediaToolbar</portlet-name>
        <display-name>MultimediaToolbar</display-name>
        <portlet-class>com.vaadin.terminal.gwt.server.ApplicationPortlet2</portlet-class>
        <init-param>
            <name>application</name>
            <value>ru.snetwork.liferay.multimedia.portlet.back.ToolbarApplication</value>
        </init-param>
        <init-param>
            <name>view-jsp</name>
            <value>/view.jsp</value>
        </init-param>
        <expiration-cache>0</expiration-cache>
        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>view</portlet-mode>
        </supports>
        <resource-bundle>ru.snetwork.liferay.multimedia.ToolbarPortlet</resource-bundle>
        <portlet-info>
            <title>MultimediaToolbar</title>
            <short-title>MultimediaToolbar</short-title>
        </portlet-info>
        <portlet-preferences>
            <preference>
                <name>portlet-setup-show-borders</name>
                <value>false</value>
            </preference>
        </portlet-preferences>
    </portlet>
В liferay-portlet.xml:
    <portlet>
        <portlet-name>MultimediaToolbar</portlet-name>
        <use-default-template>false</use-default-template>
        <instanceable>true</instanceable>
        <ajaxable>false</ajaxable>
        <header-portlet-css>/css/backoffice-multimedia.css</header-portlet-css>
        <add-default-resource>true</add-default-resource>
    </portlet>
Кроме того, чтобы не било ошибку "Reject ServeResource" на ресурсы портлеты (картинки, css, js - портлет Vaadin-овский), добавил через Hook в portal.properties:
portlet.add.default.resource.check.whitelist=58,86,87,88,103,113,145,MultimediaToolbar_WAR_SnCommonPortlets

Liferay вложенные портлеты 2

Способ вызова портлета внутри портлета предложенный в первом посте не работает в Liferay 5.2.3, который идет в бандле с Tomcat 6. Использовать PortletBagPool для получения портального ServletContext бесполезно, т.к. в версии 5.2.3 PortletBagPool не содержить встроенные портлеты.

Получить портальный ServletContext можно получить из аттрибутов ServletRequest - оказывается он передается с ключом WebKeys.CTX. Кроме того PortalUtil в API 5.2.3 немного отличается:
    public static String renderPortlet(final PortletRequest request, final PortletResponse response, final String portletId, final String queryString) {
        String result = "Error occured while running portlet";
        try {
            // Get servlet request / response
            HttpServletRequest servletRequest = PortalUtil.getHttpServletRequest(request);
            HttpServletResponse servletResponse = PortalUtil.getHttpServletResponse(response);
            HttpServletRequest portalServletRequest = PortalUtil.getOriginalServletRequest(servletRequest);

            // Get theme display
            final ThemeDisplay themeDisplay = (ThemeDisplay) servletRequest.getAttribute(WebKeys.THEME_DISPLAY);
            // Backup current state
            PortletDisplay portletDisplay = themeDisplay.getPortletDisplay();
            PortletDisplay portletDisplayClone = new PortletDisplay();
            portletDisplay.copyTo(portletDisplayClone);
            final Map requestAttributeBackup = new HashMap();
            for (final String key : Collections.list((Enumeration) servletRequest.getAttributeNames())) {
                requestAttributeBackup.put(key, servletRequest.getAttribute(key));
            }
            // Render the portlet as a runtime portlet
            try {
                StringBuilder sb = new StringBuilder();
                com.liferay.portal.model.Portlet portlet = PortletLocalServiceUtil.getPortletById(PortalUtil.getCompanyId(request), portletId);
                servletRequest.setAttribute(WebKeys.RENDER_PORTLET_RESOURCE, Boolean.TRUE);
                ServletContext ctx = (ServletContext) portalServletRequest.getAttribute(WebKeys.CTX);
                PortalUtil.renderPortlet(sb, ctx, servletRequest, servletResponse, portlet, queryString);
                result = sb.toString();
            } finally {
                // Restore the state
                portletDisplay.copyFrom(portletDisplayClone);
                portletDisplayClone.recycle();
                for (final String key : Collections.list((Enumeration) servletRequest.getAttributeNames())) {
                    if (!requestAttributeBackup.containsKey(key)) {
                        servletRequest.removeAttribute(key);
                    }
                }
                for (final Map.Entry entry : requestAttributeBackup.entrySet()) {
                    servletRequest.setAttribute(entry.getKey(), entry.getValue());
                }
            }
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        }
        return result;

    }  

среда, 13 февраля 2013 г.

Mac Safari vs Vaadin scrollable

Скроллинг Vaadin таблиц c большим объемом данных в Safari под Mac заметно притормаживает и затрудняет работу для пользователя. При чем Ajax подгрузки не происходит - все данные уже загружены и отрисованы браузером. Протестировал в Chrome, Firefox и Opera - тормозов нет.

Возникло подозрение, что тормоза в Safari как-то связаны с анимацией скроллинга и скролбаров. Надо побровать отключить анимацию скрытия скролбаров. Прогуглил и нашел: http://stackoverflow.com/questions/7855590/how-can-i-prevent-scroll-bars-from-being-hidden-for-trackpad-users-in-webkit

Прописал в стилях темы Vaadin для всех scrollable компонентов:
.v-scrollable::-webkit-scrollbar {
    -webkit-appearance: none;
    width: 11px;
    height: 11px;
}
.v-scrollable::-webkit-scrollbar-thumb {
    border-radius: 8px;
    border: 2px solid white; /* should match background, can't be transparent */
    background-color: rgba(0, 0, 0, .5);
}
Анимация отключилась, скролбары видны постоянно, скроллинг работает без тормозов!