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

Pirating is illegal? Since then?!

Столько копий сломано вокруг темы компьютерного пиратсва. Поднимаются темы копирайта, передачи права, легального и не легального обмена, копилефта.

Все это можно найти  в интернете, почитать - очень много хороших идей.

Однако, бизнес в очередной раз все переврал. Сказал - защищать авторов, а на деле защищает свои интересы.

Не хочу писать много воды. А просто посоветовать свой выход из ситуации, мое видение. Если мои идеи (или я это тоже у кого то украл, надо бы проверить) это случится на практике то будут довольны все и бизнес и пользователи.

Идея простая - сделать незаконным, уголовным преступлением продажу всех форм интелектуальной собственности, музыки, фильмов, программ, игр и всего прочьего за реальные деньги. То есть запретить продажу того что для воспроизводства (копии) не требует серезных материальных затрат. 

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

Что бы это не привело к уничтожению всей индустрии целиком, необходимо ввести новые формы отношений - понятие электронной-интеллектуальной валюты. Это электронная форма денег будет позволять совершать платежы в рамках описанных отношений.

Все легальные операции с интелектуальной собственностью должны проходить исключительно в электонной валюте. Захотел купить песню в интернет на страницу своего кошелька и за 100 едениц покупай песню.

Где взять эту валюту?

Сделать биржу, на котороый будут проходить торги электронной валюты в реальную валюту. На этой бирже можно обменить деньги на электронные деньги на открытом рынке. На этой бирже владельцы счетов - компании индустрии полуачют от вас реальный деньги в обмен на электронные, на которые вы потом купите их продукт.

Третий шаг сделать эмисию электронных денег. Электронные деньги вы можете не только обменять на бирже но и получить их со своего веб сайта за высокую посещяемость \ клики.

Главные плюсы от этой схемы:

- рынок цен должен урегулировать, не будет такой большой разницы в цене для разных стран и разных достатков

- цены на продукты упадут (думаю раз в 50. песня станет стоить не 99 центов а 0.02 цента), станут более доступные и их продажи. А продажи возврастут, так как цена стнет просто смешной. Думаю что эта индустрия потеряет в прибыли раза 2 бюджета, но станет более доступной и более полезной для общества.

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

Это черновик, не судите строго.

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

bash reload feature

Оказывается что после изменения содержимого bash  скрипта на диске исполняемый скрипт перегружается и продолжает исполнение с предыдущий позиции.

Исполнение файла происходит даже в том случае если новый замещенный файл по старой позиции содержит комментарий или другую функцию испольнение которой не должно происходить при нормальном запуске.

222.txt
#!/bin/bash

u() {
  cat 333.txt > 222.sh

  sleep 1
}

u

333.txt

#!/bin/bash


u() {
  cat 333.txt > 222.sh
#####################
  sleep 1
}


u

# cp 222.txt 222.sh; bash 222.sh

222.sh: line 10: eep: command not found
222.sh: line 11: syntax error near unexpected token `}'
222.sh: line 11: `}'

endless bash script


111.sh
#!/bin/bash


update() {
  echo "echo -n ." >> 111.sh
  echo "update" >> 111.sh
  
  sleep 1
}


echo -n .
update

Skype security breach 2

Опять, второй раз за пол года, украли мой аккаунт скайпа.

По той же схеме. Пишут письмо в тех поддержку - смените пожалуйста регистрационный имейл. И те, торопятся его меняют.

О чем это говорит?

Только о том что если ваши данные, деньги на счету, контакты, переписка, не прочитанные сообщения важны вам (а так же если они нужны кому-то еще) то не пользуйтесь скайпом.

В этот раз схема чуть изменилась.

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

Разница с первым слуачем в ноябре, в том, что теперь мой аккаунт был зарегистирован на @gmail.com. В то время как первый раз на мой персональный домен @axet.ru

Пока не пойму где тут пробелма. Первое о очивидное - фигли поддержка меняет так легко адреса. Второе - зачем понадобилось менять сначало на @axet.ru потом уже окончательно воровать.

Хостинг @axet.ru держит гугл через наследование доменных имен и тот и другой на серверах гугла. Хостин dns имен http://freedns.afraid.org. Есть предположение что письма с активацией воруются через поисинг dns или через ошибку админского доступа на freedns.afraid.org. Но минуту назад я заходил проверить - все настройки на месте. И зашел я проверить их ровно в ту минуту когда получил это пиьсмо "счастья". Похоже что не там проблема.

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

