Sunday, September 10, 2006

Fashion Axis-Hibernate

Дали ще бъдем модерни ако решим да използваме Java, Linux, JBoss, Apache Axis и Hibernate3?
Определено ДА. Наскоро ми се наложи да сглобя нещо такова за много кратко време.
Като разпитах се оказа, че това и често избирана комбинация при разработката на софтуер. Затова ще споделя моя опит и идеи.
Да се направи нещо такова:
Имаме някакъв уеб базирано приложение и когато се обърнем към него през уеб сървисите, които той предлага, взема нещо от базата дани и ни врща резултат.
Ама и аз какво описание дадох. Много общо го формулирах ама който се интересува ще му стане ясно за какво идва реч.

Ето какви мисли ме налегнаха, когато видях задачата и как си отговорих на тях.

Каква линукс дистрибуция да използвам?
Как да напрявя така че да променям приложението най-безболезнено?
Дали ще мога да генерирам част от кода?
и още един допълнителен: Какво ще ям довечера?

Първия въпрос го отсях веднага щом ми казаха, че имам пълна свобода за хостващата система.
Казах си, да бъде Debian. При Дебиан нещата с инсталирането на java са специфични. Няма .deb пакети затова човек сам трябва да си го направи. Ето как:

1. Като root си инсталирам нещата, които ми трябват за създаването на java пакета:
# apt-get install java-package

2. Важно! "fakeroot" се използва за създаването на .deb
# apt-get install fakeroot

3. Вече не съм root.

4. Изтеглям <SUN JAVA SDK FILENAME>.bin от сайта на Sun .
Изтегляния файл го записвам в една temp директория.

5. Смело стартирам 
$ fakeroot make-jpkg <SUN JAVA SDK FILENAME>.bin

6. Инсталирам Java .deb Packagе вече като root:
# dpkg -i <GENERATED DEB PACKAGE FILENAME>.deb

7. Проверявам какво съм направил и дали рабои
$ java -version

5. Setup на JAVA_HOME, CLASSPATH и JBOSS_HOME в /etc/profile

export JAVA_HOME=/usr/lib/j2sdk1.5-sun/
export CLASSPATH=.

export JBOSS_HOME=/opt/jboss-4.0.4.GA


Подготвям се старателно и за JBOSS. Директорията /opt съм я отделил предвидливо в отделен patition. Създавам и user - jboss.
Логвам се като jboss свалям последната версия на сървъра и разархивирам в /opt. Да обаче като гледам как се пуска си викам, абе какво ще стане ако сървъра се рестартира? Някои трябва да го стартира автоматично всеки път. За production сървъра ще е добре jboss да се пуска като демон. Ето как става и това:
#! /bin/sh
# /etc/init.d/jboss
#

# NOTE: Points JBoss installation dir
JBOSS=/opt/jboss-4.0.4.GA

start(){
echo "Starting jboss..."
su -l jboss -c "$JBOSS/bin/run.sh > /dev/null 2> /dev/null &"
}

stop(){
echo "Stopping jboss..."
su -l jboss -c "$JBOSS/bin/shutdown.sh -S &"
}

restart(){
stop
sleep 60
# protect against any services that can't stop before we restart
# (warning this kills all Java instances running as 'jboss' user)
su -l jboss -c 'killall java'
start
}

case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
*)
echo "Usage: /etc/init.d/jboss {start|stop|restart}"
exit 1
esac

exit 0

В Дебиан, за да сложиш една програма като демон съпките са следните:
# инсталираме
Write this file as /etc/init.d/jboss
chmod 755 /etc/init.d/jboss
update-rc.d jboss defaults

# ако искаме да го разкараме
update-rc.d -f jboss remove


Имам си средата за исталация на приложението, време е да се заема със структурата на проекта. Това е и отговора на моя втори въпрос.
Искам всяко нещо да си знае мястото и ant скрипта да ми е максимално упростен. Да знам къде се намират генерираните файлове и къде е бизнес логиката. Мислих, мислих и ето какво измислих:
bin - тук се намират всички компилирани класове. 
Запомнете, при компилация всички конфигорационни файлове трябва да се копират
тук(всичко от conf directory)!
conf - конфигорационни файлове
data (dbschema.sql)- sql който ще се генерира
lib - всички нужни директории
src (com.zlatozar.persistence, com.zlatozar.persistence.base, com.zlatozar.soap, com.zlatozar.common)- всички сорсове
test (com.zlatozar...) - всички тестове. Запомнете, че src и test трябва да имат еднаква структура
webcontent (html, jsp) - нещата за нашия сайт
WEB-INF (web.xml, server-config.wsdd)- нужните конфигурации за сайта

