barbitoff programmer`s blog

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

пятница, 29 июня 2012 г.

Hibernate: копирование persistent-объекта

Задача:

Имеется некоторый persistent-объект, ассоциированный с текущей сессией. Необходимо модифицировать некоторые его поля и сохранить в БД как копию, т.е. не заменяя предыдущий объект (выполняя INSERT вместо UPDATE).

Решение:

Проблема заключается в том, что при вызове session.save() для persistent-объекта все равно выполняется UPDATE, как и при вызове session.update(). Для того, чтобы модифицированный объект трактовался Hibernate`ом как новый объект, а не как измененный уже имеющийся в БД объект, необходимо для этого объекта сначала выполнить session.evict(), а уже затем - session.save().
PS При установленном для связи cascade="evict" вызов session.evict() будет влиять также и на связанные объекты.

java.lang.NoSuchMethodError: org.apache.xml.security.transforms.Transform.init()

Проблема:

В веб-приложении, использующем Apache xml-security, валятся исключения:
java.lang.NoSuchMethodError: org.apache.xml.security.transforms.Transform.init()

Решение:

В CLASSPATH лежит слишком поздняя версия xml-security (к примеру, 1.5.1), в которой этого метода уже нет. Необходимо заменить jar-ник на версию 1.4.5.

вторник, 19 июня 2012 г.

Hibernate Criteria API: подсчет числа строк (аналог SQL COUNT)

Чтобы получить число строк, соответствующих определенному Criteria, нужно установить соответствующую проекцию:
Criteria criteria = session.createCriteria(theClass);
...
criteria.setProjection(Projections.rowCount());
long rowCount = (Long)criteria.uniqueResult();
Здесь rowCount - число строк, которое возвратил бы вызов criteria.list(), будучи вызванным без установки проекции.

понедельник, 18 июня 2012 г.

WCF-веб-сервис: ответ "415 Unsupported Media Type"

Проблема:  

Бидинг WCF-веб-сервис имеет примерно следующий конфиг:
<wsHttpBinding>
<binding name="myBinding">
  <!-- Аудентификация клиента по сертификату. -->
  <security>
<message clientCredentialType="Certificate" />
  </security>
</binding>
</wsHttpBinding>  
При отправке запроса на веб-сервис последний отвеча HTTP-кодом:
415 Unsupported Media Type.
При этом заголовок "Content-Type" запроса установлен в "text/xml;charset=UTF-8".

Причина:

Неверный заголовок "Content-Type", для биндинга "wsHttpBinding" WCF ожидает значение "application/soap+xml".

суббота, 16 июня 2012 г.

Точки в названиях скриптов в /etc/network/if-*.d/

Полчаса борьбы и я пришел к выводу, что использовать точки в именах скриптов в /etc/network/if-up.d, /etc/network/if-down.d, /etc/network/if-pre-up.d и /etc/network/if-post-down.d нельзя. Почему - непонятно, но скрипты с точками в имени не исполняются.

bash: определение ipv4-адреса на заданном интерфейсе

Для определения ip-шника на интерфейсе (для использования в каком-либо скрипте) мне пришла в головку такая комбинация:
ifconfig eth0 | grep 'inet addr' | sed -e 's/\s*inet addr://' | sed -e 's/ .*//g'

пятница, 15 июня 2012 г.

Включение WCF Tracing

Для включения трэйсинга WCF-приложения в web.config / конфиг настолького приложения нужно добавить:
<configuration>
   <system.diagnostics>
      <sources>
            <source name="System.ServiceModel"
                    switchValue="Information, ActivityTracing"
                    propagateActivity="true">
            <listeners>
               <add name="traceListener"
                   type="System.Diagnostics.XmlWriterTraceListener"
                   initializeData= "c:\log\Traces.svclog" />
            </listeners>
         </source>
      </sources>
   </system.diagnostics>
</configuration>

четверг, 14 июня 2012 г.

КриптоПРО Sharpei: ошибка "MessageSecurityException: The algorithm 'http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411' is not accepted for operation 'AsymmetricSignature' by algorithm suite CryptoPro.Sharpei.ServiceModel.GostAlgorithmSuite."

Проблема:

При проверке веб-сервисом, использующим КриптоПРО Sharpei 3.6, ЭЦП SOAP-сообщения в логи валится ошибка:
MessageSecurityException: The algorithm 'http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411' is not accepted for operation 'AsymmetricSignature' by algorithm suite CryptoPro.Sharpei.ServiceModel.GostAlgorithmSuite.
Причина:

В запросе используется  указание алгоритма ЭЦП "http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411", тогда как шарпей версии 3.6 использует идентификатор "urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34102001-gostr3411" для обозначения этого алгоритма.

Решение:

Поменять обозначение алгоритма в запросе на "urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34102001-gostr3411".

Логи ASP.NET Development Server

Логи ASP.NET Development Server можно смотреть в просмотре системных событий:


среда, 13 июня 2012 г.

Проброс портов c Amazon EC2-инстанса (Ubuntu 12.04) на домашний HTTP-сервер

Задача:

Есть инстанс Amazon EC2 под Ubuntu 12.04 (как любой инстанс, он имеет публичное DNS-имя), хочется пробросить HTTP-порт с него на локальный ПК, находящийся, во-первых, за NAT-ом интернет-провайдера, а, во-вторых, за домашним роутером. Внешний IP провайдер не предоставляет.

Решение:

Во-первых, я поднял OpenVPN-сервер на инстансе, чтобы объединить его с моим ПК в единую виртуальную локалку (подсеть 192.168.7.0/24, амазоновский инстанс имеет ip-адрес 192.168.7.1, мой ПК - 192.168.7.10). Конфиг сервера следующий:
; подключение
port 1194
proto udp
dev tun
; аутентификация
ca ca.crt
cert aspxvpnserver.crt
key aspxvpnserver.key
dh dh1024.pem
; адресация
server 192.168.7.0 255.255.255.0
; клиентские конфиги
client-config-dir ccd
; прочее
keepalive 10 120
comp-lzo
persist-key
persist-tun
user nobody
group nogroup
client-to-client
; логирование
status /var/log/openvpn-status.log
log-append  /var/log/openvpn.log
verb 5
Конфиг клиента:
client
; подключение
dev tun
proto udp
remote ec2-xxx-xxx-xxx-xxx.compute-1.amazonaws.com 1194
; аутентификация
ca ca.crt
cert aspxclient.crt
key aspxclient.key
; прочее
resolv-retry infinite
nobind
persist-key
persist-tun
comp-lzo
; логирование
verb 5
На сервере для клиента  aspxclient определен собственный конфиг в файле /etc/openvpn/ccd/aspxclient:
ifconfig-push 192.168.7.10 192.168.7.9
, что позволяет задать клиенту статичный IP.

Подключаю OpenVPN, поднимаю на локальном ПК HTTP-сервер, проверяю, что с сервера есть телнет на 192.168.7.10:80, и что с моего ПК идут пинги на 192.168.7.1. 

Теперь осталось настроить iptables на сервере для проброса 80 порта (конечно, ещё нужно открыть этот самый 80 порт в амазоновском файрволе):
#!/bin/sh
/sbin/modprobe iptable_nat
echo "1" > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A PREROUTING -d 10.203.50.215 -p tcp -m tcp --dport 80 -j DNAT --to-destination 192.168.7.10:80
iptables -t nat -A POSTROUTING -p tcp --dst 192.168.7.10 --dport 80 -j SNAT --to-source 192.168.7.1
exit 0
Здесь 10.203.50.215 - ip-адрес инстанса в локалке амазона. Т.к. этот адрес выдается инстансу динамически и меняется при перезапуске, удобнее вместо фильтрации по адресу назначения использовать фильтрацию по входящему интерфейсу:

#!/bin/sh
/sbin/modprobe iptable_nat
echo "1" > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A PREROUTING -i eth0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 192.168.7.10:80
iptables -t nat -A POSTROUTING -p tcp --dst 192.168.7.10 --dport 80 -j SNAT --to-source 192.168.7.1
exit 0
Ну или модифицировать скрипт так, чтобы он подхватывал ip-адрес с интерфейса eth0:
#!/bin/sh
/sbin/modprobe iptable_nat
echo "1" > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A PREROUTING -d `ifconfig eth0 | grep 'inet addr' | sed -e 's/\s*inet addr://' | sed -e 's/ .*//g'` -p tcp -m tcp --dport 80 -j DNAT --to-destination 192.168.7.10:80
iptables -t nat -A POSTROUTING -p tcp --dst 192.168.7.10 --dport 80 -j SNAT --to-source 192.168.7.1
exit 0
Для удаления созданных выше правил iptables нужно выполнить:
#!/bin/sh
iptables -t nat -D PREROUTING 1
iptables -t nat -D POSTROUTING 1
exit 0
Поместив эти 2 скрипта, соответственно, в /etc/network/if-up.d и /etc/network/if-down.d, получим  сохраняемость этих правил при перезапуске интерфейса и при перезагрузке сервера (важно не использовать точки в именах скриптов!).

