barbitoff programmer`s blog

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

среда, 29 февраля 2012 г.

Модификация Path в заголовке Set-Cookie, отправляемом backend-сервером, сидящим за Apache2 с mod_proxy

Проблема:

За Apache2 веб-сервером сидит backend-сервер Tomcat, при этом путь на Apache2, по которому ведется проксирование, отличается от пути на Tomcat`е, и, следовательно, куки, устанавливаемые томкатом, оказываются недействительными, т.к. в заголовке Set-Cookie браузер видит не тот Path, который указан в URL`е.

Решение:

mod_proxy предусматривает модификацию отправляемого клиенту заголовка Set-Cookie директивой ProxyPassReverseCookiePath. Например, если запросы в корень апача проксируются на путь /mypath томката, директива будет выглядеть следующим образом:
ProxyPassReverseCookiePath /mypath /
Тогда заголовки вида:
Set-Cookie JSESSIONID=2CAF42328709F0A93ABD515651002089; Path=/mypath
будут преобразовываться проксёй в:
Set-Cookie JSESSIONID=2CAF42328709F0A93ABD515651002089; Path=/

Attribute value is quoted with " which must be escaped when used within the value

Проблема:

При использовании следующего кода:

<jsp:include page="includes/dataTable.inc.jsp" >
  <jsp:param name="fileName" value="<%=this.getServletContext().getRealPath("/WEB-INF/myfile.txt")%>" />
</jsp:include>
вываливается исключение:
org.apache.jasper.JasperException: Attribute value this.getServletContext().getRealPath("/WEB-INF/myfile.txt") is quoted with " which must be escaped when used within the value
Причем под Windows в Tomcat 6.0.10 все ок, а вот под Debian с версией Tomcat`а 6.0.28 ломается.

Причина:

Действительно, в соответствии со спецификацией JSP 2.0 так писать нельзя (https://issues.apache.org/bugzilla/show_bug.cgi?id=45015). И, похоже, в 6.0.28 некорректное поведение томката было поправлено.

Решение:

Например, заключить значение атрибута в одинарные кавычки:
<jsp:include page="includes/dataTable.inc.jsp" >  <jsp:param name="fileName" value='<%=this.getServletContext().getRealPath("/WEB-INF/myfile.txt")%>' /></jsp:include>
Можно, в принципе, и экранировать кавычки вокруг пути к файлу, но NetBeans подсвечивает такой синтаксис, как неверный.

Атрибут redirectPort тега Connector конфигурации Tomcat

Атрибут redirectPort указывает, на какой порт нужно перенаправить пользователя, пытающегося подключиться к коннектору в случае, если для ресурса, к которому он обращается, установлено требование в защищенном канале (тегом <transport-guarantee> в <security-constraint>), а данный коннектор не является защищенным. Естественно, указанный порт должен кто-то слушать (обычно, другой коннектор того же Tomcat`а).

Отключение автозаполнения полей форм

Иногда запоминание браузером значений, введенных / выбранных пользователем в поле формы, мало того, что неудобно, так ещё и портит корректную работу приложения. Например, если некоторое поле играет роль фильтра, работающего по onChange, то при перезагрузке страницы браузер подставит в него предыдущее введенное / выбранное значение, тогда как фильтр на самом деле не отработает, т.к. событие "change" сгенерировано не будет. Отключается это установкой нестандартизированного атрибута autocomplete в значение "off" (который может быть задан для отдельного input`а / select`а, или для формы в целом):

<select onChange="applyFilter()" autocomplete="off">
      <option ...
Чтобы не ломалась валидация документа на соответствие требованиям стандарта, можно расширить DTD документа следующим образом (спасибо lany):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
<!ATTLIST input autocomplete CDATA #IMPLIED>
]>
Приведенный пример можно расширить для разрешения использования атрибута "autocomplete" у тегов select и form.

PS. В HTML 5 атрибут "autocomplete" для тега "input" стал стандартизированным.

Установка ширины столбца YUI2 DataTable

По-умолчанию ширины столбцов таблицы выбираются автоматически в зависимости от содержимого ячеек. Это порой неудобно: например, при переходе между страницами в многостраничной таблице ширины столбцов прыгают. 
Управлять шириной колонок YUI2 DataTable можно свойствами width, minWidth и maxAutoWidth. Проблема только в том, что параметр width YUI вообще почему-то игнорирует, а minWidth и maxAutoWidth приводят к тому, что при загрузке таблицы она дергается, т.к. проверка и корректировка ширины столбцов в соответствии с этими параметрами происходит уже после отрисовки таблицы (на это обращено внимание в документации YUI). 
Выход - использовать свойство столбца "className", позволяющее назначить класс всем ячейка таблицы и управлять их шириной с помощью CSS.

вторник, 28 февраля 2012 г.

Работа с CSV в Java

Для работы с CSV (записи / чтения) есть не одна библиотека, например, Java CSV Library, скачать которую можно тут: http://www.javenue.info/files/csv.zip или тут: http://sourceforge.net/projects/javacsv/. В архиве имеется как скомпилированный jar-ник, так и сорцы, в т.ч. Unit-тест, по которому можно без труда разобраться, как этим творением пользоваться. 

Excel не открывает csv-файлы, которые сам же только что сохранил

Проблема:

Создаю простенькую табличку, в ячейке 1:1 которой находится значение "ID". Сохраняю как csv, закрываю, пытаюсь открыть. Excel сначала говорит что формат файла отличается от указываемого в расширении (что уже странно), после чего выдает сообщение "Ошибка чтения записи #n", а затем - "Приложению Microsoft Excel не удается преобразовать некоторые ячейки. Число обнаруженных ошибок: 1" и файл открывается пустым.

Причина:

Оказывается, если файл начинается с последовательности заглавных букв "ID", то Excel полагает, что формат этого файла SYLK, и, естественно, открыть его как SYLK у него не получается. Этот баг описан на саппорте MS и, судя по всему, является уже старым и исправлять его никто не собирается.

Решение:

Перед "ID" в ячейке 1:1 поставить, скажем, пробел. Тогда файл откроется нормально.

Ломающаяся кодировка при использовании jsp:param для передачи параметра включаемой странице

Проблема:

При включении страницы тегом jsp:include с передачей ей параметров с помощью jsp:param бьются нелатинские символы в значении параметра, хотя все jsp-страницы используют кодировку UTF-8 и в server.xml для коннектора установлено URIEncoding ="UTF-8". При этом если вызывать включаемую страницу непосредственно и передавать ей эти же параметры с помощью GET, кодировка не бьется.

Решение:

На всех jsp-страницах перед работой с параметрами запроса / включением других страниц (а лучше просто первой строчкой кода страницы) необходимо явно задавать кодировку запроса как UTF-8:
<%
request.setCharacterEncoding("UTF-8");
%>

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

"Fast Forward Merge" в Git

Например, пусть у нас есть ветка master (на рисунках - фиолетовым цветом). Мы сделали branch, создав новую ветку mybranch (зеленым цветом), сделали в этой ветке некоторые изменения и делаем merge обратно (в ветке master делаем merge mybranch):
Если при этом между командами branch и merge ветка master не менялась, вместо того, чтобы сделать commit  в ветке master, Git просто переносит голову ветки master в голову ветки mybranch:
Это случился т.н. "Fast Forward Merge". Если мы хотим всё же сделать влив изменений в master именно commit`ом, чтобы создать красивую историю ветки maser, можно установить опцию --no-ff при выполнении merge. Тогда мы получим:
Однако такой подход не является хорошим тоном, почитать рекомендации можно в этой публикации: http://sandofsky.com/blog/git-workflow.html, спасибо огромное sandofsky за неё.

пятница, 24 февраля 2012 г.

Как сделать кнопку YAHOO.widget.Button недоступной (disabled)

Методов для управления доступностью кнопок не предусмотрено, но можно управлять ей через соответствующее свойство вызовом метода set:
myButton.set('disabled',true)

четверг, 23 февраля 2012 г.

QIP 2012: отключение значка в панели задач и вечно открытого мини-окошка в левом верхнем углу экрана

И значок в панели задач, и не закрывающееся мини-окошко в верхнем левом углу экрана - имхо достаточно раздражающие нововведения qip 2012, отключаются с помощью отключения модуля "Win7 - помошник 1.1".

среда, 22 февраля 2012 г.

xforms:case и xpath в атрибуте "selected"

В атрибуте "selected" использовать xpath нельзя, его значение интерпретируется как текст, и любое значение, отличное от true, приводит к скрытию case`а (если все case`ы оказываются скрыты, первый из них все же отображается). По крайней мере в Orbeon это так.

вторник, 21 февраля 2012 г.

Orbeon: отличие xpath-функций instance() и xxforms:instance()

xxforms:instance() выбирает сущность по идентификатору также, как и стандартная функция instance() с той лишь разницей, что:
  1. Не учитывает область видимости, заданную текущей моделью (выбранной атрибутом "model")
  2. Осуществляет поиск в областях видимости вышестоящих XBL-компонентов
, в то время как instance() ищет только в текущей модели текущего компонента.

воскресенье, 19 февраля 2012 г.

cmd: массовое создание символических ссылок

Задача:
Есть папка, содержащая набор подпапок с jpg-файлами:
fldr1
     - subfldr1
          - 1.jpg
          - 2.jpg
     - subfldr2
          - 1.jpg
          - 2.jpg
...

     - subfldrN
          - 1.jpg
          - 2.jpg
Нужно в некоторой другой папке создать символические ссылки на все файлы всех подпапок папки fldr1 (имена символических ссылок непринципиальны).

Решение:
Следующий bat-ник принимает на вход 2 параметра: имя папки-источника и имя папки для создания символических ссылок (все имена без слешей в конце и с обратными слешами), и создает в последней символические ссылки с файлами 1.jpg .... m.jpg:

@echo off
SETLOCAL EnableDelayedExpansion
SET curdir=%CD%
SET cnt=0
FOR /F "tokens=*" %%i  IN ('dir %1 /b') DO (
FOR /F "tokens=*" %%f  IN ('dir "%1\%%i" /b') DO ( mklink %2\!cnt!.jpg  "%1\%%i\%%f"
SET /a cnt+=1 )
)
pause
Очень полезно с учетом того, что ротатор обоев в Windows 7 не умеет смотреть в подпапки выбранной папки, поэтому, чтобы в ротации участвовали обои из разных подпапок одной папки, нужно сгрузить символические ссылки на них в какую-нибудь одну папку.

bat-ники: изменение переменных в цикле FOR

Если просто попробовать изменять переменную в теле FOR и выводить её на экран, то получим обескураживающий результат: переменная не меняет свое значение внутри цикла, хотя по выходу из него принимает корректное значение, т.е. при выполнении кода:
SET cnt=1
FOR /F "tokens=*" %%f  IN ('dir "%1" /b') DO ( echo %cnt%
SET /a cnt+=1 )
echo Total: %cnt%
получим результат наподобие:
1
1
1
....
1
Total:  285
Для того, чтобы в теле цикла виделось актуальное значение переменной, необходимо использовать SETLOCAL EnableDelayedExpansion, а значение переменной получать, обрамляя её имя не в "%", а в "!":
SETLOCAL EnableDelayedExpansion
FOR /F "tokens=*" %%f  IN ('dir "%1" /b') DO ( echo !cnt!
SET /a cnt+=1 )
echo !cnt!
Теперь вывод будет корректен:
1
2
...
284
Total: 285

суббота, 18 февраля 2012 г.

Синхронизация папок в Windows

Для синхронизации есть утилитка от Microsoft под названием SyncToy. Раньше пользовался vuBrief, он в целом пофункциональнее, но дружелебность его интерфейса явно хромает, а для моих целей (просто синхронизации папок на HDD и флехе) хватило и мелокомягкого творения, благо оно бесплатно. Обзор этой программки есть на текнете: http://technet.microsoft.com/ru-ru/magazine/2007.03.utilityspotlight.aspx. По моим ощущения она работает помедленнее vuBrief, но все равно скорость работы вполне приемлемая: на поиск изменений в синхронизируемых папках объемом 4Гб с 12к файлов (одна - на винте, вторая - на шустрой флешке) уходит около 20 сек.

пятница, 17 февраля 2012 г.

Windows 7: Добавление ещё одной Windows 7 в меню загрузки

Из-под админа:

  1. bcdedit /export D:\bcd.bak -- делаем бэкап BCD
  2. bcdedit /copy {current} /d "My old win7"
  3. bcdedit /set {хххххххх-хххх-хххх-хххх-хххххххххххх} device partition=X:
  4. bcdedit /set {хххххххх-хххх-хххх-хххх-хххххххххххх} osdevice partition=X:
  5. bcdedit /displayorder {хххххххх-хххх-хххх-хххх-хххххххххххх} /addlast
, где X: - буква раздела, на котором установлена винда, запись для загрузки которой нужно добавить, а {хххххххх-хххх-хххх-хххх-хххххххххххх} - номер, выведенный в консоль после выполнения bcdedit /copy.
Подробнее про bcdedit написано тут: http://www.oszone.net/10998/Edit_Boot_Menu

Восстановление пароля root в OpenSUSE

При появлении загрузчика Grub дописываем опцию загрузки init=/bin/bash, после загрузки выполняем passwd и меняем пароль. Перезагружаемся как обычно.

вторник, 14 февраля 2012 г.

Аналог GROUP_CONCAT из MySQL для PostgreSQL

Агрегатная функция GROUP_CONCAT, которая в MySQL позволяет объединять строковые ячейки разных строк, не имеет прямого аналога в postgres, однако можно сделать тоже самое, используя ARRAY_TO_STRING:
SELECT ARRAY_TO_STRING(ARRAY(SELECT "colToAggregate" FROM "myTable"),',') 
Такой вызов объединит все значения колонки "colToAggregate" из таблицы "myTable" через запятую.

пятница, 10 февраля 2012 г.

Broadcast-сообщение всем пользователям Linux

Выполняется командой wall, которая умеет читать из файла:
echo "Vsem privet" > mess
wall < "mess"
Получим:
                                                                           
Broadcast Message from root@myserver                                      
        (/dev/pts/0) at 8:44 ...                                            
                                                                             
Vsem privet                                          
                                   

четверг, 9 февраля 2012 г.

Регистронезависимое сравнение строк в PostgreSQL

В MySQL для регистронезависимого сравнения строк предусмотрены caseinsensitive collations (collation`ы с "_ci" на конце). В Postgres, на сколько я знаю, collation установить невозможно, поэтому остается использовать приведение обеих сравниваемых строк к одному регистру (через lower() или upper()) или использование регистронезависимого ILIKE. Оба варианта могут привести к тому, что в запросе не будет использоваться индекс, даже если поле, по которому ведется сравнение, проиндексировано. Также отсутствие регистронезависимого сравнения приводит к тому, что UNIQUE-ограничения также регистрозависимы.
Обсуждение данной темы есть тут: http://habrahabr.ru/qa/2445/.

Автозапуск СУБД eXist под Windows

Задача:
Запускать eXist автоматически при включении сервера от имени Network Service.

Решение:
В отличие от, скажем, томката, eXist не имеет возможности инсталляции в качестве сервиса, поэтому приходится использовать другое решение, а именно, планировщик заданий:
  1. Пуск -> Все программы -> Стандартные -> Служебные -> Планировщик заданий
  2. Действие -> Создать простую задачу
  3. Ввести для задачи какое-то имя, далее выбрать "При запуске компьютера" 
  4. Выбрать "Запустить программу" и выбрать server.bat из директории /bin eXist`а. В качестве рабочей папки указать директорию /bin eXist`а.
  5. Нажать "Готово", после чего зайти в свойства только что созданной задачи (или сразу перед нажатием на "Готово" установить соотв. галочку)
  6. На вкладке "Общие" нажать кнопку "Изменить" рядом с именем пользователя, от которого производится запуск, и выбрать Network Service.
  7. На вкладке "Параметры" снять галочку "Останавливать задачу, выполняющуюся дольше", иначе винда будет убивать eXist через указанный промежуток времени, что нам вовсе не нужно.
Также нужно убедиться, что у Network Service есть права на запись в папку eXist`а.

среда, 8 февраля 2012 г.

Ошибка "NonAlpha 95" при добавлении службы Windows для Tomcat 6

Проблема:
Добавляем томкат как службу Windows:
%CATALINA_HOME%\bin\service.bat install mytomcat
В результате выполнения команды появляется не сильно информативное окно с заголовком "Application system error" и текстом "NonAlpha 95" (или другим числом вместо 95), и служба не добавляется.

Причина:
Символ "_" (его ASCII-код как раз таки и равен 95) содержится либо в %CATALINA_HOME%, либо в имени сервиса (в моем случае - это "mytomcat", и в нем точно нет "_"). Если ошибка имеет вид "NonAlpha 45" - то речь идет о символе дефиса.

Решение:
Не использовать символы "_", "-" и, возможно, другие символы, в %CATALINA_HOME% и имени сервиса. Спасибо http://drumcoder.co.uk/blog/2012/jan/10/nonalpha-95/).

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

Кодировка GET параметров в Tomcat 6

Не знаю, можно ли в томкате, как в Glassfish, настраивать кодировку параметров на уровне веб-приложения (http://barbitoff.blogspot.com/2011/07/postget-glasshfish-2.html), но на уровне всего сервера кодировка GET-параметров настраивается атрибутом URIEncoding коннектора в server.xml (http://tomcat.apache.org/tomcat-6.0-doc/config/http.html). Например, для UTF-8:
<Connector URIEncoding ="UTF-8" ... />
Много полезной информации по кодировкам в Tomcat есть тут: http://wiki.apache.org/tomcat/FAQ/CharacterEncoding.

четверг, 2 февраля 2012 г.

Использование JNDI DataSource в Spring Secutiry

При конфигурации Spring Security можно задать bean с явными настройками соединения с БД, в которой хранится информация о пользователях:
<beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<beans:property name="driverClassName" value="org.postgresql.Driver"/>
<beans:property name="url" value="jdbc:postgresql://localhost:5432/myuserdb"/>
<beans:property name="username" value="postgres"/>
<beans:property name="password" value="postgres"/>
</beans:bean>
<beans:bean id="userDetailsService"
 class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<beans:property name="dataSource" ref="dataSource"/>      
</beans:bean>

Однако существует возможность извлекать dataSource, используя JDNI-интерфейс:
<beans:bean id="dataSource"
class="org.springframework.jndi.JndiObjectFactoryBean">
  <beans:property name="jndiName" value="java:/comp/env/jdbc/myuserdb"/>
</beans:bean>
Такой подход позволяет в качестве dataSource использовать пул соединений с БД (как настроить его, я писал ранее тут).


Быстрый старт: использование пула соединений с БД в веб-приложении, разворачиваемом на Tomcat 6

Одной из возможностей, предоставляемых контейнером сервлетов является использование пула соединений с БД, позволяющего более эффективно взаимодействовать с ней благодаря возможности повторного использования уже открытых соединений вместо создания новых.
Неплохое HOWTO по этой теме есть в документации Tomcat, ниже я приведу короткую выдержку оттуда для случая создания пула соединений к Postgres и его использования в сервлете.
1) Описываем JNDI-ресурс в context.xml веб-приложения (META-INF/context.xml, подробнее про элемент Context можно почитать тут), разместив внутри тега <Context> следующее:
<Resource name="jdbc/myDb"
auth="Container"
type="javax.sql.DataSource"
maxActive="100"
maxIdle="30"
maxWait="10000"
username="postgres"
password="postgres"
driverClassName="org.postgresql.Driver"
url="jdbc:postgresql://localhost:5432/myDB"/>

Здесь "jdbc/myDb" - это имя ресурса, по которому он будет доступен из приложения.
2) Описываем ссылку на ресурс в web.xml приложения:
<resource-ref>
<description>It`s myDb DataBase</description>
<res-ref-name>jdbc/myDb</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>

3) Теперь в коде сервлета мы можем получить соединение следующим образом:

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import java.sql.Connection;
// ...
Context initContext = new InitialContext();
Context envContext  = (Context)initContext.lookup("java:/comp/env");
DataSource ds = (DataSource)envContext.lookup("jdbc/myDb");
Connection jdbcConn = ds.getConnection();
4) Завершив работу с соединением, его можно закрыть, как обычно:
jdbcConn.close();
jdbcConn = null;
При этом запись null`а позволит Вам случайно не закрыть это соединение повторно где-нибудь в блоке finally, что очень опасно при использовании пула соединений: если первый вызов close() всего лишь сигнализирует томкату, что приложение больше не нуждается в соединении и он может отдать его другому потоку, то повторный вызов действительно закроет соединение, которое на момент этого вызова может быть уже отдано другому потоку!