При тази схема на проекта много лесно мога да генерирам нужния ми war файл и да пускам всички тестове. Идва най-интересната част за мен, настройката на Apache Axis. Нека първо да погледнем катинката и после е лесно - разказ по картинка.

Какви са минималните усилия за направата на Web Services(WS)? Пиша интерфейса, който искам да се вижда "навън" и пиша клас, които го имплементира всичко друго е Axis гимнастика. Давам пример:
package com.zlatozar.soap;
public interface SaySomething extends java.rmi.Remote {

public String sayHello(String name) throws java.rmi.RemoteException;

public String sayGoodbye() throws java.rmi.RemoteException;

public String writeSometingToDB(String id, String something)  throws java.rmi.RemoteException;

}

ето и имплементацията:
package com.zlatozar.soap;

import java.rmi.RemoteException;
import com.zlatozar.persistence.QueryManager;

public class SaysomethingSoapBindingImpl implements SaySomething {

public String sayHello(String name) throws RemoteException {
return "hello " + name + " !";
}

public String sayGoodbye() throws RemoteException {
return "goodbye my master !";
}

public String writeSomethingToDB(String id, String something)
throws RemoteException {

Something ft = new SomethingBase(id, something);
QueryManager.saveTicketToDB(ft);

return "Done. Persistance is cool. Bye.";
}

// Utilties methods
}

Обаче тук има една важна особенност: пише се SaysomethingSoapBindingImpl, а не SaySomethingSoapBindingImpl. Гледаме картинката. От тези два класа искаме да получим всичко за "axis deployment"(server side). Тук е и тънката хватка. От тези два класа генерирам .wsdl файла после от него нещата които са ми необходими - SaysomethingSoapBindingSkeleton и server-config.wsdd. Вижте и ant scrip-a ми:
<!-- ================================ -->
<!--       Axis tools definiion       -->
<!-- ================================ -->

<taskdef name="axis-java2wsdl" classname="org.apache.axis.tools.ant.wsdl.Java2WsdlAntTask">
<classpath refid="compile.classpath" />
</taskdef>

<taskdef name="axis-wsdl2java" classname="org.apache.axis.tools.ant.wsdl.Wsdl2javaAntTask">
<classpath refid="compile.classpath" />
</taskdef>

<!-- ================================ -->
<!--       Axis code generation       -->
<!-- ================================ -->

<target name="export-wsdl" depends="prepare">

<axis-java2wsdl
classname="com.zlatozar.soap.SaySomething" 
style="rpc"
classpathref="compile.classpath"
namespace="urn:soap.zlatozar.com" 
location="http://${server.name}:8080/${project.distname}/soap/${project.distname}" 
output="${basedir}\generated\${project.distname}.wsdl">
</axis-java2wsdl >

</target>

<target name="export-java" depends="export-wsdl">

<axis-wsdl2java 
output="${basedir}\generated" 
serverside="true" 
skeletondeploy="true" testcase="true"
url="${basedir}\generated\${project.distname}.wsdl" 
verbose="true" 
debug="true">
</axis-wsdl2java>

<java 
classname="org.apache.axis.utils.Admin" 
fork="true" 
failonerror="true" 
classpathref="compile.classpath"
dir="${basedir}\WEB-INF\">
<arg value="server" />
<arg file="${basedir}\generated\com\zlatozar\soap\deploy.wsdd" />
</java>

<!-- copy the files we need to the src/com/zlatozar/soap dir -->
<copy todir="src/com/zlatozar/soap" includeEmptyDirs="no">
<fileset dir="generated/com/zlatozar/soap">
<patternset>
<include name="*SoapBindingSkeleton.java" />
</patternset>
</fileset>
</copy>

<!-- copy the wsdd to WEB-INF/server-config.wsdd -->
<copy file="generated/com/zlatozar/soap/deploy.wsdd" 
tofile="WEB-INF/server-config.wsdd" />

<!-- copy generated WSDL file -->
<copy file="generated/${project.distname}.wsdl" 
todir="." />
</target>



