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
четверг, 29 декабря 2011 г.
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
Прежде всего хочу сказать что есть два варианта включения этой поддержки. Через ява JSSE движок или напрямую через OpenSSL бибилотеку.
Так как я уже давно использую оба способа я пришел к выводу что меньше всего проблем возникает в с использованием первого варианта.
Хочу сказать пару слов про второй варинат. Во втором случае у вас возникает множество проблем с libtcnative-1. В некоторых дистрибутивах эта библиотека отсуствтует и вам необходимо ее устанавливать и следить за ее обновлениями в ручном режиме. Так же недавно я обнаружил что поддержка privileged ports так же не доступна с OpenSSL движком. Это означает что штатными средствами вашей платформы вы не сможете заставить работать tomcat на портах ниже 1024 и скорей всего будете использовать iptable nap mapping или apache proxy. Оба этих варианта не будут самым оптимальным способом использование этой фукнции. Потому я совсем рекомендую этот последний варинат не использовать.
Для запуска апача необходимо настроить server.xml и включить Connector https. Такая настройка уже есть в конфиге, достаточно убрать комментарии и указать путь к хранилищу сертификатов:
maxThreads="150" scheme="https" secure="true"
clientAuth="want" sslProtocol="TLS"
keystoreFile="${catalina.base}/conf/keystore" keystorePass="123456"
truststoreFile="${catalina.base}/conf/keystore" truststorePass="123456" />
И исправить второй коннектор, его порт:
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
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
TG2480
CUPS pdf - printer
/usr/sbin/lpadmin -p Cups-PDF -v cups-pdf:/ -m CUPS-PDF.ppd -E
/usr/sbin/lpadmin -E -p VKP80 -P VKP80.ppd -m VKP80 -v usb://CUSTOM%20Engineering/VKP80
/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.
Так хранить или не хранить проектыные файлы в репозитории, даже если они могут быть сгенерированы? Мое мнение - хранить.
Позволю себе на эту тему небольшую лекцию. Все файлы исходников, которые имеют отношение к проекту храняться в репозитории. Все настройки, которые производятся локально, не должны затрагивать репозиторий. Так если я создаю скрипты, которые запускают локальный сервер с логином и паролем - я не кладу эти файлы в репозиторий, и не держу их в дирректории с локальным репозиторием (можно хранить на следующим уровне вверх). Так как если этот файл поместить в дирректорию с репозиторием этот файл будет отображаться каждый раз когда я запрашиваю статус проекта под системой контроля версии. Таким образом команда 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
Поскольку система сборки очень запутана и сайт не достаточно информативен делаю набор выдержек, которые могут пригодится для прошивки роутера.
База данных: нужно вбить имя роутера и кликнуть на его имени (ссылка не подсвечивается)
Последние сборки доступны по ссылке (в дирректории dlink-dir300-revb):
На сайте предоставлены 2 вида прошивок: factory-webflash, ddwrt-webflash. Используется только factory-webflash. ddwrt-webflash используется для перепрошивки уже перепрошитого роутера (upgrade).
Ссылки на вики для роутера DIR-300:
Восстановление оригинальной прошивки (файл dir300b_v2.06_b9fe.bin):
Поскольку система сборки очень запутана и сайт не достаточно информативен делаю набор выдержек, которые могут пригодится для прошивки роутера.
- Роутер, в режиме восстановления не прошивается из других под других ОС кроме Windows. Ошибка будет очень не информативна. При прошивке роутера он напишет что файл испорчен.
База данных: нужно вбить имя роутера и кликнуть на его имени (ссылка не подсвечивается)
Последние сборки доступны по ссылке (в дирректории dlink-dir300-revb):
На сайте предоставлены 2 вида прошивок: factory-webflash, ddwrt-webflash. Используется только factory-webflash. ddwrt-webflash используется для перепрошивки уже перепрошитого роутера (upgrade).
Ссылки на вики для роутера DIR-300:
- http://www.dd-wrt.com/wiki/index.php/DIR-600
- http://www.dd-wrt.com/wiki/index.php/D-Link_DIR-600_/_300vB
Восстановление оригинальной прошивки (файл dir300b_v2.06_b9fe.bin):
В настоящий момент очень плохо реализована поддержка IPTV. Пакеты маршрутизируются на все интерфейсы роутера и глушат полностью ВайФай. Данная проблема решена в последней версии ядра (2.6.34).
Hibernate nested transactions
Сталкивались вы когда-нибудь с вложенными транзакциями?
В двух словах: эта такая транзакция которая создается внутри родительской транзакции. И может быть обработана независимо от основной.
Но проще это объяснить на примере.
Например. Например вам нужно сделать сервис, который будет регулярно опрашивать базу данных и проводить обработку транзакций. Каждый такой вызов транзакции должен сопровождаться обращением в интернет или какую-другую нестабильную среду и в результате чего может образоваться ошибка (или что более правильно исключение).
Вот как-то так.
Что бы обеспечить стабильность, вы должны обрабоать эту ошибку и в дальнейшем привизать эту ошибку к транзакции.
То есть простым языком получаете список необработанных транзакций. Вызываете функцию обработки транзакции и ошибку записываете в другое поле той же транзакции.
Как все это реализовать на Hibernate \ Spring?
Ну в документации сказано что есть такое средство. Называется nested transaction. Для его активации у метода, создающего вложенную транзакцию необходимо использовать атрибут PROPAGATION_NESTED. А еще прописать в tarnsactionmanager проперти nestedTransactionAllowed со значением true.
Вот как выглядит эта теория на практике:
dao.xml
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
IncomeProcessor.java
В приведенном примере создан базовый класс для обработки вложенных транзакций с использованием фукнций сохранения \ отката состояний (Savepoint) и так же исправлена проблема с откатом объектов на памяти вызовом фукнции sessionFactory.getCurrentSession().refresh(e)
В двух словах: эта такая транзакция которая создается внутри родительской транзакции. И может быть обработана независимо от основной.
Но проще это объяснить на примере.
Например. Например вам нужно сделать сервис, который будет регулярно опрашивать базу данных и проводить обработку транзакций. Каждый такой вызов транзакции должен сопровождаться обращением в интернет или какую-другую нестабильную среду и в результате чего может образоваться ошибка (или что более правильно исключение).
Вот как-то так.
Что бы обеспечить стабильность, вы должны обрабоать эту ошибку и в дальнейшем привизать эту ошибку к транзакции.
То есть простым языком получаете список необработанных транзакций. Вызываете функцию обработки транзакции и ошибку записываете в другое поле той же транзакции.
Как все это реализовать на 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);
}
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);
}
}
суббота, 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 {
Для сериализации объектов достаточно имплиментировать интерфейс Serializable и после этого одним вызовом метода writeObject класса ObjectOutputStream сохранять объекты в любой поток. Этот поток может быть массивом на памяти, файлом или сокетом. Создав правильным образом экземпляр класса ObjectOutputStream можно заливать данные в любых направлениях.
Однако я работал не с совсем стандартными объектами и их сериализация несколько усложнилась. В мою задачу входила серилизация объектов полученных из базы данных через Hibernate библиотеку и передача их на другую машину в сети. Теперь я хочу поделиться найденым решением для удобной сериализации Hibernate объектов.
Работая с базой данных через Hibernate у вас есть выбор. Этот выбор напрямую скажется на том как эффективно и как сложно будет реализовавать мехнизм передачи объектов.
Первый вариант заключается в том, что бы получать все объекты полностью загруженными из базы данных. Это довольно простой способ, все данные загружены в объект и сериализация объектов сводится к простому помещению объектов в поток без подготовки. Однако, если вы начнете работать с базой данных в таком режиме - то это будет крайне не эффективно и врядли ваш сервер сможет рабоать больше чем с 10 пользователями одновременно.
Второй вариант позволяет получаеть объект не полностью инициализированным данными. Он заключается в том, что бы после получения объекта данные в нем подгружались из базы только после обращения к ним. То есть например, вы получаете объект записи транзакций за последние 3 месяца и обращаетесь к запясям начиная с 5000 номера по 9000 номер. В этом случае Хибернейт получит из базы только указанный интервал данных, без выкачивания всех остальных записей. Ко всему прочьему, так же не будут сразу загружены все связанные объекты и свойства классов. Работа по такой схеме очень ускоряется так как объем трафика и данных получаемой с базы уменьшается.
Однако если вы попытаетесь передать по сети полученный объект с без загруженных объектов то ява выдаст следующую ошибку:
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.
Скажем если вы загружаете объект командой:
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);
}
}
Метод вызываемый перед сериализацией:
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, которые должны быть инициализированы:
@Table(name = "wallet")
public class Wallet extends Lazy implements Serializable {
Подписаться на:
Сообщения (Atom)