И кроме того, что не менее важно, не понятно почему письма не содержат информации о том на какой адрес меняется имя скайп. Так как если вы меняете имейл через веб интерфейс - то вам приходит соовершенно другое письмо и в нем указан этот адрес. А сейчас очивидно что это сделал поддержка, и очивидно есть какой-то другой хитрый способ воровать толи активацию то ли ключи.. Черт его знает.


Вообщем пока буду думать, надеюсь в этот раз поддержка скайпа расскроет данные вора.





Второе письмо:


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

samba on debian 6


Так как пришлось перестанавливать сервер восстановил конфиги самбы.

Какой функционал мне требуется?

- Хранить файлы для интернета
- Хранить файлы личные: фото, проекты.

Потому настройка довольно простая. Но с небольшим удобным дополнением - файлы открыты на запись только для тех кто хочет эти файлы изменить. Причем защита чисто от случайной модификации а не с правами пользователей.

Потому схема простая. Если заходите на сервер по пути \\mini.local\www попадаете в папку только для чтения - ее видно всем, без логина и пароля. А если хотите поменять файлы заходите по ссылке \\mini.local\www-rw.

Все просто.

Еще небольшой фокус. Что бы вирусы и просто случайно никто не удалил файлы папка www-rw невидимая.

Вот конфигруационный файл:


/etc/samba/smb.conf

[...]

map to guest = bad user
guest ok = yes
guest account = nobody

[www]
  path = /mnt/shared/www
  writeable = no
  browseable = yes
  guest ok = yes
  force user = nobody

[www-rw]
  path = /mnt/shared/www
  writeable = yes
  browseable = no
  guest ok = yes
  force user = nobody

[local]
  path = /mnt/shared/local
  writeable = no
  browseable = yes
  guest ok = yes
  force user = nobody

[local-rw]
  path = /mnt/shared/local
  writeable = yes
  browseable = no
  guest ok = yes
  force user = nobody

OpenVPN for Debian 6

Продолжая серию статей о том как настроить openvpn на разных дистибутивах.

Последняя инструкция была для Fedora 12, которая была установлена на моем сервере. К сожалению Fedora перестала выпускать сборки для PowerPC и я вынужен был перейти на .deb based дистрибутив.

Настройку я копирую, сохраня авторизацию по имени \ паролю. Использовать сертификаты слижком усложнет настройку и она не удобна в дальнейшем.

Как настроить openvpn  на сервере не сломая его?

Первое что нужно сделать это установить требуемые пакеты.

# apt-get install openvpn bridge-utils

Следующий шаг - создать ключи.

cd /etc/openvpn
mkdir keys
cd keys

openssl dhparam -out dh1024.pem 1024

openssl genrsa -des3 -out ca.key 4096
openssl req -new -x509 -days 365 -key ca.key -out ca.crt

openssl genrsa -des3 -out server.key 4096
openssl req -new -key server.key -out server.csr
openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt

cat server.key | openssl rsa -text > server.txt

Следующее: обновить файл /etc/default/openvpn

/etc/default/openvpn

[...]

OPTARGS="--script-security 2"


Затем настроить интерфейсы.

/etc/network/interfaces
auto lo
iface lo inet loopback

auto br0

iface br0 inet dhcp
  pre-up /usr/sbin/openvpn --mktun --dev tap0
  post-down /usr/sbin/openvpn --rmtun --dev tap0
  bridge_ports eth0 tap0

И файл конфига сервера.

/etc/openvpn/tap0.conf
proto tcp-server
mode server
tls-server

dev tap0

plugin /usr/lib/openvpn/plugin/lib/openvpn-auth-pam.so login
client-cert-not-required
username-as-common-name

ca keys/ca.crt
cert keys/server.crt
key keys/server.txt
dh keys/dh1024.pem

client-to-client
duplicate-cn
keepalive 10 120
comp-lzo
persist-key
persist-tun
status openvpn-status.log
verb 3 
up /usr/bin/openvpn-upnp-up.sh
down /usr/bin/openvpn-upnp-down.sh


Конфигурационный файл для клиента:

client.conf
client
dev tap

proto tcp
remote tax.ath.cx 1194
resolv-retry infinite

nobind

persist-key
persist-tun

# route 192.168.2.0 255.255.255.0 192.168.54.2

ca ca.crt
auth-user-pass
comp-lzo

script-security 2

Следующим шагом, если вы хотите запустить сервер за nat сервером (роутером) и сделать доступным его с интернета необходимо прописать переброску портов.


