barbitoff programmer`s blog

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

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

YUI DataTable: определение редактируемой записи и её индекса из функции-валидатора

Функция-валидатор редактора ячеек таблицы вызывается в контексте этой таблицы, которая, следовательно, доступна валидатору через this. Получить редактируемую запись (Record) можно вызовом:
this.getCellEditor().getRecord()
Таким образом, можно из валидатора обращаться к другим полям той записи, к которой относится валидируемая ячейка (если, скажем, нужно сравнить новое значение с значением в другой ячейке той же строки). 
Определить индекс редактируемой записи в RecordSet табицы можно следующим образом:
this.getRecordIndex(this.getCellEditor().getRecord())

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

Orbeon xforms-jsp и теги <script> внутри <head> на включаемых jsp-страницах

Наткнулся тут на интересные грабли при размещении jsp-страниц в директории xforms-jsp веб-приложения Orbeon`а: мало того, что генерируемая jsp-шкой html-страница должна быть валидным xml (с чем я ещё готов как-то мириться), так ещё к тому же Orbeon умудряется по своему усмотрению переносить в пределах документа теги <script>. 
А именно: пусть есть некоторая jsp-страница header.inc.jsp, включаемая в другую страницу index.jsp с помощью тега  <jsp:include>. Тогда, если на странице header.inc.jsp в блоке <head> есть теги <script> с inline яваскриптом или со ссылками на внешние скрипты, в итоговом html-коде, генерируемом страницей index.jsp, теги <script> окажутся после закрывающегося тега </body>, а вовсе не в <head>. 
Такое поведение бывает крайней неудобно, например, когда переменные из js-файлов или inline js-кода используются в теле страницы т.к. предполагается, что js уже загружен в <head>. Единственное пока найденное мною решение - перенести теги <script> из <head> в начало тега <body>.
Вся эта неразбериха вызвана тем, что к jsp-страницам в директории xforms-jsp применяется фильтр org.orbeon.oxf.servlet.OrbeonXFormsFilter, выполняющий рендеринг xform`ы из xml в html (благодаря этому jsp-страницы в этой директории могут содержать xforms-код, который будет корректно отрендерен в html). 

среда, 25 января 2012 г.

JavaScript: Копирование и объединение объектов

Для копирования объектов и их объединения можно использовать средства Yahoo UI 2:
YAHOO.lang.augmentObject({},obj)
YAHOO.lang.merge(obj1, obj2)
Проблема этих методов в том, что они не выполняют рекурсивное копирование полей объектов в случае, если они сами являются объектами. Проверяется это просто:
var a = {a:{a:"a",b:"b"}}
alert(a.a.a);// выведет "а"
var b = YAHOO.lang.augmentObject({},a);
alert(b.a.a);// выведет "а"
a.a.a = "b"alert(a.a.a);// выведет "b", что естественноalert(b.a.a);// выведет "b", что говорит о том, что поле b.a ссылается реально на тот же объект, что и a.a
В принципе, можно было и не проверять, я просто посмотреть в исходниках, где явно видно, что поля переносятся простым присваиванием (http://developer.yahoo.com/yui/docs/Lang.js.html). Т.к. из тех же исходников видно, что метод merge вызывается augmentObject, и, следовательно, при его использовании полученный объект также будет ссылаться своими полями на теже объекты, что и поля исходных объектов.

Поэтому пришлось написать подобные функции самому (copyObj имеет дополнительный функционал, копируя массивы не как объекты, а как массивы, а также не копируя функции, оставляя их как есть):
/**
 * Копирует объект (рекурсивно)
 * Аналогична YAHOO.lang.augmentObject({},obj), только выполняет копирование полей
 * рекурсивно, т.е. если поле - объект, то он тоже копируется.
 */

function copyObj(obj)
{
    // если передан не объект - копировать не надо, возвращаем значение. Функции тоже не копируем
    if(!(obj instanceof Object) || obj instanceof Function)
        return obj;
    // иначе - производим рекурсивное копирование всех полей (в т.ч. унаследованных). Объекты возвращаем
    // как объекты, массивы - как массивы  
    var ret;
    if(obj instanceof Array)
        ret = new Array();
    else
        ret = Object();

    for(var key in obj)
        ret[key] = copyObj(obj[key]);
    return ret;  
}

/**
 * Объединяет объекты, возвращая новый объект
 * (все поля нового объекта являются копиями (рекурсивными, т.е. полнями)
 * переданных объектов, т.е. изменение переданных объектов никак не повлияет
 * на результат, полученный с помощью данной функции)
 * Аналогична YAHOO.lang.merge, только копирование полей из переданных объектов выполняется
 * рекурсивно (т.е. если поля переданных в качестве аргументов объектов сами являются объектами,
 * они копируются в выходной объект не присваиванием, а вызовом copyObj, которая рекурсивно копирует
 * поле в новый объект)
 * TODO: распространить на любое число объектов-аргументов
 */
function mergeObjects(obj1, obj2)
{
    var ret = copyObj(obj1);
    for(var key in obj2)
        ret[key] = copyObj(obj2[key]);
    return ret;

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

Java: преобразование спецсимволов в соответствующие HTML сущности (аналог php-функции htmlentities)

Для этого есть статический метод StringEscapeUtils.escapeHtml()* из библиотеки Apache Commons. Помимо этого метода, данный класс содержит много других полезных методов для экранирования / разэкранирования строк в соответствии с правилами Java, JavaScript, HTML, XML, SQL и CSV.

* - или два метода: escapeHtml4 и escapeHtml3 в библиотеке Apache Commons версии 3.

Регулярные выражения в Java: удаление из строки не-ASCII символов

Удаление из строки всех символов, не входящих в набор ASCII, можно сделать с помощью регулярного выражения, использовав POSIX класс символов \p{ASCII}:
mystr.replaceAll("[^\\p{ASCII}]", "");

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

Определение в Firefox, что загружено в iframe: html или файл

В Firefox в случае, если в iframe загружается файл, свойство location устанавливается в "about:blank",  что можно использовать для проверки:
if(iframeElement.contentWindow.location == 'about:blank')
     // значит в iframe загрузился файл
else
    // в iframe загружен html-документ (точнее, пользователю не было показано диалоговое окно сохранения файла, возможно, при других mime-типах, отображаемых в браузере, мы тоже попадем в эту ветку, я не проверял)
В IE в обоих случаях в location будет src iframe`а.

