четверг, 21 марта 2013 г.

Primefaces: selectors. Селекторы в ajax запросах

Primefaces продолжает радовать интересными фичами.

Начиная с версии 3.3 появились селекторы для определения контролов, которые надо обновить или запроцессить. Для реализации используется одноимённый механизм из jQuery, соответственно, здесь полный мануал к тому, какие возможности при написании селекторов нам предоставлены. Непосредственный пример для primefaces ниже.

Постановка задачи:
Есть некая таблица (или другой UINamingContainer), в которой отображаются иконки со статусами чего-либо. При этом статусы нужно обновлять, скажем, раз в 5 секунд.

Решение:
Описываем таблицу, в ней одной из колонок будет картинка со статусом (зелёная или красная).
<p:dataTable id="bigTable"
    var="item"
    value="#{myBigController.objectList}">
...
    <p:column id="operationTypeIcon">
        <f:facet name="header">
            <h:outputText value="Статус" />
        </f:facet>
        <p:graphicImage id="statusImage"
            styleClass="for-update"
            value="/resources/images/#{statusController.getStatusImageName(item)}"/>
    </p:column>
...

</p:dataTable>
Для автообновления будем использовать компонент primefaces p:poll. В его параметрах указывается:
  • интервал, через который нужно выполнять аякс-запрос;
  • метод-листенер, который нужно выполнять;
  • идентификаторы компонентов, которые нужно обновить.
Соответственно, раньше нам приходилось обновлять, например, целиком таблицу (пример ниже), хотя на самом деле достаточно обновить только иконки статусов.
<p:poll interval="5" widgetVar="statusPoll"
    global="false"
    listener="#{statusController.refreshStatuses}"
    update="bigTable"
    process="@this" />
И вот теперь есть возможность указать некие правила, по которым будет определено, какие компоненты надо обновлять. При этом в критериях можно оперировать и css-классами, и типами контролов.
Например:
update="@(.ui-panel :input:not(select))"
Здесь мы определяем, что надо обновить все контролы с css-классом ui-panel и типом input, но не select-контролы (к последним, например, относится комбобокс).

Как это поможет нам при решении задачи?
Будем обновлять только те элементы, которые обладают css-классом for-update (не зря же я его так назвал?).
<p:poll interval="5" widgetVar="statusPoll"
    global="false"
    listener="#{statusController.refreshStatuses}"
    update="@(.for-update)"
    process="@this" />
На что обратить внимание?
Есть один важный аспект, который несправедливо опускается в мануалах: у компонентов, которые должны найтись селектором, необходимо вручную проставлять идентификаторы. Если не сделать этого, то они будут генерироваться слишком поздно и primefaces не сможет подставить их в параметры аякс-запроса. Так что обратите внимание на:
<p:graphicImage id="statusImage" ...>

среда, 20 марта 2013 г.

j_security_check request и генерация идентификаторов контролов

Наткнулся на интересную ошибку (особенность?) работы JSF.
Честно говоря, особого желания нет копаться до истины и "а почему же всё-таки так", но случай интересный.

История возникновения
На странице, куда осуществляется редирект после некорректного логина было несколько контролов (в моём случае h:link на попробовать ещё раз и на страницу восстановления пароля). Страница очень простая, никаких аяксов, лишь эти 2 ссылки и статический текст о том, что авторизация не удалась.

Запускаем, вводим неверный пароль, вместо страницы ошибки входа видим в браузере internal server error 500, а в логе:
Caused by: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
 at java.util.ArrayList.RangeCheck(ArrayList.java:547)
 at java.util.ArrayList.get(ArrayList.java:322)
 at javax.faces.component.AttachedObjectListHolder.restoreState(AttachedObjectListHolder.java:165)
 at javax.faces.component.UIComponentBase.restoreState(UIComponentBase.java:1560)
А если не поленимся обновить страницу, то всё отобразится корректно.

Кто виноват и что делать?
Оказалось, всё дело в том, что стандартный j_security_check request обрабатывается напрямую веб-контейнером, а не JSF. Собственно, это не секрет, секрет в том, что из этого следует: на странице ошибки входа JSF не сгенерирует id-шников для контролов. Поэтому всё, что нужно, это явно указать идентификаторы:
...
    try login again
    restore your password


Почему после обновления страницы всё работает?
Потому что это будет обычный запрос "файла с хтмл" на сервере, а не j_security_check, поэтому его обработает движок JSF.