Я написал небольшой скрипт, который делает это автоматически.


# mkdir ~/source
# git clone https://code.google.com/p/openvpn-upnp/
# cd openvpn-upnp
# git submodule init
# git submodule update
# make deb
# make ideb

Возможные проблемы:

Если ваш дистрибутив еще не настроен (после установки) то возможно на нем будет запущен сервис network-manager в место привычного networking.  Если попробуете включить и выключить по-привычке через update-rc.d то возникнет знакомая ошибка:

# insserv networking
insserv: warning: current start runlevel(s) (empty) of script `networking' overwrites defaults (2 3 4 5).
insserv: warning: current stop runlevel(s) (0 6) of script `networking' overwrites defaults (0 1 6).

Для переключения его на использование последнего сервиса необходимо выполнить следующие:


# update-rc.d network-manager remove
# insserv -d networking

ICS 4

Не смог дождаться автоматического обновления андроида на своем телефоне Nexus S. Установил  в ручную. Версия 4.0.3.

В интернете полно инструкций как это сделать и все прошло без ошибок.

Какие замечания? Скажу сразу проблем не добавилось. Минусы из серии предыдущих версий. Плюсов больше.

Плюсы
  • Появилась возможность сохранять логин \ пароль для VPN соединений
  • Появился график и встроеннный контроллер для мобильного трафика
  • Появилась возможность шифровать телефон ключем, защищающий данные на флешке от кражы.
  • Опявилась отдельная галочка для смены часовой зоны. Раньше, во время путешествий, было нужно снимать галочку "Автоматическая установка времени" и менять зону руками.
  • Небольшие приятные изменения по интерфейсу во всех пунктах меню, настройках и рабочим экранам.
  • Оффлайн почта.

Минусы
  • Залоченый экран паттерном, иногда не дает ввести ключ и сбрасывает введенный паттерн пока вы пытаетесь разлочить телефон
  • Много параметров не сохраняться онлайн. Если вы очистили телефон не сохраняться настройки: vpn, позиции иконок, системные настройки.
  • Часто валится Google+ и просить отправить репорт.
  • Нет возможности добавить в Google Talk сторонний jabber аккаунт.
  • Хоть и появилось шифрование, но есть ряд проблем.
    • Нельзя отменить шифрование, один раз залоченый телефон требует форматирования для продолжения работы.
    • Паттерн для шифрования обязательно должен быть либо цифровым либо текстовым и нельзя совмещать его с визуальным паттерном. То есть если вы защитили телефон 20 символами пароля то эти 20 символов вы и должны вводить каждый раз что бы посмотреть входящую СМСку.
    • Шифрование не шифрует системные файлы.
  • Переключение языка раньше было возможно слайдом на клавише пробел. Теперь через длительное на него нажатие открывается новый диалог. Ужасно неудобно.
  • Иногда закрывает все отктые приложения (видимо оконный менеждер все грохает)
  • Иногда самотоятельно перегружается при большой нагрузке или нехватки памяти. 

четверг, 29 декабря 2011 г.

monit tomcat

Tomcat довольно часто валиться от нехватки памяти. это чаще всего происходит при перезагрузке выложенных под томкат модулей.


Избежать этого не получается потому приходится запускать дополнительные службы мониторинга. и при наступлении критической систуации (падение, нехватка памяти или просто зависание) эти службы перезапустят любой системный сервис.

Как настроить monit для tomcat6?

Для начала установить monit.

apt-get install monit


Потом разрешить его запуск в defaults debian

/etc/default/monit
[...]
startup=1

Создать скрипт для мониторинга tomcat

/etc/monit/conf.d/tomcat6 
check process tomcat6 with pidfile /var/run/tomcat6.pid
start program = "/etc/init.d/tomcat6 start"
stop program = "/etc/init.d/tomcat6 stop"
if failed host localhost port 443 type tcpssl protocol http
  and request "/monit" with timeout 15 seconds for 3 cycles then restart
if totalmem > 1500 MB for 10 cycles then restart
if 5 restarts within 5 cycles then timeout




Далее необходимо создать веб приложение из одного файла index.html.


mkdir /var/lib/tomcat6/webapps/monit/

/var/lib/tomcat6/webapps/monit/index.html 
OK


И самым последним моментом будет настройка файла monitrc. Можете просто убрать комментарии с соответсвующих паарметров конфигруационного файла.

/etc/monit/monitrc
 set daemon  120
