barbitoff programmer`s blog

Здесь я публикую заметки из программерской жизни: грабли, на которые мне случилось наступить, проблемы, для которых было найдено элегантное (или не очень) решение, а также все, с чем мне пришлось столкнуться и чем хотелось бы поделиться =)
PS Если хотите меня поблагодарить - на странице есть 3 места, чтобы это сделать =)

вторник, 17 января 2012 г.

kettle в контейнере сервлетов и dom4j / poi - зависимости

При выполнении kettle-преобразований в контейнере сервлетов (по крайней мере, на Tomcat) библиотеки poi и dom4j, от которых зависит kettle, нужно не включать в war-файл, а класть в директорию lib/ томката, иначе, если библиотеки присутствуют только в war-нике, kettle вообще их не видит, если же они присутствуют и там, и там, то происходят ошибки вида:
java.lang.ClassCastException: org.dom4j.DocumentFactory cannot be cast to org.dom4j.DocumentFactory
java.lang.LinkageError: loader constraint violation: loader (instance of org/apache/catalina/loader/WebappClassLoader) previously initiated loading for a different type with name "org/apache/poi/ss/usermodel/Workbook"
Что говорит о том, видимо, что класс загружается дважды разными класс-лоадерами.
Вообще, в /lib томката придется положить следующие библиотеки для случая работы с Excel (xls / xlsx) таблицами:

  • dom4j
  • jaxen
  • poi-ooxml
  • poi
  • xbean
  • oooxml-schemas

IE, загрузка файлов и Cache-Control: no-cache, no-store

Проблема:
Заголовок "Cache-Control: no-cache, no-store", предусмотренный в HTTP/1.1 для предотвращения кэширования информации браузерами и прокси-серверами, будучи установленным для ответа, отсылающего файл, вызывает у Internet Explorer появление сообщения (правда не в 100% случаев, я так и не нашел закономерности):
"Не удалось открыть этот интернет-узел. Запрошенный узел недоступен или не найден..."
и файл, естественно, не загружается.

Решение:
Ничего не поделаешь, для файлов заголовок "Cache-Control: no-cache, no-store" придется не ставить. Впрочем, я думаю IE и так не будет кэшировать файлы =)

понедельник, 16 января 2012 г.

HttpServlet: установка Content-Disposition заголовка для скачивания файла с русскими символами в имени

Проблема:
Задача следующая: отправить в браузер файл с русскими символами в имени. Проблема заключается в том, что если помещать имя файла в заголовок непосредственно кодом:
response.setHeader("Content-Disposition", "attachment; filename="+fileName);
вместо корректного имени получаем кракозябры, а IE 8 вообще выкидывает невнятное сообщение о невозможности загрузки.

Решение:
Ниже приведет кроссбраузерный код (работает в IE 8, FF 9, Opera 11.60, Chrome 16):

        if(request.getHeader("user-agent")!=null && request.getHeader("user-agent").indexOf("Firefox")!=-1) // Firefox
            response.setHeader("Content-Disposition", "attachment; filename*=utf-8''"+java.net.URLEncoder.encode(fileName, "UTF-8") + ";");
        else
            response.setHeader("Content-Disposition", "attachment; filename="+java.net.URLEncoder.encode(fileName, "UTF-8"));



пятница, 13 января 2012 г.

Spring Security: расширение информации о принципале безопасности

Столкнулся тут с необходимостью хранения в контексте безопасности дополнительной информации о пользователе (помимо имени и прав), например, полного ФИО. При этом в качестве UserDetailsService использовался org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl, извлекающий информацию о пользователе из БД PostgreSQL (из таблиц users и authorities), и кладущий в контекст безопасности объект класса org.springframework.security.core.userdetails.User.
Для того, чтобы реализовать хранение дополнительных данных о пользователе, нужно:
  1. Добавить соотв. колонки для хранения доп. информации в таблицу users базы данных
  2. Реализовать свой класс, реализующий интерфейс UserDetails, представляющий пользователя, с необходимыми свойствами для хранения доп. информации и методами доступа к ним. Класс можно унаследовать от org.springframework.security.core.userdetails.User, определив необходимые конструкторы, переопределив метод toString и добавив все необходимые свойства / методы для хранения доп. информации о пользователе.
  3. Реализовать свой класс, реализующий интерфейс UserDetailsService, который сохранял бы в контексте безопасности объект класса из п.2. Можно унаследовать org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl, переопределив метод loadUsersByUsername, извлекающий информацию из БД и метод createUserDetails, сохраняющий пользователя в контексте безопасности.
  4. Настроить Spring Security на использование созданного UserDetailsService с помощью атрибута user-service-ref тега authentication-provider и бина, инициализирующего объект созданного на п.3. класса:

        <beans:bean id="userDetailsService"
              class="my.package.myUserDetailsServiceImpl">
            <beans:property name="dataSource" ref="dataSource"/>
        </beans:bean>
  5.     <authentication-manager>
            <authentication-provider user-service-ref='myUserDetailsService'>
                <password-encoder hash="md5" />
            </authentication-provider>
        </authentication-manager>  

  6. Всё, теперь полученный вызовом SecurityContextHolder.getContext().getAuthentication().getPrincipal() объект можно приводить к созданному в п.2 классу и вызывать методы доступа к дополнительным информационным полям.

cannot find symbol symbol : constructor Service(java.net.URL,javax.xml.namespace.QName,javax.xml.ws.WebServiceFeature[]) location: class javax.xml.ws.Service super(wsdlLocation, CONFIRMSERVICE_QNAME, features);

Проблема: 
При компиляции проекта, использующего JAX-WS и клиент веб-сервиса, сгенерированный по WSDL, вылетает ошибка, ругающаяся на код, сгенерированный wsimport`ом:
cannot find symbol symbol : constructor Service(java.net.URL,javax.xml.namespace.QName,javax.xml.ws.WebServiceFeature[]) location: class javax.xml.ws.Service super(wsdlLocation, CONFIRMSERVICE_QNAME, features);
Причина:
Wsimport и компилятор используют разные версии JAX-WS (например, wsimport использует 2.1, а компилятор - 2.2).