воскресенье, 10 июня 2012 г.

soapUI 4.5: особенность разворачивания WAR-ника Mock-сервиса, собранного на Windows, на томкате под Linux

Проблема:

При разворачивании WAR-ника Mock-сервиса, собранного на Windows, на томкате под Linux, в логи валятся ошибки вида:
ERROR [SoapUI] An error occured [Missing local file for [file:/D:/Документы/...%201.2%2005052012/wsdl/types/documents.xsd]], see error log for details
java.lang.Exception: Missing local file for [file:/D:/Документы/...%201.2%2005052012/wsdl/types/documents.xsd]
at com.eviware.soapui.impl.support.definition.export.AbstractDefinitionExporter.replaceLocation(AbstractDefinitionExporter.java:212)
at com.eviware.soapui.impl.support.definition.export.AbstractDefinitionExporter.replaceImportsAndIncludes(AbstractDefinitionExporter.java:184)
и, естественно, сервис не разворачивается. При этом при создании soapUI-проекта кэширование было включено.

Решение:

Очевидно, мок-сервис не находит xsd-схем, используемых wsdl-файлом, на основании которого создавался проект для мока. Вот только на виндовом томкате на машине, на которой также этих файлов и в помине нет, WAR-ник стартует без проблем, потому что копии всех необходимых моку ресурсов есть внутри WAR-ника (в XML-ке soapUI-шного проекта, которая там лежит) и по идее моку не нужно никуда за ними лазить. 
Оказалось, причина в том, что "умный" soapUI (не могу сказать точно, начиная с какой версии, но на 3.0 все было в этом плане в порядке, насчет 3.6 точно не помню), использует в XML-ке soapUI-шного проекта разделители, зависящие от ОС, для именования путей к файлам, которые он закэшировал (в 3.0 всегда использовался прямой слеш). При поиске закэшированных файлов также используется ОС-зависимый разделитель, поэтому сохраняются в проекте файлы с обратными слешами в названии, а ищутся при разворачивании - с прямыми.
Выход из такой ситуации - исправить вручную обратные слеши на прямые в soapUI-шном проекте, лежащем внутри WAR-ника мок-сервиса (где именно, сейчас не помню, попозже посмотрю и уточню). У меня правда после этого все равно не получилось развернуть моки на линуксе, исключения валиться перестают, но и мок не стартует, валится с 404-ошибкой с полной тишиной в логах, поэтому было принято решение разворачивать моки на виндовой машине.
PS При генерации WAR-ника под XP есть ещё одна проболема - неверная кодировка генерируемого web.xml, он кодируется в cp1251, тогда как нужно - в UTF-8 (что важно, если  soapUI-шный проект назывался по-русски), поэтому перед деплоем его нужно перекодировать.

OpenVPN: пинг с сервера (Ubuntu) на клиента (Win7)

Поднял тут OpenVPN (tun) сервер на амазоновском EC2-инстансе (Ubuntu 12.04), и столкнулся с проблемой, что с клиента (Win7) пинг на сервер идет, а вот с сервера на клиента - нет. Оказалось, проблема с брандмауэром (что неудивительно), который режет пинги (и вообще видимо все входящие соединения) на интерфейсе OpenVPN, т.к. эта сеть по-умолчанию стала "Общественной":


Вот только изменить тип этого соединения никак - надпись "Общественная сеть" для этого подключения не является ссылкой. Дело видимо в том, что для подключения не настроен шлюз по-умолчанию (т.к. шлюз по-умолчанию есть в подключении к инету, а в vpn-сеть я хожу с помощью статических маршрутов). Чтобы не трогать настройки подключения, проще либо отключить брандмауэр (что, конечно, не лучший вариант), либо разрешить все входящие соединения с OpenVPN-овской подсети. Делается это созданием "настраиваемого" правила входящих соединений брандмауэра, разрешающего подключения по всем протоколами и портам с подсети OpenVPN на локальные компьютер.