[…]
set logfile
[…]
set idfile /var/.monit.id
[…]
set statefile
[…]
set eventqueue
[...]
set httpd port 2812 and

tomcat6 keystore

Для подключения SSL в tomcat 6 необходимо сделать несколько шагов.

Прежде всего хочу сказать что есть два варианта включения этой поддержки. Через ява JSSE движок или напрямую через OpenSSL бибилотеку.

Так как я уже давно использую оба способа я пришел к выводу что меньше всего проблем возникает в с использованием первого варианта.

Хочу сказать пару слов про второй варинат. Во втором случае у вас возникает множество проблем с libtcnative-1. В некоторых дистрибутивах эта библиотека отсуствтует и вам необходимо ее устанавливать и следить за ее обновлениями в ручном режиме. Так же недавно я обнаружил что поддержка privileged ports так же не доступна с OpenSSL движком. Это означает что штатными средствами вашей платформы вы не сможете заставить работать tomcat на портах ниже 1024 и скорей всего будете использовать iptable nap mapping или apache proxy. Оба этих варианта не будут самым оптимальным способом использование этой фукнции. Потому я совсем рекомендую этот последний варинат не использовать.




Для запуска апача необходимо настроить server.xml и включить Connector https. Такая настройка уже есть в конфиге, достаточно убрать комментарии и указать путь к хранилищу сертификатов:

    <Connector port="443" protocol="HTTP/1.1" SSLEnabled="true"
               maxThreads="150" scheme="https" secure="true"
               clientAuth="want" sslProtocol="TLS"
               keystoreFile="${catalina.base}/conf/keystore" keystorePass="123456" 

               truststoreFile="${catalina.base}/conf/keystore" truststorePass="123456" />
И исправить второй коннектор, его порт:

    <Connector port="80" protocol="HTTP/1.1"
               connectionTimeout="20000"
               URIEncoding="UTF-8"
               redirectPort="443" />


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

И последний момент - разрешение на запуск на служебных портах. Для этого в файле Debian tomcat6 необходимо изменить строчку:

/etc/defaults/tomcat6

[...]

AUTHBIND=yes

понедельник, 26 декабря 2011 г.

x509 – Basic commands

# create CA key
openssl genrsa -des3 -out ca.key 4096
# create CA request
openssl req -new -x509 -days 365 -key ca.key -out ca.crt

# create server key
openssl genrsa -des3 -out server.key 4096
# create server req
openssl req -new -key server.key -out server.csr
# sign server req
openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt

# create client key
openssl genrsa -des3 -out client.key 4096
# create terminal req
# Common Name (eg, YOUR name) []:5 (where 5 - is client id)
openssl req -new -key client.key -out client.csr
# sign terminal req
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 02 -out client.crt
# convert to der
openssl pkcs8 -topk8 -inform PEM -in client.key -outform DER -nocrypt -out client.der
# convert to pk12
openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name client

# Printout
openssl x509 -text < client.crt

# Printout key
openssl rsa -text < client.key

Как автоматически установить дарйвера принтера на Линукс?

Очень просто из консоли установить драйвер, если вы знаете модель и у вас есть сам драйвер:

CUPS pdf - printer
/usr/sbin/lpadmin -p Cups-PDF -v cups-pdf:/ -m CUPS-PDF.ppd -E


VKP80
/usr/sbin/lpadmin -E -p VKP80 -P VKP80.ppd -m VKP80 -v usb://CUSTOM%20Engineering/VKP80


TG2480
/usr/sbin/lpadmin -E -p TG2480-H -P TG2480-H.ppd -m TG2480-H -v usb://CUSTOM%20Engineering/TG2480-H



Если вы не знаете модель и какой драйвер требуется - установите принтер один раз в ручном режиме и запишите настройки.

Проектные файлы в репозитории

Довольна острая тема принимающее явление активного противостояния между разными группами людей имеющие разные взгляды... Очем это я... А да!

Так хранить или не хранить проектыные файлы в репозитории, даже если они могут быть сгенерированы? Мое мнение - хранить.