Также, как вариант, в Firefox можно воспользоваться свойством iframeElement.contentWindow.document.inputEncoding, которое будет null при загрузке файла и не-null для HTML. В IE этого свойства нет вообще.

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

Проект под контролем git и svn одновременно

Возникла тут необходимость одновременно использовать для контроля версий проекта и git, и svn. Всё бы хорошо, но служебные файл мешают, пытаясь залезть в коммит конкурирующей VCS. Заигнорить служебные файлы гита просто, добавлением в игнор svn`а диретории .git в корне проекта. С svn сложнее, ведь она раскидывает свои ".svn" по всем подпапкам. Но и тут оказалось всё несложно: открываем (или создаем) файл .gitignore в корне проекта, и записываем туда строчку ".svn" (без кавычек и слеша в начале). Всё, теперь git будет игнорить все директории .svn и их содержимое.

среда, 18 января 2012 г.

Просмотр HTTP-заголовков в Firefox

Смотреть HTTP-заголовки запросов/ответов позволяет дополнение HttpFox. Помимо заголовков, можно смотреть куки, данные POST и содержимое ответа сервера.

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

iframe onLoad и загрузка файлов

Проблема:
Используется скрытый iframe для загрузки файлов (из JS устанавливается src заданного в html-разметке iframe`а равным URL`у, по которому сервер выдает необходимый файл). Необходимо после ответа сервера (когда браузер открывает окно сохранения файла) вызвать callback-функцию. Проблема заключается в том, что если содержимое iframe не является html-ем (точнее, Content-Type заголовок является таковым, что браузер открывает окно сохранения), событие onLoad не срабатывает (по крайней мере в Firefox, в IE однако все ок).

Решение:
Вместо динамической установки src iframe`а создавать iframe каждый раз заново, тогда onLoad срабатывает:
document.getElementById("reportLoadIframeDiv").innerHTML = '<iframe src="getFileServlet" onload="myCallback()"></iframe>';
Видимо, загрузка файла в iframe не вызывает onLoad только при повторной загрузке iframe (т.к. при задании iframe html`ем он сначала загружается с пустым src, и его загрузка после динамической установки src является уже повторной). При приведенном выше подходе iframe каждый раз - новый, и при первой же своей загрузке он выдает нам файл.
Однако, приведенный выше код теперь не работает в IE: событие onload в таком случае не происходит (тогда как при статичном iframe все работало). Спасает специфичное для IE событие "onreadystatechange", оно вызывается в любом случае, даже при загрузке файла:
document.getElementById("reportLoadIframeDiv").innerHTML = '<iframe src="getFileServlet" onload="myCallback()" onreadystatechange="myCallback()"></iframe>';
 При таком подходе в FF вызовется обработчик onload, в IE - onreadystatechange.


В общем, выводы из вышеописанного такие:
  1. При статичном задании iframe с обработчиком onLoad в HTML-коде при динамическом изменении src и последующей загрузке файла в этот iframe обработчик не вызывается в Firefox, но вызывается в IE
  2. При динамическом создании iframe с необходимым для загрузки файла src обработчик onLoad вызывается в Firefox и не вызывается в IE (причем в IE он не вызывается, даже если в iframe загружается не файл, а просто HTML)
  3. При динамическом создании iframe при его загрузке в IE всегда вызывается onreadystatechange, независимо от того, что загружается в iframe, файл или html. При этом в Firefox данный обработчик всегда игнорируется.

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);

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

Небольшое HOWTO по прикручиванию авторизации и аутентификации с помощью Spring Security к уже существующему веб-приложению

Итак, изначально ситуация была следующая: есть небольшое веб-приложение на JSP, не использующее никаких фреймворков. Задача - добавить в него авторизацию и аутентификацию с помощью фреймворка Spring Security.
Поискав в инете я так и не нашел какого-нибудь работающего примера как сделать так, чтобы "оно заработало", а потом уже разбираться в тонкостях и возможностях Spring Security. Примеры были либо непонятные, либо по Spring Security 2.х, тогда как мне хотелось работать сразу с 3.х (точнее, 3.1).
После некоторых проб и ошибок получилось найти следующий алгоритм:
1) Подключаем к проекту библиотеки спринга:
  • org.springframework.core-3.1.0.RELEASE.jar
  • org.springframework.beans-3.1.0.RELEASE.jar
  • org.springframework.context-3.1.0.RELEASE.jar
  • org.springframework.expression-3.1.0.RELEASE.jar
  • org.springframework.web-3.1.0.RELEASE.jar
2) Подключаем библиотеки Spring Security:
  • spring-security-core-3.1.0.RELEASE.jar
  • spring-security-config-3.1.0.RELEASE.jar
  • spring-security-web-3.1.0.RELEASE.jar
3) Добавляем в web.xml следующие строки:
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
- для инициализации контекста спринга в целом
    <context-param>
        <param-name>
            contextConfigLocation
            </param-name>
        <param-value>
            /WEB-INF/spring-security.xml
        </param-value>
    </context-param> 
- для установки пути к файлу конфигурации Spring
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter> 
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping> 
- для добавления фильтра Spring Security на все страницы

4) Создаем в WEB-INF/ файл конфигурации спринга spring-security.xml и добавляем в него:

<beans:beans xmlns="http://www.springframework.org/schema/security"
  xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
      http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">


    <!--Public resources -->
    <http pattern="/js/**" security="none"/>
    <http pattern="/style/**" security="none"/>
    <http pattern="/img/**" security="none"/>

    <http pattern="/login.jsp" security="none"/> 
    <http auto-config="true" access-denied-page="/login.jsp?accessDeniedError=1">
        <intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN
" />
        <form-login authentication-failure-url="/login.jsp?loginError=1"
                    login-page="/login.jsp"
                    login-processing-url="/j_spring_security_check"/>
    </http>

    <authentication-manager>
        <authentication-provider >
            <password-encoder hash="md5" />
            <user-service>
                <user name="test" password="098f6bcd4621d373cade4e832627b4f6" authorities="ROLE_USER,ROLE_ADMIN" />
            </user-service>
        </authentication-provider>
    </authentication-manager>
    <beans:bean id="loginSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler" >
        <beans:property name="defaultTargetUrl" value="/index.jsp"/>
        <!--beans:property name="redirectStrategy" ref="defaultRedirectStrategy"/-->
    </beans:bean>
    <beans:bean id="loginFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler" >
        <beans:property name="defaultFailureUrl" value="/login.jsp?loginError=1" />
    </beans:bean>
</beans:beans>
Что в приведенном выше конфиге делается:
  • Устанавливается страница, отображаемая если на запрошенный ресурс не хватает прав у пользователя: login.jsp?accessDeniedError=1
  • Устанавливается страница, на которой будет размещена форма для ввода логина / пароля: login.jsp (на неё идет редирект если пользователь ещё не авторизован). Эта страница будет, естественно, доступна не авторизованным пользователям.
  • Устанавливается страница, куда идет редирект при неуспешной авторизации: login.jsp?loginError=1
  • Устанавливается страница, на котороую идет редирект после успешной авторизации: index.jsp
  • Пользователи задаются прямо в конфигурации, создается один пользователь с логином / паролем test/test (пароль захэширован по алгоритму md5) и ролями USER и ADMIN
  • Устанавливается путь, по которому размещается обработчик авторизации: j_spring_security_check (обработчик будет размещен спрингом автоматически, поэтому без разницы, какой урл указывать, главное чтобы он не конфликтовал с уже существующими)
  • Устанавливается ограничение на доступ ко всем страницам, кроме login.jsp, позволяющее иметь к этой странице доступ только пользователям с ролью USER или ADMIN
  • Доступ к всем элементам директорий js, style и img делается открытым (не требующим аутентификации)
5) Создаем форму авторизации. В ней указываем в качестве action="j_spring_security_check", а поля имени пользователя и пароля называем, соответственно, "j_username" и "j_password".
6) Разавторизация может осуществляется кодом:
SecurityContextHolder.getContext().setAuthentication(null);
Правда, этот код нельзя вызывать на login.jsp, т.к. она помечена как незащищенная, и, следовательно, на ней контекст безопасности недоступен (такой вызов попросту ни к чему не приведет). Чтобы решить эту проблему, понадобится следующая конфигурация:
     <!--Public resources -->
    <http pattern="/js/**" security="none"/>
    <http pattern="/style/**" security="none"/>
    <http pattern="/img/**" security="none"/>
    <!-- login.jsp is under spring security control, but is accessible anonimously -->
    <http pattern="/login.jsp">
        <intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
        <form-login authentication-failure-url="/login.jsp?loginError=1"
                    login-page="/login.jsp"
                    login-processing-url="/j_spring_security_check"/>
    </http>
    <http auto-config="true" access-denied-page="/login.jsp?accessDeniedError=1">
        <intercept-url pattern="/**" access="ROLE_USER" />
        <form-login authentication-failure-url="/login.jsp?loginError=1"
                    login-page="/login.jsp"
                    login-processing-url="/j_spring_security_check"/>
    </http>
В прочем, разавторизацию можно сделать и проще, перейдя на URL "/j_spring_security_logout" (этот URL можно при желании переопределить в конфиге). После перехода на него произойдет разавторизация и редирект на страницу логина. 

java.lang.NoSuchMethodError: org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.getLocalName(Lorg/w3c/dom/Node;)Ljava/lang/String;

Проблема:
При попытке задеплоить на Томкат веб-приложение, использующее Spring Security, вываливается исключение:
java.lang.NoSuchMethodError: org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.getLocalName(Lorg/w3c/dom/Node;)Ljava/lang/String;
Причина:
Spring Security 3.x используется с Spring 2.x.

Решение:
Заменить библиотеки Spring 2.x на Spring 3.x.

org.xml.sax.SAXParseException: cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'authentication-provider'

Проблема:
При попытке задеплоить на Томкат веб-приложение, использующее Spring Security, вываливается исключение, ругающееся на xml-файл конфигурации Спринга, точнее, на вложенный в <beans> тег <authentication-provider>:
org.xml.sax.SAXParseException: cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'authentication-provider'
Причина:
При переходе версий Spring Security с 2.x на 3.x изменилась схема конфигурационного файла, теперь тег <authentication-provider> должен быть вложен в <authentication-manager>.

Решение:
Поправить конфигурацию спринга, обернув <authentication-provider> в <authentication-manager>:

    <authentication-manager>
        <authentication-provider >
            <user-service>
                <user name="test" password="test" authorities="ROLE_USER,ROLE_ADMIN" />
                <user name="alex" password="847c6f184197dc1545d9891d42814a7d" authorities="ROLE_USER" />
                <user name="tim" password="0513111ff330e25c631b5d3e9c0a4aae" authorities="ROLE_USER" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

org.xml.sax.SAXParseException: cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'http'

Проблема:
При попытке задеплоить на Томкат веб-приложение, использующее Spring Security, вываливается исключение, ругающееся на xml-файл конфигурации Спринга, точнее, на вложенный в <beans> тег <http>:
org.xml.sax.SAXParseException: cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'http'
Решение:
Подключить в проект библиотеку spring-security-config.jar, содержащую схемы для конфигурационных файлов.

суббота, 7 января 2012 г.

GUI SNV-клиент для Debian: rapidsvn

rapidsvn - вполне удобный snv-клиент с графическим интерфейсом. 

1C-Bitrix на Debian

Для установки битрикса с кодировкой UTF-8 на установленный из репозитория Debian Apache2+php нужно изменить следующие настройки:
1. /etc/apache2/sites-available/default: в блоке <Directory /var/www/> установить:
AllowOverride All
2. /etc/php5/apache2/php.ini: раскомментировать и отредактировать следующие значения:
mbstring.internal_encoding = UTF-8
mbstring.func_overload = 2

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

Настройка тачпада Synaptics в Debian + XFCE

В Гноме тап по тачпаду "из коробки" работал как клик левой кнопкой мыши, в XFCE же почему-то - нет, что собственно и стало поводом поковыряться в конфигах иксов с целью настройки тачпада. Как ни странно, двухпальцевый скроллинг сразу же работал и там, и там (чего не было в Ubuntu, где приходилось ставить Synaptics`овскую утилиту для настройки), а вот поведение тапа двумя пальцами меня тоже не устраивало - оно воспринималось как правый клик, а мне хотелось бы чтобы как в винде - как клик центральной кнопкой, удобно открывать ссылки в браузере на новой вкладке =).
За основу была взята инструкция отсюда: http://www.crunchbang.ru/viewtopic.php?id=938, большое спасибо за неё автору. Я добавил в неё лишь двухпальцевый скроллинг, настройку тапа двумя пальцами и откалибровал скорость перемещения курсора.
Во-первых, убеждаемcя, что у нас действительно тачпад Synaptics:

root@eeedebian:/home/aspirin# egrep -i 'synap|alps|etps' /proc/bus/input/devices
N: Name="SynPS/2 Synaptics TouchPad"
Нам понадобиться модуль synaptics для иксов, поэтому он тоже должен быть установлен:
root@eeedebian:/home/aspirin# aptitude search xserver-xorg-input-synaptics
i A xserver-xorg-input-synaptics    - Synaptics TouchPad driver for X.Org server
p   xserver-xorg-input-synaptics-de - Synaptics TouchPad driver for X.Org server
В этот пакет также входит удобная утилита для мониторинга событий тачпада synclient, которая в дальнейшем пригодится.
Теперь идем в /etc/X11/xorg.conf. Если у Вас его нет - как его сгенерировать, я писал тут: http://barbitoff.blogspot.com/2011/06/touchscreen-egalax-eeepc-t101mt-debian.html, под пунктом 2. Там же написано, как перезапускать искы.
В xorg.conf добавляем следующие строчки (секции, которые уже имеются, добавлять нет необходимости - можно просто вставить в них ещё одну строчку):
Section "ServerLayout"
     InputDevice "Synaptics Touchpad" "SendCoreEvents"
EndSection
...
Section "Module"
Load "synaptics"
EndSection
...
Section "InputDevice"
  Identifier      "Synaptics Touchpad"
  Driver          "synaptics"