Почему такой странный лог restoreState : IndexOutOfBoundsException, если всё дело в идентификаторах контролов?
Не знаю, как раз в этом разбираться у меня желания не было. Надеюсь, разработчики это пофиксят со временем.

воскресенье, 15 января 2012 г.

Использование @Decorator

Хочу поделиться недавним открытием: использование в j2ee приложениях механизма Декораторов из JSR 299 действительно очень удобно и, на мой вкус, хорошо реализовано с точки зрения интерфейса.

Что такое Декоратор?
Это один из паттернов (шаблонов) проектирования, предназначенный для динамического подключения дополнительного поведения к объекту. Шаблон Декоратор предоставляет гибкую альтернативу практике создания подклассов с целью расширения функциональности. Определение честно скопировал из Википедии.
Попытаюсь пояснить, чем может быть плохо наследование. Пусть есть у нас иерархия классов, формирующих строку приветствия. Суперкласс пишет просто "Здравствуйте", следующий наследник добавляет к этому приветствию имя пользователя, следующий дополнительно поздравляет с новым годом, следующий напоминает, что надо заплатить за интернет/сходить на выборы. Думаю, идея ясна. Но вот прошёл новый год, поздравлять с ним уже не надо. Но класс, напоминающий об оплате за интернет, отнаследован от него... Хотелось бы иметь механизм, позволяющий конфигурировать, какое дополнительное поведение нужно добавить, а какое нет. Именно это и представляет собой Декоратор: множество обёрток, которые можно использовать в любой комбинации. Стандарт JSR 299 определяет правила, по которым реализован механизм Декораторов в javaEE.

Знакомимся ближе
Для меня всегда наиболее понятной формой объяснения был пример, поэтому и в данной заметке покажу использование Декоратора на примере. Допустим, необходимо уметь формировать приветственные сообщения для пользователя при входе на некоторый сайт. Только учтите, что пример надуманный и в обычных условиях его можно (и нужно) реализовать проще.
Итак, интерфейс, определяющий способность формировать приветственное сообщение и сервис, его реализующий:
public interface Greeting {
    
    String helloString();    
}
@Stateless
public class GreetingService implements Greeting {

    @Override
    public String helloString() {
        return "Здравствуйте!";
    }    
}
Ну в этом коде ничего необычного и заслуживающего упоминания нет. Теперь к нему создадим модель (backing bean), стоящую за веб-страницей:
@SessionScoped
@Named
public class GreetingBean implements Serializable {
    
    @Inject
    private transient Greeting greetingService;
    
    public String getHelloString() {
        return greetingService.helloString();
    }    
}
На самой странице вызовем метод приветствия из модели getHelloString. Саму страницу мне сгенерировал netbeans:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
    
        Facelet Title
    
    
        #{greetingBean.helloString}
    
</html>
Всё, мы получили работающий код, выводящий "Здравствуйте!" при заходе на страницу. Теперь нужно решить задачу кастомизации приветствия:
  • нужно поздравлять с новым годом;
  • нужно сообщать о том, что вышел новый плагин для более комфортной работы с сайтом.


Приступим к декорированию приветствия?
@Decorator
public class NewYearGreeting implements Greeting {
    
    @Inject @Delegate
    private Greeting greeting;

    @Override
    public String helloString() {
        return greeting.helloString() + " С новым годом!";
    }    
}
@Decorator
public class ReminderGreeting implements Greeting {
    
    @Inject @Delegate
    private Greeting greeting;

    @Override
    public String helloString() {
        return greeting.helloString() + " Не забудьте обновить ваш плагин!";
    }    
}
Это два класс-обёртки (декоратора) для изменения поведения сервиса приветствия.
  • @Decorator - определяет, что создаваемые классы являются Декораторами.
  • implements Greeting - определяет, поведение какого именно интерфейса эти декораторы могут изменять.
  • @Inject @Delegate private Greeting greeting - сюда с помощью dependency injection будет подставлена какая-то имплементация интерфейса Greeting. Правила такие же как везде: допускается @Any и создание своих квалификаторов (@Qualifier) для определения конкретной реализации, если их несколько.


По умолчанию, механизм декораторов выключен. Для включения необходимо отредактировать файл beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
           
           
                ru.vrn.vrk.service.ReminderGreeting   
                ru.vrn.vrk.service.NewYearGreeting                
           