Позволю себе на эту тему небольшую лекцию. Все файлы исходников, которые имеют отношение к проекту храняться в репозитории. Все настройки, которые производятся локально, не должны затрагивать репозиторий. Так если я создаю скрипты, которые запускают локальный сервер с логином и паролем - я не кладу эти файлы в репозиторий, и не держу их в дирректории с локальным репозиторием (можно хранить на следующим уровне вверх). Так как если этот файл поместить в дирректорию с репозиторием этот файл будет отображаться каждый раз когда я запрашиваю статус проекта под системой контроля версии. Таким образом команда git stauts (svn st, hg st) показывает все файлы, которые должны быть помещены под контроль версий или измененные файлы. Сама библиотека - должна легко и удобно портироваться между средами разработками. Ее задача - быть доступной для всех платформ \ сред разработки, которые мы хотим поддерживать. Следовательно ее должно быть просто импортировать и подключать к проектам без доп настроек. Таких как создание нового проекта и импорирование файлов по одному \  с указанием библиотек для импорта. К тому же к этому я придерживаюсь следующего правила: если мы запускаем еклипс на исходники и он создает проектные файлы, внутри дирректории исходников (он так и должен делать) то пометить эти файлы в .gitignore - будет помещение помещение локальной информации нерелевантной к проекту под систему контроля версии. Так какая разница пометить эти файлы в .gitignore или поместить их под проект тем самым облегчить импорт библиотеки?

И последнее - платформенно зависимые файлы \ пользовательские файлы проекта, конечно ненужно сохранять под систему контроля версий. Такие файлы генерируются специально для текущего окружения и \ или настроект пользователя. А именно: результат automake или пользовательские файлы visual studio .suo.

А остальнмо дело личное, поддерживать \ тестировать платформы целевые платформы или опустить это на пользователей. Все это ложиться того кто поддерживает проект.

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

DIR-300/NRU/B1 - dd-wrt.com


DIR-300/NRU/B1

Поскольку система сборки очень запутана и сайт не достаточно информативен делаю набор выдержек, которые могут пригодится для прошивки роутера.
  • Роутер, в режиме восстановления не прошивается из других под других ОС кроме Windows. Ошибка будет очень не информативна. При прошивке роутера он напишет что файл испорчен.

База данных: нужно вбить имя роутера и кликнуть на его имени (ссылка не подсвечивается)
Последние сборки доступны по ссылке (в дирректории dlink-dir300-revb):
На сайте предоставлены 2 вида прошивок:  factory-webflash, ddwrt-webflash. Используется только factory-webflash. ddwrt-webflash используется для перепрошивки уже перепрошитого роутера (upgrade).

Ссылки на вики для роутера DIR-300:
Восстановление оригинальной прошивки (файл dir300b_v2.06_b9fe.bin):

В настоящий момент очень плохо реализована поддержка IPTV. Пакеты маршрутизируются на все интерфейсы роутера и глушат полностью ВайФай. Данная проблема решена в последней версии ядра (2.6.34).

Hibernate nested transactions

Сталкивались вы когда-нибудь с вложенными транзакциями?

В двух словах: эта такая транзакция которая создается внутри родительской транзакции. И может быть обработана независимо от основной.

Но проще это объяснить на примере.

Например. Например вам нужно сделать сервис, который будет регулярно опрашивать базу данных и проводить обработку транзакций. Каждый такой вызов транзакции должен сопровождаться обращением в интернет или какую-другую нестабильную среду и в результате чего может образоваться ошибка (или что более правильно исключение).

Вот как-то так.

Что бы обеспечить стабильность, вы должны обрабоать эту ошибку и в дальнейшем привизать эту ошибку к транзакции.

То есть простым языком получаете список необработанных транзакций. Вызываете функцию обработки транзакции и ошибку записываете в другое поле той же транзакции.

Как все это реализовать на Hibernate \ Spring?

Ну в документации сказано что есть такое средство. Называется nested transaction. Для его активации у метода, создающего вложенную транзакцию необходимо использовать атрибут PROPAGATION_NESTED. А еще прописать в tarnsactionmanager проперти nestedTransactionAllowed со значением true.

Вот как выглядит эта теория на практике:

dao.xml

  <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
    <property name="nestedTransactionAllowed" value="true" />
  </bean>


IncomeProcessor.java

@Service
@Transactional
public class IncomeProcessor {


    [...]