#  Option          "SendCoreEvents"        "true"
  Option          "Device"                "/dev/psaux"
  Option          "Protocol"              "auto-dev"
  Option          "HorizEdgeScroll"       "0" # выключаем скроллинг границами тачпада
  Option          "VertEdgeScroll"       "0" # выключаем скроллинг границами тачпада
  Option    "VertTwoFingerScroll" "1" # включаем двухпальцевый скроллинг
  Option  "HorizTwoFingerScroll" "1" # включаем двухпальцевый скроллинг
  Option  "EmulateTwoFingerMinW" "8" # если толщина больше 8 - значит 2 пальца (мой один палец вообще воспринимается не больше, чем 6)
  Option  "EmulateTwoFingerMinZ" "60" # при 70 иногда "проскальзывает", воспринимая 2 пальца за один
  Option          "SHMConfig"             "true"
  Option          "Emulate3Buttons"   "on"
  Option          "LeftEdge"      "1700"
  Option          "RightEdge"     "5300"
  Option          "TopEdge"       "1700"
  Option          "BottomEdge"    "4200"
  Option          "FingerLow"     "35"
  Option          "FingerHigh"    "40"
  Option          "MaxTapTime"    "180"
  Option          "MaxTapMove"    "220"
  Option          "VertScrollDelta" "100"
  Option          "HorizScrollDelta" "50"
  Option          "MinSpeed"      "0.24"
  Option          "MaxSpeed"      "0.48"
  Option          "AccelFactor" "0.00100"
  Option          "TapButton1" "1" #тап одним пальцем -> левый клик
  Option          "TapButton2" "2" #тап двумя пальцами -> центральный клик
  Option          "TapButton3" "3" #эх, жаль, что при эмуляции многопальцевости 3 пальца почему-то не детектируются, а то можно было бы на 3 пальца навесить правую кнопку мыши