</beans>
При этом порядок, в котором перечислены декораторы важен: именно в таком порядке они и будут применяться, т.е. сначала ReminderGreeting, а затем NewYearGreeting. Собираем приложение ещё раз и видим при входе:
> Здравствуйте! С новым годом! Не забудьте обновить ваш плагин!
Признаться, я был удивлён, т.к. ожидал иного порядка фраз. Оказалось, ReminderGreeting действительно применяется первым. Но в процессе выполнения его метода helloString() идёт обращение к Greeting, и теперь контейнер применяет следующий декоратор NewYearGreeting. Порядок вызовов можно описать так:
  • GreetingBean.getHelloString: ReminderGreeting.helloString()
  • ReminderGreeting.helloString: NewYearGreeting.helloString() + " Не забудьте обновить ваш плагин!"
  • NewYearGreeting.helloString: GreetingService.helloString() + " С новым годом!"
  • GreetingService.helloString: "Здравствуйте!"
Это несложно проверить, если в helloString() декораторов прописать вывод имени класса в лог, а уж затем возврата строки-приветствия.

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

среда, 14 декабря 2011 г.

Возможность изменения строк java и их кэширование

Поговорим об изменении неизменяемых классов Java и немного о кэшировании строк.
Для тех, кто знает эти механизмы здесь не будет ничего нового. Для тех, кто не задумывался о том, как они уживаются вместе - немного уличной магии.
Итак, есть код:
public class SecureClass {
    
    private String msg = "Fail";
    
    public String getMessage() {
        return msg;
    }    
}
public class Main {

    public static void main(String[] args) {
        SecureClass secureClass = new SecureClass();        
        System.out.println(secureClass.getMessage());
    }   
}
После выполнения в консоли по вполне очевидным причинам будет:
>>Fail
Но мы то хотим выиграть, а не проиграть?
При этом внести исправления в SecureClass не можем или не хотим? Тогда немного уличной магии для класса Main:
public class Main {

    public static void main(String[] args) throws Exception {
        SecureClass secureClass = new SecureClass();  
        Field valueField = String.class.getDeclaredField("value");
        valueField.setAccessible(true);
        valueField.set("Fail", new char[] {'W', 'i', 'n', '!'});
        System.out.println(secureClass.getMessage());
    }   
}
Для большей наглядности и чтобы не перегружать заметку обработкой всех исключений, которые нужно обрабатывать при использовании reflect, сократил код, написав throws Exception. Запускаем ещё раз и видим:
>>Win!
Ну прекрасно, так и должно быть!

Небольшие пояснения:
В локальной безымянной переменной метода main с помощью reflect меняем значение внутреннего поля "value". Таким образом эта локальная переменная уже содержит в себе другую строку (вот вам и невозможность изменить класс String). Эти изменения отражаются и на переменной из класса SecureClass, потому что java старается хранит строки в пуле, когда это возможно и когда программист этому явно не воспрепятствовал. Получается, что в обоих классах строковые переменные "смотрели" на один и тот же объект. Попытаемся намекнуть java, что мы не хотим своими reflect-операциями изменять строки в пуле, указав new String("Fail") вместо "Fail":
public class Main {

    public static void main(String[] args) throws Exception {
        SecureClass secureClass = new SecureClass();  
        Field valueField = String.class.getDeclaredField("value");
        valueField.setAccessible(true);
        valueField.set(new String("Fail"), new char[] {'W', 'i', 'n', '!'});
        System.out.println(secureClass.getMessage());
    }   
}
Запускаем и видим:
>>Fail
Потому что теперь переменные указывают на разные объекты. Меняя один - не оказываем никакого влияния на другой.

Если честно, не могу представить ситуации, когда бы стал это использовать, но выглядит забавно.

воскресенье, 11 декабря 2011 г.

Про ошибки

Вспомнились пословицы о том, что на ошибках учатся. Интересно, кто их придумал? А кто вообще придумывает пословицы?

На днях допустил ошибку: выбрал неверный способ реализации для своей задачи. В результате потратил день на бесплодные попытки заставить систему работать. Через 8 часов смог смириться со своей ошибкой и придумать другой вариант реализации. В итоге время упущено и ничего не работает. Обидно: оглядываясь назад, я понимаю, что та птица и не могла взлететь. И я знал причины, по которым не сможет, просто хорошенько не обдумал в своё время и не вспомнил о них.