    @Scheduled(fixedRate = 60 * 1000)
    public void processNormal() {
       List<Transaction> list = some.list();
       for(Transaction t : list) {
         try {
           process(t);
         } catch(Exception e){
             t.setError(e);
             dao.update(t);
         }
    }
    
    @Transactional(propagation = Propagation.NESTED)
    public void process(Transaction t) {
       doSomeExternalJob(t);
    }
}

В примере мы получем список, пробегаемся по всем эелемнтам и все ошибки (исключения) записываем в объект transaction в базу данных. В териории все исключения возникшие в вызове process(), обернуты обработчиком откатывают все изменения в базе данных сделаные методом doSomeExternalJob(). Таким образом мы не теряем данные и не захламляем базу новыми объектами созданными до возникновения исключения.

Но на практике такой подход не работает. Я не стал разбераться ошибка ли это хибернейта или jdbc драйвера. Я стал разбераться в том как эти вложенные транзакции реализованы и нашел сразу две довольно критичные проблемы в приведенной выше схеме. Так что стандартный механизм спринга лучше в таком варианте не использовать, если не хотите потерять данные (или пропустить обработку).

Прежде всего хочу сказать как это должно быть реализовано в jdbc драйвере.

При обработке вложенных транзакций драйвер, в точке входа в функцию создает так называемые Savepoint. И при выходе, в случае возникновения ошибки вызвает функцию у соединения rollback() с передачей ей нужной точки сохранения. Таким образом при входе в фукнцию process должна была бы вызваться connection.setSavepoint() и при выходе, для отката части транзакции connection.rollback(savePoint).

Почему это не работает, непонятно. Можно покапаться в исходниках sping / hibernate благо они доступны и найти что нибудь глупое навроде не правильной версии jdbc или пропуск создания какогонибудь хитрого bean с указанной логикой. Я решил не искать проблему и не создавать баг репорта по причине что я нашел вторую проблему. Так же связанную с привиденным выше алгоритмом работы. И эта ситуация на корню блокирует любые попытки программно (с помощью атрибутов) сделать элегантное решение этих nested transactions.

В чем эта вторая проблема заключается и почему исправление вложных транзакций так сложно?

Я провел простой эксперимент. Провел симуляцию правильной логики сохранения \ откатов savepoints и обнаржил что не все объекты всстанавливают свое исходное состояние. Несмотря на то что откат проходит успешно некоторые объекты все равно остаются в своем прежнем состоянии как до отката. Отмененная транзакция до указанного savepoint ни как не затрагивает объектов уже скаченных с сервера, закешированных и измененных на памяти.

Что это означает. Если вы создаете savepoint для текущей транзакции и передаете в фукнцию обработки объект транзакция то не смотря на то что вызываете rollback нет такого маханизма в спринге что бы отслеживать объекты измененные на памяти и запрашивать автоматически их состояния с сервера.

Что привело меня к мысли что исправление вложенных транзакций все равно не повзолит реализовать указанную лоигку только на стороне спринг \ хибернейт библиотек. Так как перечитывать состояние всех объектов после отката одной транзакции не имеет практического смысла и к тому же приведет к потере данных. А откатывать отдельный параметр было бы так же не допустимо.

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

NestedTransaction.java
package com.payment.db.services;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.util.List;
import java.util.Stack;

import org.hibernate.SessionFactory;
import org.hibernate.jdbc.Work;

public abstract class NestedTransaction<T> {

    public void list(SessionFactory sessionFactory, List<T> list) {
        final Stack<Savepoint> s = new Stack<Savepoint>();
        for (T e : list) {
            try {
                sessionFactory.getCurrentSession().doWork(new Work() {
                    @Override
                    public void execute(Connection connection) throws SQLException {
                        s.push(connection.setSavepoint());
                    }
                });
                process(e);
            } catch (Exception ee) {
                sessionFactory.getCurrentSession().doWork(new Work() {
                    @Override
                    public void execute(Connection connection) throws SQLException {
                        connection.rollback(s.pop());
                    }
                });
                sessionFactory.getCurrentSession().refresh(e);
                exception(e, ee);
            }
        }
    }

    public abstract void process(T e);

    public abstract void exception(T e, Exception ee);
}

IncomeProcessor.java
package com.payment.db.services;

@Service
@Transactional
public class IncomeProcessor extends NestedTransaction<ChargePool> {

    [...]

    @Scheduled(fixedRate = 60 * 1000)
    public void processNormal() {
        List<ChargePool> list = chargepool.listAvailable();

        if (list == null || list.size() == 0) {
            return;
        }

        list(sessionFactory, list);
    }

    public void process(ChargePool cp) {
        Charge c = cp.getCharge();
        if (!c.isLinked())
            throw new RuntimeException("operation on non linked charge");
        SomeProcessIncome(cp);
    }