EndSection


Собственно, что настроено вышеуказанным конфигом:
  • Выключена прокрутка краями тачпада, включена прокрутка двумя пальцами
  • Скорость движения курсора настраивается параметрами MinSpeed и MaxSpeed, тут уж, как говорится, на вкус и цвет.. Мне нравится, чтобы курсор бегал пошустрее (наверное из-за мыши с 3000dpi), так что я эти параметры увеличил.
  • Опция TapButton1 включила заветный левый клип по тапу одним пальцем
  • Опцией TapButton2 я включил нажатие центральной кнопки по тапу двумя пальцами. Тап 3 пальцами к сожалению не заработал, так как у моего тачпада родной поддержки многопальцевости видимо нет, а при эмуляции драйвер распознает только 2 пальца. 
Кстати об эмуляции: необходимость её использования определить просто: запускаем synclient -m 100, и нажимаем по тачпаду несколькоми пальцами стразу. Если в столбце "f" все время единица - значит тач не умеет определять многопальцевое нажатие. В таком случае продолжаем нажимать на тач то одним, то двумя пальцами, и смотреть на изменение значений в столбцах "w" (ширина нажатия) и "z" (сила нажатия). Наша задача - определить порог по ширине и силе нажатия, отличающий нажатие одного пальца от нажатий двух. Причем важнее всего именно параметр "w", так как сила может быть примерно одинаковой для обоих случаев. Для моих пальцев "w" при нажатии одним пальцем равняется 5, максимум 6, а двумя - не менее 10. Поэтому я взял на всякий случай пороговое значение 8, и присвоил его опции "EmulateTwoFingerMinW". В качестве "EmulateTwoFingerMinZ" я взял 60, хотя порой можно и одним пальцем с такой силой тыкнуть, но ложных срабатываний это не вызывает. А вот установка значения, скажем, 70, приводит иногда к тому, что двухпальцевый скроллинг воспринимается просто как перемещение курсора, если нажать с недостаточной силой. В принципе, при такой эмуляции многопальцевости не составило бы труда определять и нажатия тремя пальцами, установив соответствующий порог ширины нажатия, но программисты synaptics не стали с этим заморачиваться, а жаль.

После изменения конфига перезапускаем иксы и пробуем.

По всем опциям настройки тачпада есть неплохое описание в man synaptics, ещё пара полезностей (как, например, отключение тачпада при наборе на клавиатуре, что мне, лично, не нужно) достаточно доступно описана тут: http://www.crunchbang.ru/viewtopic.php?id=938, так что успехов =).