пятница, 8 июня 2012 г.

Hibernate: использование JNDI источника данных

Hibernate очень просто настраивается на использование JNDI-датасорса (уровня приложения или сервера приложений). Например, если в приложении используется пул c3p0 (у hibernate есть возможность использовать свой пул c3p0, но в моем случае хотелось, чтобы он использовал именно мой пул), сконфигурированный в context.xml так:
<Resource name="jdbc/myDb" auth="Container"
  type="com.mchange.v2.c3p0.ComboPooledDataSource"
  factory="org.apache.naming.factory.BeanFactory"
  user="postgres" password="postgres" driverClass="org.postgresql.Driver"
  jdbcUrl="jdbc:postgresql://postgresserver:5432/mydb"
  maxPoolSize="50"
  maxIdleTime="7200"
  minPoolSize="5"
  testConnectionOnCheckout="true"
  idleConnectionTestPeriod="300"
  maxIdleTimeExcessConnections="120"
  maxAdministrativeTaskTime="600"
  maxStatements="0"
  maxStatementsPerConnection="0"/>  
, и в web.xml ссылка на этот ресурс описана так:
<resource-ref>
<description>My 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>
, то конфигурация hibernate будет выглядеть следующим образом:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
  <session-factory>
    <property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
    <property name="hibernate.current_session_context_class">thread</property>  
    <property name="hibernate.connection.datasource">java:/comp/env/jdbc/myDb</property>
    <property name="show_sql">true</property>
    <!-- some mappings -->
<!-- ... -->
  </session-factory>
</hibernate-configuration>


PostgreSQL и upper() / lower() русских букв

Проблема:

В PostgreSQL функции upper() и lower() не работают для русских букв (буквы остаются в том же регистре, в каком и были). Кодировка БД - 'UTF-8'.

Решение:

Чтобы корректно работали функции upper() / lower() для русских букв (а также, например, регистронезависимые регулярные выражения), нужно корректно задать collation и character classification при создании базы (а не только кодировку) параметрами LC_COLLATE и LC_CTYPE команды CREATE DATABASE, установив их в 'ru_RU.UTF-8'. Вот только введены эти параметры были только в 8.4, а в моем случае версия сервера - 8.3, где эти параметры конфигурируются единожды, при инициализации PostgreSQL-кластера (командой initdb). Сомневаюсь, что получится это обойти, поэтому остается только дампануть все базы, переинициализировать кластер, и восстановиться из дампа. Ну или обновить версию сервера ;)