Как тут не согласиться с мнениями кучи знаменитых программистов о том, что ошибки проектирования куда сильнее сказываются на сроках сдачи проекта и авральности работы, чем непосредственные ошибки кодинга.
Вот только многие недели до этого я всё так же особенного внимания не уделял проектированию. А в результате оказывался прав, и экономил кучу времени на обсуждениях. Конечно, это всё были достаточно локальные проблемы и риски малы: в самом худшем случае я терял бы не более 1,5-2 дней, поэтому мог себе позволить такую роскошь как отсутствие заранее продуманной полной модели, и просто полагался на чутьё. В этот раз не удалось. Я расстроен? Очень. Сейчас, в свой выходной, я всё ещё не могу заставить себя прекратить думать над проблемой. Но учитывая всю динамику проекта - считаю верным принимать не самые ключевые решения быстро. На длительной дистанции это всё равно даёт существенный плюс в скорости. И чем больше опыта - тем более весомым оказывается плюс.

Ну и про ошибки: не учатся на ошибках, учатся на правильных решениях и удачных примерах. Ошибки - всего лишь плата за плохую подготовку. В последнее время почему-то стало модным идти от противного и пропагандировать изучение всяких "антипаттернов". Не хочу сказать что это совсем бесполезно, но можно потратить время с большей пользой.

Неудачная попытка вызова метода @Stateless сервиса

Сегодня предлагаю рассмотреть RuntimeException при вызове метода сервиса:
javax.ejb.EJBException: Illegal non-business method access on no-interface view
Как ошибка получается?
Пусть есть крайне полезный сервис:
@Stateless
public class MyService {

    public String getAnswer() {
        return "42";
    }

    protected String getQuestion() {
        return "20 + 22 = ?";
    }
}
И также есть сервис (расположенный в том же пакете), обращающийся к первому в процессе выполнения какой-то своей логики:
@Stateless
public class MyClientService {

    @Inject
    private MyService myService;

    public String getPageHeader() {
        return myService.getQuestion() + myService.getAnswer();
    }
}
Чем нам так нравятся последние версии EJB?
Например, не нужно делать лишнюю и утомительную работу по написанию никому не нужных home-интерфейсов для сервисов. Объявляем сервис @Stateless, контейнер вставляет его куда надо с помощью @Inject/@EJB и вперёд. Смотрите сами: приведённый код достаточно ясен и краток. И даже компилируется... Вот только не выполняется, выбрасывая обозначенную выше ошибку.

Почему?
А вот не надо забывать, что механизмы работы EJB особенно не поменялись. Все эти позволения не писать лишнего - некоторый синтаксический сахар (хотя и очень классный). И на самом деле интерфейс для сервиса всё равно будет создан. И всё равно прокси-объект "над нашим сервисом" будет реализовывать этот автоматически созданный интерфейс. И войдут в него только public-методы. Другими словами, если грубо, то получится следующее:
public interface MyServiceIntf extends EJBHome {

  public String getAnswer();
}
Как видно, метода getQuestion() здесь нет. И именно этот интерфейс будет иметь переменная myService в MyClientService. А потому и происходит RuntimeException, когда мы пытаемся сделать вызов
myService.getAnswer()
При этом компилятор не показывает ошибок: с его точки зрения всё верно: вызов protected-методов внутри одного пакета разрешён.

среда, 26 октября 2011 г.

Автоматический update, выполняемый hibernate

Сегодня столкнулся со следующей проблемой в j2ee приложении, использующем jpa (hibernate):
Обычный поисковый select-запрос, реализованный с помощью Criteria API, после своего выполнения отсылал update-запрос базе. В большинстве случаев в этом не было ничего критичного, ну разве что перфоманс слегка страдал. Вот никто и не замечал... Сегодня же select выполнялся ко view, поэтому следующий за ним update вызвал ошибку.
Caused by: java.sql.SQLSyntaxErrorException: ORA-01031: insufficient privileges
Caused by: org.hibernate.exception.SQLGrammarException: could not update
Объяснение эффекта достаточно простым оказалось, но тем не менее.Всё работает абсолютно так, как должно быть: в классе был метод @PostLoad, проверяющий определённые строковые поля на null и заменяющий их на пустые строки. После окончания работы с данными Hibernate проверял объект, видел изменения и считал необходимым сделать аналогичные изменения в БД. Отсюда и update, отсюда и ошибка шла. Ещё чаще подобные "нежелательные" апдейты встречаются, когда orm работает через методы-аксессоры, а не через поля, а программисты спасаются от NullPointerException, делая вот такие getter'ы:
public String getStr() {
    return str == null ? "" : str;
}
Вообще, любые изменения приводят к такому вот эффекту... Например замена "Ё" на "Е", поэтому надо помнить об этой особенности.