Решение:
Исправить либо библиотеки, используемые компилятором, либо используемые wsimport`ом (первый использует библиотеки, подключенные к проекту, а второй - библиотеки JDK, их можно переопределить через endorsed-механизм).

четверг, 12 января 2012 г.

Spring Security: определение собственного authentication-provider

Путь, к примеру, нужно создать свой провайдер аутентификации на основе org.springframework.security.authentication.dao.DaoAuthenticationProvider. Пусть раньше провайдер определялся в конфиге так:
    <authentication-manager>
        <authentication-provider user-service-ref='userDetailsService'>
            <password-encoder hash="md5" />
        </authentication-provider>
    </authentication-manager>  
Тогда:

  1. Создаем свой класс, наследуя org.springframework.security.authentication.dao.DaoAuthenticationProvider. Пусть, к пример, наш класс будет называться my.package.MyAuthenticationProvider
  2. В конфиге пишем:
    <beans:bean id="md5PassEncoder"
          class="org.springframework.security.authentication.encoding.Md5PasswordEncoder">
    </beans:bean>
   
    <beans:bean id="authProvider" class="my.package.MyAuthenticationProvider">
        <beans:property name="userDetailsService" ref="userDetailsService"/>
        <beans:property name="passwordEncoder" ref="md5PassEncoder"/>
    </beans:bean>  
 
    <authentication-manager>
        <authentication-provider ref='authProvider'/>
    </authentication-manager>  
Такой конфиг будет аналогичен приведенном выше, однако будет использовать наш класс провайдера.

Java: преобразование массива в ArrayList

String[] myArr;
ArrayList<String> myArrList = Arrays.asList(myStrArr);