Такааа, да поясним. Създавам директория generated, която след това мога да изтрия лесно. Генерирам wsdl файла и го използвам за генерацията на SaysomethingSoapBindingSkeleton. В soap пакета трябва да имам генериран SaysomethingSoapBindingSkeleton.java, а в WEB-INF server-config.wsdd. В писането на build.xml обърнете внимание на namespace="urn:soap.zlatozar.com" - името на пакета ама в обратен ред и прехвърлянето на generated/com/zlatozar/soap/deploy.wsdd в WEB-INF/server-config.wsdd.
До тук бяхме с Axis. Да се заемем с конфигурирането на Hibernate3.

Само да кажа, че бях доста затруднен, не в самото конфигуриране а да измисля "доброто конфигуриране".
Тъй като това беше първия ми сблъсък с Hibernate3 исках да намерея бърз начин да науча основните стъпки.

От къде да прочета нещо?

Разберах как се "мапват" класове и таблици
По advanced неща

Как да генерирам java класовете от .hbm.xml файловете?
За целта трябва да имам hibernate-tools.jar. Този jar е част от Hibernate-tools проекта и за да го имате трябва да свалите този
проект, който не е никак малък като обем и от там да си гепите толкова полезния tool. При мен нещата стоят ето така:
<!-- Hibernate code generation -->
<target name="hibernategen" depends="init" description="Generate what hibernate needs">

<property name="libHibernatetool" value="lib/hibernatetool" />

<path id="hibernateTool">
<pathelement location="${outputDir}" />
<fileset dir="${libHibernatetool}">
<include name="*.jar" />
</fileset>
</path>

<taskdef name="hibernate-tool" classname="org.hibernate.tool.ant.HibernateToolTask" classpathref="hibernateTool" />

<hibernate-tool>
<configuration configurationfile="${conf}/hibernate.cfg.xml" />
<hbm2ddl export="true" drop="true" create="true" format="true" outputfilename="dbschema.sql" destdir="data" />
<hbm2java destdir="${sourceDir}" />
</hibernate-tool>
</target>


След като имам нужните .hbm.xml лесно мога да получа нужните ми класове и sql-a.

Една добра идея е да отделя генерираните неща от нещата, които се променят. Какво имам в предвид. При генерацията се получават POJO обекти и нищо повече, а аз може да искам да добавя нещо повече в тези обекти, като utility методи. Как да отделя променящото се от непроменящото се? Вижте:
<hibernate-mapping package="com.zlatozar.persistence.base">
<class name="SomethingBase" table="SAYSOMETHING_T">
<meta attribute="implement-equals">true</meta>
<meta attribute="scope-field">protected</meta>

<id name="sys_oid" column="SYS_OID" type="long">
<generator class="increment" />
</id>

<property name="id" type="string" column="ID_C">
<meta attribute="use-in-equals">true</meta>
</property>

<property name="something" type="string"
column="SOMETHING_C">
<meta attribute="use-in-equals">true</meta>
</property>

</class>
</hibernate-mapping>


И така имам си com.zlatozar.persistens.base за POJO обектите - hibernate да си генира там каквото иска. Аз си имам:
public class Something extends SomthingBase

и си работя с него. А самото генериране не е сложно като видяхте. Почти свършихме само още няколко мисли за hibernate.
Остана управлението на сесиите към базата. Аз имам един Singleton клас InitSessionFactory, който отваря и затваря сесията.
public static SessionFactory getInstance() {
if ( sessionFactory == null )
initSessionFactory();

return sessionFactory;
}

a всички запитвания минават през QueryManager. Нещo такова:
public static void saveSomthing(SomethingBase aSome) {

Transaction tx = null;
Session session = InitSessionFactory.getInstance().getCurrentSession();
try {
tx = session.beginTransaction();

session.save(aSome);

tx.commit();
} catch (HibernateException e) {
log.error("Opperation will be rolled back", e);

if (tx != null && tx.isActive())
tx.rollback();
}
}


Е това е за сега. Решил съм да споделям и програмиските си главоблъскници. Meчтая си скоро да напиша същото и на Lisp...
.....
Какво ли ще вечерям все пак?

2 comments:

Anonymous said...

Mnogo gotin post!

Радо said...

Здрасти,
Няма да е трудно да се направи и на Лисп :)
Започни от тук: http://www.adampetersen.se/articles/lispweb.htm

Няма да е трудно и да се добавят Web Services. Очаквам и същата статия на Лисп. Успех.

algorithms (1) cpp (3) cv (1) daily (4) emacs (2) freebsd (4) java (3) javascript (1) JSON (1) linux (2) Lisp (7) misc (8) programming (16) Python (4) SICP (1) source control (4) sql (1) думи (8)