PS Посмотреть текущие настройки collation`а можно запросом:
select * from pg_settings where name like 'lc_%'
Однако запросом:
SET lc_collate to 'ru_RU.UTF-8';
ничего поправить не удастся:
ERROR:  parameter "lc_collate" cannot be changed

вторник, 5 июня 2012 г.

Hibernate 4.1: Инициализация SessionFactory

При генерации класса HibernateUtil средствами Netbeans 7.1.2 (не очень люблю такого рода генераторы, но все же иногда пользуюсь =) ) генерируемый класс просто пестрит deprecated методами и классами, ставшими таковыми при появлении 4ой версии Hibernate:


Во-первых, документация Hibernate предписывает использовать класс Configuration вместо AnnotationConfiguration:
Deprecated. All functionality has been moved to Configuration
Но это не решает проблемы с  методом buildSessionFactory:
Deprecated. Use buildSessionFactory(ServiceRegistry) instead
В итоге создание SessionFactory выглядит так:
private static final SessionFactory sessionFactory;
private static final ServiceRegistry serviceRegistry;
...
Configuration configuration = new Configuration();
configuration.configure(); // при необходимости нужно указать путь к hibernate.cfg.xml
serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();    
sessionFactory = configuration.buildSessionFactory(serviceRegistry); 
Правда, судя по документации, тоже не очень долгосрочное решение, потому что класс Configuration разработчики Hibernate также планируют сделать устаревшим и вообще отказаться от него к 5му релизу в пользу классов ServiceRegistryBuilder и MetadataSources.

воскресенье, 3 июня 2012 г.

CodeGear C++: определение промежутка между датами TDateTime

Задача:

Определить промежуток времени (например, в месяцах) между текущей датой и некоторой другой датой, представленной объектом TDateTime.

Решение:
#include <DateUtils.hpp>
TDateTime myDateTime;
....
int interval = MonthsBetween(TDateTime::CurrentDate(),myDateTime);
В результате в перемеенной interval получим промежуток времени в месяцах между текущей датой и датой myDateTime. Аналогичные функции есть и для определения промежутка времени в днях, годах и пр.

пятница, 1 июня 2012 г.

Spring Security: аутентификация и авторизация с помощью Active Directory

Задача:

Реализовать в веб-приложении авторизацию с помощью Active Directory (для начала без применения различных ролей).

Решение:

Не буду описывать весь процесс прикручивания Spring Security к веб-приложению (о нем я уже когда-то писал тут), расскажу лишь о специфичных для LDAP моментах (в моем случае роль LDAP-сервера выполнял AD, но возможно, описанное здесь справедливо и для других серверов).
Во-первых, потребуются jar-ник Spring LDAP (http://www.springsource.org/ldap), а также модуль Spring Security "spring-security-ldap". 
Для осуществления подключения к LDAP и аутентификации / авторизации через него потребутся следующие вещи:
  1. DN и пароль пользователя, от имени которого приложение будет выполнять запросы к LDAP (если AD не позволяет подключаться и искать пользователей анонимно)
  2. Собственно хост / порт LDAP-сервера
  3. BaseDN, т.е. тот контекст, который будет базовым для поиска пользователей
  4. Критерий, по которому мы будем соотносить введенный пользователем логин с объектом, соответствующим этому пользователю в LDAP. Например, роль логина может выполнять электронная почта пользователя или его UID, как в моем случае.
В конфигурации Spring Security необходимо объявить соответствующий провайдер аутентификации и инициализировать необходимые для него бины:
<beans:bean id="contextSource"
class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
  <beans:constructor-arg value="ldap://myldap:389/dc=barbitoff,dc=blogspot,dc=com"/>
  <!--beans:property name="userDn" value="uid=barbitoff,ou=People,dc=barbitoff,dc=blogspot,dc=com"/>
  <beans:property name="password" value="barbitoffspass"/-->
</beans:bean>  
<beans:bean id="ldapActiveDirectoryAuthProvider"
class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
  <beans:constructor-arg>
<beans:bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
 <beans:constructor-arg ref="contextSource"/>
 <beans:property name="userDnPatterns">
<beans:list>
 <beans:value>uid={0},ou=People</beans:value>
</beans:list>
 </beans:property>
</beans:bean>
  </beans:constructor-arg>
  <beans:constructor-arg>
<beans:bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
 <beans:constructor-arg ref="contextSource"/>
 <beans:constructor-arg value="ou=Groups"/>
 <beans:property name="groupRoleAttribute" value="ou"/>
</beans:bean>
  </beans:constructor-arg>
</beans:bean>      
<authentication-manager erase-credentials="true">
<authentication-provider ref="ldapActiveDirectoryAuthProvider"/>
</authentication-manager>   
Сконфигурированный таким образом Spring Security будет пытаться аутентифицировать  в LDAP пользователя, используя DN "uid=<entered-login>,ou=People,dc=barbitoff,dc=blogspot,dc=com", где <entered-login> - введенный пользователем в форму входа логин. При успешной аутентификации список ролей пользователя будет заполняться на основе результата поиска в ou=Groups,dc=barbitoff,dc=blogspot,dc=com с фильтром "member=<userDN>" (имя роли будет браться из атрибута "ou").
Закомментированные строки в бине "contextSource" нужно раскомментировать, если поиск пользователей невозможен при анонимном подключении к LDAP. 
Для отладки конфигурации очень полезно включить логирование на уровень DEBUG (http://barbitoff.blogspot.com/2012/05/spring-security.html). 

PostgreSQL-аналог MySQL-функции LAST_INSERT_ID()

В постгресе нет полей AUTO_INCREMENT, но есть псевдо-тип данных serial, который использует последовательности для задания новых значений поля. Для получения значения, использованного для задания значения такого поля при последней  INSERT-операции можно воспользоваться функциями для работы с последовательностями:
SELECT CURRVAL(<имя_последовательности>);
или
SELECT LASTVAL();
Последний вариант не требует задания имени последовательности, используемой для генерации новых значений поля, а просто возвращает значение последней использованной последовательности (точнее значение, возвращенное последней операцией nextval), что вполне подходит при использовании сразу после проведения INSERT`а.