Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Friday, September 28, 2007

Enum as dispatcher

    Вчера видях един от коментарите на предишно мое писание (ето това) и се сетих за нещо, което може да ми влезне в употреба. След като имам краен брой операции (enum), не може ли да "разхвърлям". Добре, няма да е много трудно. Примерно две операции: минус и плюс.

protected interface SumHandler {
   String handle(int one, int two);
}

protected enum Operation {

    minus(new MinusHandler()), plus(new PlusHandler());
    SumHandler handler;

    Operation(SumHandler handler) {
        this.handler = handler;
    }

    public String process(int one, int two) {
        return handler.handle(one, two);
    }
}

Това е случая, в който операциите плюс и минус са мнооого трудоемки или пък ни тряба отделна инстанция за всеки един от тях. При мен случая обаче е друг, много кратки обработки(даже тривиални), за които не е нужен никакво състояние.
Затова се изхитрям и ги реализирам като стойност, на която "присвоявам" функция. Да така е макар, че не се вижда на пръв поглед.
public class EnumAsDispatcher {
    private final static Logger log = Logger.getLogger(EnumAsDispatcher.class);

    protected interface SumHandler {
        String handle(int one, int two);
    }

    private static SumHandler minusHandler = new SumHandler() {

    public String handle(int one, int two) {
        log.debug("Substitution: one = " + one + ", two = " + two);
        return "Success substitution";
    }
};

private static SumHandler plusHandler = new SumHandler() {
    public String handle(int one, int two) {
        log.debug("Sum: one = " + one + ", two = " + two);
        return "Success sum";
    }
};

/** Enumeration for all operations (with attached functionality for each operation) */
protected enum Operation {
    minus(minusHandler), plus(plusHandler);

    SumHandler handler;

    Operation(SumHandler handler) {
        this.handler = handler;
    }

    public String process(int one, int two) {
        return handler.handle(one, two);
    }
}

    public static void main(String[] args) {

        String mathOperation = "minus";

        Operation operation = Operation.valueOf(mathOperation);
        operation.process(1, 2);

        mathOperation = "plus";

        operation = Operation.valueOf(mathOperation);
        operation.process(2, 1);
    }
}


Така че благодаря за подсказката

Friday, August 31, 2007

Repeated operations shortcut

   Когато пиша някакъв код се стремя той да е макасимално четим(разбирай опростен и подреден), но с времето нещата набъбват и четимоста отстъпва място на елегантноста. Ето и конкретния случай. Имам си класа SFAdapter, който прави връзката към SalesForce.com и изпънява заявките. Този менаджер може да бъде всъшност всякакъв примерно към базата. В него имах само три неща - инициализация на връзката, логин и метод за подаване на заявките:

................
public SFAdapter(final String sfUser, final String sfPass) {
if( sfUser==null || sfPass==null ) return;

this.sfUser=sfUser;
this.sfPass=sfPass;
try {
binding = (SoapBindingStub) new SforceServiceLocator().getSoap(new URL("some_url"));
} catch (Exception e) {

.....................

protected void doLogin() .............

public QueryResult query(final String ) .............

Да обаче проеката си нараства и се налага да се добавят все повече и повече методи, който са абсолуютно еднакви като структура:
public типа_които_ще_върне име(параметри) {
try {

изпънява_заявката

// следват нещата, които ще направи при грешка
} catch (UnexpectedErrorFault uef) {


И какво се получава - много писане. Верно проситчки неща ама много писане беее. Сегиз-тогиз е добре да се консултираш. Ето в какво се превърна кода след консултацията с колегите.
   Повтарящите се неща да се на едно място. Операциите трябва да се уеднаквят. Това е посоката на мислене. И самия код:
public void delete(final String[] ids) {
executeInSf(new WriteCommand<Object>() {
public Object execute() throws UnexpectedErrorFault, RemoteException {
binding.delete(ids);
return null;
}
});
}

public SObject[] retrieve(final String fieldList, final String sObjectType, final String[] ids) {
return executeInSf(new ReadCommand<SObject[]>() {
public SObject[] execute() throws UnexpectedErrorFault, RemoteException {
return binding.retrieve(fieldList, sObjectType, ids);
}
});
}

private <T> T executeInSf(Command<T> c) throws RecoverableSFException, CriticalSFException {
try {
return c.execute();
} catch (UnexpectedErrorFault uef) {
if (uef.getExceptionCode().equals(ExceptionCode.INVALID_SESSION_ID)) {
try {
doLogin();
return c.execute();
} catch (UnexpectedErrorFault e) {
throw c.createException(e);
} catch (InvalidIdFault e) {
................... 
}

interface Command<T extends Object> {
T execute() throws UnexpectedErrorFault, RemoteException;
RecoverableSFException createException(Exception cause);
}

abstract class ReadCommand<T extends Object> implements Command<T>{
public SFReadException createException(Exception cause) {
return new SFReadException(cause);
}
}

abstract class WriteCommand<T extends Object> implements Command<T>{
public SFWriteException createException(Exception cause) {
return new SFWriteException(cause);
}
}

С простички думи. Операцията, която трябва да се изпълни я дефинираме в интерфейс(Command), а нещата които се повтарят в един метод, който приема като параметър този инерфейс(executeInSf). Следва и самото изпълнение(retrieve), която не прави нищо друго освен да иплементира изпълнението на операцията - execute метода.
NOTE: В метода delete забележете, че нама return, а execute трябва задължително да върне стойност и тя е null.
Малко се усложниха нещата, защото имам два вида грешки:
try {
doLogin();
return c.execute();
} catch (UnexpectedErrorFault e) {
// see here!
throw c.createException(e);
...........

Правилото е, ако има има различия и допълнителни неща те се отделят в абстрактни класове, които наследяват инерфейса.


И всичко това само, защото в Java не можем да предаваме функции. Ако беше така, на executeInSf щахме да предадем функцията, която трябва да се изпълни. Може би посоката на мислене трябва да е към езици за функционално програмиране.

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...
.....
Какво ли ще вечерям все пак?

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)