    public void exception(ChargePool cp, Exception e) {
        cp.setLastOperationRespond(ExceptionText.covert(e));
        cp.setLastOperationDate(new Date());
        chargepool.update(cp);
    }
}


В приведенном примере создан базовый класс для обработки вложенных транзакций с использованием фукнций сохранения \ отката состояний (Savepoint) и так же исправлена проблема с откатом объектов на памяти вызовом фукнции sessionFactory.getCurrentSession().refresh(e)


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

Send Hibernate to the Network

Пришлось решть стандартную задачу на ява по пересылке объектов Java с одной машины на другую по сети. Довольно простая операция если учитывать что все это уже релализовано в самой Джаве и воспользоваться механизмом сериализации может любой желающий.

Для сериализации объектов достаточно имплиментировать интерфейс Serializable и после этого одним вызовом метода writeObject класса ObjectOutputStream сохранять объекты в любой поток. Этот поток может быть массивом на памяти, файлом или сокетом. Создав правильным образом экземпляр класса ObjectOutputStream можно заливать данные в любых направлениях.

Однако я работал не с совсем стандартными объектами и их сериализация несколько усложнилась. В мою задачу входила серилизация объектов полученных из базы данных через Hibernate библиотеку и передача их на другую машину в сети. Теперь я хочу поделиться найденым решением для удобной сериализации Hibernate объектов.

Работая с базой данных через Hibernate у вас есть выбор. Этот выбор напрямую скажется на том как эффективно и как сложно будет реализовавать мехнизм передачи объектов.

Первый вариант заключается в том, что бы получать все объекты полностью загруженными из базы данных.  Это довольно простой способ, все данные загружены в объект и сериализация объектов сводится к простому помещению объектов в поток без подготовки. Однако, если вы начнете работать с базой данных в таком режиме - то это будет крайне не эффективно и врядли ваш сервер сможет рабоать больше чем с 10 пользователями одновременно.

Второй вариант позволяет получаеть объект не полностью инициализированным данными. Он заключается в том, что бы после получения объекта данные в нем подгружались из базы только после обращения к ним. То есть например, вы получаете объект записи транзакций за последние 3 месяца и обращаетесь к запясям начиная с 5000 номера по 9000 номер. В этом случае Хибернейт получит из базы только указанный интервал данных, без выкачивания всех остальных записей. Ко всему прочьему, так же не будут сразу загружены все связанные объекты и свойства классов. Работа по такой схеме очень ускоряется так как объем трафика и данных получаемой с базы уменьшается.

Однако если вы попытаетесь передать по сети полученный объект с без загруженных объектов то ява выдаст следующую ошибку:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:167)
    org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)
    org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
    com.payment.dao.Wallet_$$_javassist_2.getId(Wallet_$$_javassist_2.java)

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

Что бы этого избежать необходимо специальным образом обработать объект так, что бы все данные в него загрузились до отправки его на другой комьютер. Для этого существуют разные способы и самый правильный будет использовать библиотеку Dozer.

Эта библиотека позволяет преобразоваывать объекты на лету полученных из базы данных из одного типа, набора полей и иерархии в другой набор объектов который вы хотели бы получить на выходе. Эта библиотека программируется через xml конфигурационный файл и позволяет задавать преобазования настариваемые пользователем. Полезным для нас свойством этой библиотеки помимо преобразования объектов будет копирование объектов на лету. Так что поместив не полностью загруженный объект в метод mapper на выходе из него мы получем копию, полностью загруженных данных. После чего мы без труда отправим копию по сети изключив из нее всю информацию о сесиях и базы данных. В нашем же случае, нам даже не придется делать дополнительные настройки. В самом простом случае, Dozer можно запустить без настроек и тогда она будет преобразоыввать объект из самого себя и сам себя.

Поэтому для передачи объектов нам не потребуется разбераться в настройках, а будет достаточно просто вызова метода mapper.

Вот такой простой код позволяет получать на вход объект с сессией без данных, а на выходе получать объект с полностью загруженными данными и без информации о обазе данных и сессии:

    @SuppressWarnings("unchecked")
    static public <T> T dozer(T o) {
        if (o == null)
            return null;
        Mapper mapper = DozerBeanMapperSingletonWrapper.getInstance();
        return (T) mapper.map(o, o.getClass());
    }


Однако на практике все оказалось не так хорошо. Из за ошибки в библиотеки эта конструкция не работала. Программа валилась где-то внутри ее собственного кода и не позволяла сериализовавть объект на выходе этой функции.

Я привожу этот пример, как потенциально рабочий и возможно с ним не будет проблем в следующем резиле программы. На момент написания статьи он не работал. Версия Dozer: 5.3.2. javassist 3.12.1.GA.

