Дали ще бъдем модерни ако решим да използваме 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...
.....
Какво ли ще вечерям все пак?