SEVERE: Servlet.service() for servlet server threw exception
java.lang.NullPointerException
    at javassist.util.proxy.RuntimeSupport$DefaultMethodHandler.invoke(RuntimeSupport.java:37)
    at com.payment.dao.User_$$_javassist_13.writeReplace(User_$$_javassist_13.java)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:616)
    at java.io.ObjectStreamClass.invokeWriteReplace(ObjectStreamClass.java:1049)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1131)

Что бы обойти сложившуюся ситуацию есть несколко вариантов. Приведу только один.

Для передачи объекта необходимо форсировать загрузку данных для Lazy объекта. Для этого необходимо вызвать метод Hibernate.initialize(object) для всех объектов полученных с через hibernate.

Скажем если вы загружаете объект командой:

    @SuppressWarnings("unchecked")
    static public Wallet get(Long id) {
        return (Wallet) sessionFactory.getCurrentSession().createQuery("from Wallet where id = :id")
        .setParameter("id", id).uniqueResult();
    }

Новый объект Wallet w; должен быть проинициализирован вызовом Hibernate.initialize(w) После чего его можно сериализовать для передачи по сети. Однако подгружены будут только простые переменные класса. И если в вашем классе есть связанные коллекции ссылающиеся на другие объекты базы данных то они не будут загружены и вы не сможете сохранить объект в поток.

Для того что бы инициировать все связанные объекты необхоимо вручную вызвать методы для этих коллекций и связанных объектов. Так например если ваш объект Кошелек показывает список Транзакций, которые в свою очередь могут ссылаться на другие объекты необходимо вызвать все методы поочереди. Wallet w; for (Transaction t : w.getTransactions()) t.getUser();  и так далее.

С проктической точки зрения необходимо автоматизировать этот процесс через работу с java reflect api. Моя реализация, которая решает данную проблему можеть быть более элегантной и не использовать обязательного наследования. Но я хочу привести ее в том виде который я использовал.

Для сериализации всех связанных объектов @Entity необходимо их унаследовать от одного класса Lazy. Затем у верхнего объекта, который со всеми вложенными данными необходимо вызвать метод lazyInit()

    public class Lazy {
        boolean lazyLoaded = false;

        boolean lazyTest(Class<?> cc, Class<?> ccc) {
            try {
                return cc.asSubclass(ccc) != null;
            } catch (ClassCastException e) {
                return false;
            }
        }

        boolean lazyGeneric(Type c, Class<?> ccc) {
            try {
                ParameterizedType pp = (ParameterizedType) c;
                Class<?> tt = (Class<?>) pp.getActualTypeArguments()[0];
                return tt.asSubclass(Lazy.class) != null;
            } catch (ClassCastException e) {
                return false;
            }

        }

        public Object[] lazyAnnotate() {
            try {
                ArrayList<Object> list = new ArrayList<Object>();

                Class<?> c = this.getClass();

                for (Field f : c.getDeclaredFields()) {
                    Class<?> cc = f.getType();
                    Type g = f.getGenericType();

                    f.setAccessible(true);

                    if (lazyTest(cc, Lazy.class))
                        list.add(f.get(this));

                    if (lazyGeneric(g, Lazy.class))
                        list.add(f.get(this));
                }

                return list.toArray();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        @SuppressWarnings("unchecked")
        public void lazyInit() {
            if (lazyLoaded)
                return;
            lazyLoaded = true;

            for (Object o : lazyAnnotate()) {
                if (o instanceof Lazy) {
                    ((Lazy) o).lazyInit();
                }

                if (o instanceof Set<?>) {
                    for (Lazy l : (Set<? extends Lazy>) o) {
                        l.lazyInit();
                    }
                }

                if (o instanceof List<?>) {
                    for (Lazy l : (List<? extends Lazy>) o) {
                        l.lazyInit();
                    }
                }
            }

            Hibernate.initialize(this);
        }
    }

Метод вызываемый перед сериализацией:

    @SuppressWarnings("unchecked")
    static public <T> T dozer(T o) {
        if (o == null)
            return null;
        ((Lazy) o).lazyInit();
        return o;
    }

     @SuppressWarnings("unchecked")
    static public <T> List<T> dozer(List<T> e) {
        if (e == null)
            return null;

        ArrayList<T> r = new ArrayList<T>();

        for (Object o : e) {
             r.add((T) Dozer.dozer(o));
        }

        return r;
    }


Наследование всех классов Entity, которые должны быть инициализированы:

@Entity
@Table(name = "wallet")
public class Wallet extends Lazy implements Serializable {