В данном разделе будут рассмотрены сеансовые (Session),объектные (Entity) и управляемые сообщениями (Message-driven) компоненты (Beans) EJB.Все примеры разработаны в среде Windows XP SP2.Несмотря на это, все используемые компоненты являются кросс-платформенными, так что примеры должны работать так же в Linux, Solaris или Mac OS X.Для демонстрации примеров необходимо следующее ПО:
EJB ) - открытый сервер приложений JBoss (http : //www .jboss. org/);Eclipse, в которую добавлена широкая поддержка веб-приложений и EJB. Скачать ее можно также на сайте Eclipse - http://www. eclipse. org/.web -приложений, взаимодействующих через EJB с базой данных будут использоваться веб-сервер Apache (http : //www . apache.org/), и контейнер JSP Tomcat (http://tomcat. apache. org/).Серверные компоненты Enterprise Java Beans бывают трех принципиально разных типов:
Сеансовые и объектные компоненты являются серверными компонентами, основанными на RMI,к которым можно получить доступ при помощи распределенных объектных протоколов. Компоненты, управляемые сообщениями, появившиеся в EJB 2.0 - это асинхронные серверные компоненты, которые отвечают на асинхронные сообщения JMS и других протоколов.
Хорошее эмпирическое правило при конструировании компонентов EJB состоит в том, что объектные компоненты моделируют прикладные понятия, которые могут быть выражены существительными. Например, объектный компонент может представлять покупателя, часть оборудования, элемент инвентаря или даже место. Другими словами, объектные компоненты моделируют объекты реального мира. Эти объекты являются обычными постоянными записями в какой-либо базе данных.
Действия, которые принимают
Главное различие компонентов в том, что объектные компоненты имеют устойчивое состояние, а
Для реализации объектного или
В данной главе будут сначала рассмотрены примеры
create () его домашнего интерфейса (Home Interface).EJB, которые могут использоваться другими компонентами, размещенными в том же контейнере EJB,но не внешними по отношению к контейнеру EJB приложениями. Он позволяет компонентам, находящимся в одном контейнере EJB,взаимодействовать
без дополнительных затрат, связанных с применением протокола распределенных объектов, что повышает их производительность. Локальный интерфейс расширяет (extends) интерфейс javax.ejb.EJBLocalObject.Внешние по отношению к EJB компоненты, находящиеся в том же самом контейнере, работают с методами локального интерфейса, а получают эти методы они при помощи локального
домашнего интерфейса (Local Home Interface)Важным отличием между удаленным и локальным интерфейсом является то, что вызовы методов локального интерфейса не используют RMI и не генерируют RemoteException.Для корректной работы не обязательно предоставлять классу компонента все четыре интерфейса. К примеру, если компонент не используется никаким клиентским приложением (а нужен только другим компонентам, работающим под управлением того же EJB контейнера), то нет необходимости предоставлять удаленный интерфейс. То есть в зависимости от тех задач, которые решает данный компонент, ему может быть предоставлен либо локальные, либо удаленные, либо и те и другие интерфейсы.
ejbCreate(), соответствующие некоторым методам в обоих, домашнем (Home Interface) и локальном домашнем (Local Home Interface),интерфейсах. Объектные компоненты должны реализовывать интерфейс javax.ejb.EntityBean,а onMessage (). Этот метод вызывается при прибытии нового сообщения.У компонентов, управляемых сообщениями нет никаких компонентных интерфейсов, но, несмотря на это, они могут быть клиентами любых сеансовых или объектных компонентов и взаимодействовать с этими компонентами через их интерфейсы. Объектные и
byte, boolean, char, int, long, short, double, float ;java .io.Serializable ;java.rmi.RemoteException.При попытке передать через удаленный метод объект, не относящийся к одному из трех типов, будет сгенерировано исключение java.rmi.RemoteException. Каждый метод в удаленном интерфейсе (Remote Interface) должен генерировать исключение java.rmi.Remote. Оно генерируется при проблемах с передачей данных в сети или невозможности найти требуемый сервер. Помимо этого разработчики могут
использовать любые другие исключения при разработке компонент EJB.
Протокол Java RMI IIOP также накладывает ограничения на удаленные интерфейсы. Этих ограничений много, но реально используется только два из этих ограничений.
Существует много инструментальных средств, которые позволяют сильно упростить работу с EJB.В частности одним из средств, которое также предоставляет платформа Eclipse-WTP,является средство создания связей между объектными компонентами и базой данных. В Eclipse-WTP это делается при помощи пакета XDoclet.Каким образом это делается, будет рассмотрено далее.
Class.newInstance () создается экземпляр класса компонента.SessionBean.setSessionContext( SessionContext context). При помощи этого EJBContext, предоставляющий интерфейс для взаимодействия с контейнером EJB (в частности, в этом объекте есть метод lookup(), позволяющий искать другие объекты по путям JNDI).ejbCreate (). У При запуске контейнер обычно создает несколько экземпляров ejbRemove(). В этом методе следует закрыть все открытые подключения к различным ресурсам.
То, каким образом и когда контейнер EJB должен создавать и удалять экземпляры EJB. В каждом из существующих контейнеров EJB этот механизм реализован по-разному.
create() у домашнего интерфейса (Home Interface).После этого созданный компонент переходит в состояние готовых методов. В этом состоянии он готов выполнять свои методы, вызываемые клиентом через удаленную ссылку. Компонент в это время может хранить открытые
ресурсы (базы данных, TCP/IP сессии и др.), а также хранить посредством своих переменных определенное состояние, которое может изменяться при вызове клиентом различных его методов. Последовательность методов, вызываемых контейнером при создании
При нахождении компонента в состоянии готовых методов, он может либо оставаться в этом состоянии, либо переведен в пассивное состояние (при этом будет вызван метод ejbPassivate () - см. далее), либо быть удаленным. То, при каких условиях компонент будет переведен в пассивное состояние, в каждом из существующих контейнеров определяется по-разному. При активизации компонента (вызывается метод ejbActivate () - см. далее),
компонент переводится из пассивного состояние в состояние готовых методом. При этом значение всех внутренних переменных и состояние диалога сохраняется.
Компонент может быть удален при вызове соответствующего метода remove() домашнего интерфейса, либо контейнером после определенного промежутка времени, в течение которого компонент "бездействовал". Компонент не может быть удален при выполнении транзакции. При удалении у компонента будет вызван метод ejbRemove().
Разработаем correctTimeBean.
Сначала определим удаленный интерфейс (Remote Interface) разрабатываемого компонента в файле CorrectTimeRemote.java. Все виды удаленных интерфейсов расширяют (extends) интерфейс javax.ejb.EJBObject.
package correctTimeBean;
import java.rmi.RemoteException;
import java.util.Date;
import javax.ejb.EJBObject;
public interface CorrectTimeRemote extends EJBObject
{
public Date obtainCorrectTime() throws RemoteException;
}
Здесь присутствует единственный метод obtainCorrectTime() - он возвращает текущее время сервера. Этот метод может генерировать исключение RemoteExcepton,требуемое для всех методов удаленных интерфейсов.
Теперь определим домашний интерфейс (Home Interface) в файле CorrectTimeHomejava.
package correctTimeBean;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface CorrectTimeHome extends EJBHome
{
public CorrectTimeRemote create() throws RemoteException, CreateException;
}
Метод create () отвечает за create() может быть перегружен (overloaded),то есть, определены методы create с возможно другим списком формальных параметров. Вызов метода create() возвращает удаленную ссылку компонента CorrectTimeBean.
Теперь рассмотрим класс, реализующий разрабатываемый компонент (файл CorrectTimeBean.java ).
package correctTimeBean;
import java.rmi.RemoteException;
import java.util.Date;
import javax.ejb.EJBException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
public class CorrectTimeBean implements SessionBean
{
public static final long serialVersionUID = 6066529880637710711L;
public void ejbCreate()
{
}
public Date obtainCorrectTime()
{ return new Date();
}
public void ejbActivate() throws EJBException, RemoteException
{
}
public void ejbPassivate() throws EJBException, RemoteException
{
}
public void ejbRemove() throws EJBException, RemoteException
{
}
public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException
{
}
}
В этом классе определен метод obtainCorrectTime(). Он возвращает текущее время на сервере. Помимо этого, в данном компоненте определены еще несколько методов.
void ejbCreate(). Компоненты EJB никогда не должны определять конструкторы. Необходимая инициализация проводится в методе ejbCreate (). Для ejbCreate() не вызывается.void ejbActivate() throws EJBException, RemoteException. Этот метод используется для void ejbPassivate() throws EJBException, RemoteException. Этот метод используется для void ejbRemove() throws EJBException, RemoteException. Этот метод для void setSessionContext(SessionContext arg0).Также определено поле public static final long serialVersionUID. Начиная с Java 1.5 во всех классах, которые имплементируют (implements) интерфейс java.io.Serializable, рекомендуется определить это поле с уникальным 64-битным ключом. В случае если это поле не будет определено, то Java -машине придется
генерировать его при serialVersionUID может быть сгенерировано при помощи встроенного в Eclipse-WTP механизма (см. далее), либо при помощи утилиты serialver
, входящей в любой Java Development Kit.Более подробно про механизм сериализации можно прочитать в Java Object Serialization Specification.
Теперь необходимо определить XML -XML -файлами. Для EJB 2.1
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar id="ejb-jar ID" version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2 0 01/XMLSchema-instance" xsi:schemaLocation= "http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar 2 1.xsd"> <description>First session bean</description> <display-name>CorrectTimeProject</display-name> <enterprise-beans> <session> <ejb-name>CorrectTimeBean</ejb-name> <home>correctTimeBean.CorrectTimeHome</home> <remote>correctTimeBean.CorrectTimeRemote</remote> <ejb-class>correctTimeBean.CorrectTimeBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> </ejb-jar>
В этом дескрипторе развертывания имеются следующие элементы:
<ejb-jar> - корневой элемент. В нем обязательно должен быть один элемент <enterprise-beans>, а также дополнительные элементы.<description> - словесное описание.<display-name> - этот элемент представляет собой идентификатор, который могут использовать некоторые программы, работающие с <enterprise-beans> - содержит объявления всех компонентов, описываемых этим <session> - в этом элементе описывается <ejb-name> - имя компонента.<home> - полное имя класса домашнего интерфейса.<remote> - полное имя класса удаленного интерфейса.<ejb-class> - полное имя класса компонента. Этот класс реализует прикладные методы компонента.<session-type> - тип Stateless - без состояния, Stateful - с состоянием.<transaction-type> - определяет, управляет ли EJB. Значение Container - управляет контейнер, значение Bean - управляет компонент.Теперь создадим клиентское приложение (файл CorrectTimeClient.java ), которое будет подключаться к контейнеру EJB,получать ссылку на удаленный интерфейс компонента, вызывать метод получения серверного времени, а также печатать это время вместе со временем на клиентском компьютере.
public class CorrectTimeClient
{
public static void main(String[] args)
{
try
{
Context jndiContext = createJBossContext();
Object ref = jndiContext.lookup("CorrectTimeBean");
CorrectTimeHome home = (CorrectTimeHome) PortableRemoteObject.narrow(ref, CorrectTimeHome.class);
CorrectTimeRemote remote = home.create();
Date localDate = new Date();
Date remoteDate = remote.obtainCorrectTime();
System.out.println("Remote time is " +remoteDate.toString()+
"\nCurrent time is " + localDate.toString());
}
catch (RemoteException e)
{
e.printStackTrace();
}
catch (NamingException e)
{
e.printStackTrace();
}
catch (CreateException e)
{
e.printStackTrace();
}
}
public static Context createJBossContext() throws NamingException
{
Properties p = new Properties();
p.put("java.naming.factory.initial",
"org.jnp.interfaces.NamingContextFactory");
p.put("java.naming.provider.url", "jnp://127.0.0.1:1099");
p.put("java.naming.factory.url.pkgs",
"org.jboss.naming:org.jnp.interfaces");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
}
Клиент, собирающийся обратиться к компоненту, начинает с использования пакета JNDI (Java Naming and Directory Interface) для получения соединения с контейнером компонента. JNDI - это независимый от реализации API для систем имен и каталогов. Каждый производитель EJB должен предоставить -совместимую службу каталогов.
При помощи JNDI клиент получает удаленные ссылки на компоненты, размешенные в контейнере EJB. Метод createJBossContext() содержит логику, основывающуюся на JNDI для получения сетевого соединения с сервером JBoss.Для разных контейнеров EJB этот код будет разным, например, для контейнера WebSphere код выглядит иначе:
public static Context createWebSphereContext() throws NamingException
{
Properties p = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY,
"com.ibm.websphere.naming.WsnInitialContextFactory");
props.put(Context.PROVIDER_URL,"iiop://localhost:900");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
Подробнее JNDI будет рассмотрен далее в отдельной главе.
Параметр java.naming.provider.urI в теле метода createJBossContext () задает URL сервера приложений JBoss.В данном примере этот параметр равен jnp://127.0.0.1:1099/,что соответствует JNDI сервису от JBoss,запущенного на локальной машине. После установления -соединения и получения контекста из метода createJBossContext ()
этот контекст можно использовать для поиска внутреннего объекта CorrectTimeBean.
Object ref = jndiContext.lookup("CorrectTimeBean");
В нашем клиентском приложении используется метод
PortableRemoteObject.
PortableRemoteObject.
Имя, по которому выполняется поиск внутреннего объекта компонента CorrectTimeBean,задается во время развертывания. Оно может совпадать с именем компонента в XML- дескрипторе, либо быть чем-то совершенно другим.
Получив удаленную ссылку на внутренний объект, мы может использовать ее для создания нового объекта компонента CorrectTimeBean.
CorrectTimeRemote remote = home.create();
На самом деле, как уже говорилось раньше, create() - у контейнера EJB есть пул уже готовых компонентов без состояния. На вызов create() он просто возвращает один из них. Теперь мы можем вызвать метод obtainCorrectTime (), в результате чего будет вызван через RMI соответствующий метод у компонента из контейнера EJB.
Date remoteDate = remote.obtainCorrectTime();
Все готово, чтобы реализовать код в проекте Eclipse-WTP.Будут рассмотрены две реализации данного компонента - одна только при помощи технического средства Ant,встроенного в EcIipse-WTP,а другая - при помощи внутренних средств EcIipse-WTP,предназначенных для развертывания компонентов на различных контейнерах EJB.Начнем с создания проекта. Выберем закладку File->New->Project в главном меню.
(рис 3.1) Выбор типа проектаEclipse-WTP позволяет создавать специальные типы проектов для EJB.Но для того, чтобы лучше понять сам процесс создания и развертывания компонент EJB,сделаем обычный Java- проект (Рис. 3.1).
(рис 3.2) Название проекта Зададим проекту название CorrectTime (Рис. 3.2).
(рис 3.3) Выбор каталога с исходными кодамиСоздаем каталог ejbModuIe для исходных кодов EJB -компонента. Делается это при помощи опции Create new source folder (Рис. 3.3).
(рис 3.4) Создаем пакет для классов компонента CorrectTimeBeanПосле того, как создан каталог с исходным кодом, создается пакет для классов компонента. Делается это при помощи щелчка правой кнопкой мыши на папке с исходными кодами и выбора пункта меню New->Package (Рис. 3.4).
(рис 3.5) Выбор библиотек JBossПосле этого необходимо подключить необходимые библиотеки JBoss - jbossall-client.jar и jboss-j2ee.jar. Они находятся в подкаталоге client каталога, в который был установлен JBoss.Необходимые библиотеки можно добавить, войдя в закладку Project главного меню: Project -> Properties -> Java Build Path -> Add External Jars, и затем выбрав в появившейся
форме необходимые библиотеки (Рис. 3.5).
Необходимые приготовления выполнены, теперь можно приступать непосредственно к написанию кода компонента и клиента.
(рис 3.6) Меню создания интерфейсаВначале создаем удаленный интерфейс. Делается это правым щелчком мыши на соответствующем пакете и выборе New -> Interface. В появившемся меню (Рис. 3.6) можно выбрать имя интерфейса, а также те интерфейсы, от которых он наследуется (extends).В качестве альтернативы, можно явным образом задать наследуемые интерфейсы прямо в коде.
(рис 3.7) Удаленный интерфейс
(рис 3.8) Метод удаленного интерфейсаТеперь в созданном удаленном интерфейсе задаем единственный метод obtainCorrectTime() компонента CorrectTimeBean (Рис. 3.7, Рис. 3.8).
(рис 3.9) Домашний интерфейс (Home Interface)Аналогичным образом создаем домашний интерфейс (Home Interface) (Рис. 3.9).
(рис 3.10) Класс компонента После этого создается непосредственно класс компонента (Рис. 3.10).
(рис 3.11) Создание файла дескриптора развертыванияТеперь необходимо задать
(рис 3.12) Файл дескриптора компонентаJAR -архива с EJB -компонентом. Этот файл выглядит следующим образом:
Manifest-Version: 1.0
Class-Path:
Теперь займемся созданием кода клиента. Вначале создадим еще один каталог для исходных кодов клиента в корневом каталоге проекта. Назовем этот каталог client. Внутри него создадим пакет с названием correctTimeBeanClient,и в него уже поместим класс CorrectTimeClient. Клиентское приложение также будет упаковано в JAR -архив. Так что для него тоже нужно будет сделать файл MANIFEST.MF. Вот как он будет выглядеть:
Manifest-Version: 1.0
Main-class: correctTimeBeanClient.CorrectTimeClient
Class-path: CorrectTimeBean.jar jbossall-client.jar jboss-j2ee.jar
Main-class - это класс, который содержит в себе метод main(String args[]). В Class-path указываются библиотеки, которые необходимо подключить. Помимо jboss-client.jar и jboss-j2ee.jar,туда добавлен архив и с самим компонентом - это сделано для того, чтобы клиент смог использовать удаленные интерфейсы компонента. Можно было бы, конечно, поместить классы удаленных интерфейсов в отдельный архив, или добавить их непосредственно в архив к клиенту, но в этом примере для простоты было сделано так.
(рис 3.13) Класс клиентаТеперь займемся написанием ANT -скрипта для сборки компонента, клиента, и размещению компонента на контейнере JBoss.В Eclipse-WTP есть встроенная поддержка ANT. Создаем в корневом каталоге проекта два файла - build.xml и build.properties. Файл buildproperties выглядит следующим образом:
# Отключает часть логов Ant verbose=no # Компилятор не будет добавлять debug-информацию в классы compile.debug=off # Путь до jboss jboss.server.home=C:\\ejb\\jboss\\server\\default
А вот так выглядит файл build.xml:
<?xml version="1.0"?>
<project name="CorrectTimeEJB" default="all" basedir=".">
<property name="app" value="${basedir}"/>
<property name="bean.src" value="${app}/ejbModule"/>
<property name="client.src" value="${app}/client"/>
<property name="bean.lib" value="${app}/lib"/>
<property name="client.lib" value="${app}/lib"/>
<property name="bean.config" value="${bean.src}/META-INF"/>
<property name="bean.name"value="CorrectTimeBean"/>
<property name="client.name" value="CorrectTimeClient"/>
<property name="client.deploy.dir" value="client-deploy"/>
<property name="build" value="build"/>
<property name="build.classesdir" value="${build}/classes"/>
<property name="build.client.classesdir" value="${build}/classes-client"/>
<property name="assemble" value="${basedir}/assembled"/>
<property name="assemble-client" value="${basedir}/assembled-client"/>
<target name="init">
<property file="${basedir}/build.properties"/>
<path id="build.classpath">
<pathelement path="${build.classesdir}"/>
<fileset dir="${bean.lib}" includes="*.jar"/>
</path>
<path id="build.client.classpath">
<pathelement path="${build.classesdir}"/>
<pathelement path="${build.client.classesdir}"/>
<fileset dir="${client.lib}" includes="*.jar"/>
</path>
</target>
<target name="compile" depends="init">
<echo message="Compiling sources."/>
<delete dir="${build.classesdir}"/>
<mkdir dir="${build.classesdir}"/>
<delete dir="${build.client.classesdir}"/>
<mkdir dir="${build.client.classesdir}"/>
<!--Compiling the java bean classes-->
<javac srcdir="${bean.src}"
destdir="${build.classesdir}"
verbose="${verbose}"
debug = "${compile.debug}"
deprecation="yes">
<classpath refid="build.classpath"/>
</javac>
<!--Compiling the client classes--> <javac srcdir="${client.src}"
destdir="${build.client.classesdir}"
verbose="${verbose}"
debug = "${compile.debug}" deprecation="yes">
<classpath refid="build.client.classpath"/>
</javac>
</target>
<target name="ejbjar" depends="compile">
<echo message="Assembling JBoss ${bean.name} module." />
<delete dir="${assemble}"/> <mkdir dir="${assemble}"/>
<jar destfile="${assemble}/${bean.name}.jar">
<!-- classes -- >
<fileset dir="${build.classesdir}">
<include name="**/*.class" /> </fileset>
<!-- Descriptors -->
<metainf dir="${bean.config}" includes="ejb-jar.xml"/>
</jar>
</target>
<target name="clientjar" depends="compile">
<echo message="Assembling JBoss ${bean.name} module." />
<delete dir="${assemble-client}"/> <mkdir dir="${assemble-client}"/>
<jar manifest="${client.src}/MANIFEST.MF"
destfile="${assemble-client}/${client.name}.jar"> <!-- classes -- >
<fileset dir="${build.client.classesdir}">
<include name="**/*.class" />
</fileset>
</jar>
</target>
<target name="ejbdeploy" depends="ejbjar">
<echo message="Deploying ${bean.name} package to JBoss." />
<copy file="${assemble}/${bean.name}.jar"
todir="${jboss.server.home}/deploy" />
</target>
<target name="clientdeploy" depends="clientjar, ejbjar">
<delete dir="${client.deploy.dir}"/>
<mkdir dir="${client.deploy.dir}"/>
<echo message="Deploying ${client.name} package"/>
<copy file="${assemble-client}/${client.name}.jar"
todir="${client.deploy.dir}" />
<copy file="${assemble}/${bean.name}.jar"
todir="${client.deploy.dir}"/>
<copy todir="${client.deploy.dir}">
<fileset dir="${client.lib}"/>
</copy>
</target>
<target name="all" depends="clientdeploy, ejbdeploy">
<echo message="Building project CorrectTime..."/>
</target>
</project>
Скрипт build.xml под управлением ANT сначала компилирует файлы компонента и клиента в два разных каталога. После этого, используя XML -дескриптор и файлы MANIFEST.MF,создает два JAR -архива - один архив компонента, а другой - архив клиента. Архив клиента он помещает в каталог развертывания клиента client-deploy (то есть тот каталог, в котором размещается JAR-архив клиентского
приложения вместе с библиотеками и архивом компонента). Архив компонента помещается в каталог C:\ejb\jboss\server\default\deploy. На этом развертывание компонентов и клиента можно считать завершенным.
(рис 3.14) Запуск АNT-скриптаЗапустить скрипт из Eclipse-WTP на исполнение, можно, открыв файл build.xml, а затем щелкнув правой кнопкой на имени проекта справа и выборе пункта Run As->Ant Build (Рис. 3.14).
(рис 3.15) Результат работы АNT-скриптаЕсли все прошло успешно, то в нижней части экрана должно появиться соответствующее сообщение (Рис. 3.15).
Теперь можно запустить контейнер приложений JBoss.Стоит написать bat -файл для запуска JBoss,в котором будут выставляться значения переменных PATH и JAVAHOME,соответствующие JDK 1.4 или JDK 1.5.Это следует сделать из-за того,
что БД Oracle также устанавливает свою версию JDK и прописывает все пути по умолчанию к своей версии JDK. А с этой версией JDK JBoss работать не будет.
set PATH=C:\Program Files\Java\jdk1.5.0_02\bin\ set JAVA_HOME=C:\Program Files\Java\jdk1.5.0_02 run
Запускаем JBoss посредством bat-файла из каталога bin.
(рис 3.16) Компонент развернутЕсли развертывание прошло успешно, то в конце загрузки появится название компонента и его -имя (Рис. 3.16).
Теперь запускаем клиентское приложение. Для него тоже стоит создать bat -файл для запуска.
set PATH=C:\Program Files\Java\jdk1.5.0_02\bin\ set JAVA_HOME=C:\Program Files\Java\jdk1.5.0_02 java -jar CorrectTimeClient.jar
(рис 3.17) Точное времяЕсли все было сделано правильно, но на экране появится точное время, полученное от компоненты с контейнера JBoss (Рис. 3.17).
В предыдущем разделе был рассмотрено создание проекта и его развертывание только при помощи средства ANT. Однако при помощи Eclipse-WTP создание EJB -компонент можно сильно упростить (напомним, что WTP расшифровывается как Web Tools Platform).Рассмотрим, как это сделать.
Вначале создаем специализированный проект для EJB компонента. Выбираем File -> New -> New Project -> EJB -> EJB Project.
(рис 3.18) Создание EJB-проектаНазовем проект CorrectTimeBean (названия EJB -проектов должны обязательно оканчиваться на суффикс Bean).Одной из полезных возможностей Eclipse-WTP является возможность
непосредственного подключения EJB -контейнера к проекту. Сделать это можно, щелкнув на кнопку New рядом с параметром Target Runtime (Рис. 3.18).
(рис 3.19) Путь к EJB-контейнеруДалее в следующем меню выбираем используемый EJB -контейнер - JBoss 4.0 (Рис. 3.19). Теперь необходимо задать путь к EJB -контейнеру и JDK,используемый для запуска контейнера. После создания проекта будет предложено выбрать "J2EE-перспективу" (J2EE perspective ) - то есть режим раскладки окон на экране, при помощи которых удобно работать с EJB -контейнерами и компонентами. Выбираем J2EE -перспективу.
После того, как проект был создан, в корне проекта уже появился каталог ejbModule,а в нем каталог META-INF и пустой файл ejb-jar.xml для XML -дескриптора.
Переносим исходные файлы с классами компонента из проекта CorrectTime в каталог ejbModule,а XML -дескриптор в файл ejb-jar.xml.
(рис 3.20) Дерево проектовДерево проектов будет выглядеть примерно так, как на Рис. 3.20.
Теперь для того, чтобы запустить EJB -контейнер с созданным компонентом, следует выбрать вкладку Servers в нижней части экрана, щелкнуть правой кнопкой мыши и выбрать New Server.
(рис 3.21) Добавление EJB-контейнера в проектВыбираем среди Server runtime JBoss 4.0 (он там пока всего один - других мы еще не установили) и щелкаем Finish (Рис. 3.21).
(рис 3.22) Добавление проекта к ЕJВ-контейнеруТеперь осталось добавить наш проект к только что сконфигурированному контейнеру. Щелкаем правой кнопкой на строчке с именем контейнера (JBoss 4.0) и выбираем пункт Add and Remove Projects. После чего добавляем наш проект к серверу (Рис. 3.22). Щелкнув правой кнопкой на строчке контейнера JBoss,а затем, выбрав вкладку Start,можно запустить
контейнер JBoss,при этом созданный компонент из проекта будет при необходимости скомпилирован, а затем автоматически развернут.
(рис 3.23) Компонент развернут на контейнере JBossТеперь создадим отдельный проект для клиента. Выбираем пункт меню File -> New -> Project -> Java Project. Называем проект CorrectTimeBeanClient.При создании проекта Eclipse предложит перейти в перспективу с раскладкой окон для обычной, не Enterprise, Java.Это делать не обязательно. Добавляем в проект две внешних библиотеки - jbossall-client.jar и jboss-j2ee.jar.
В корне проекта создаем каталог для исходных кодов и копируем туда пакет с классом клиента из проекта CorrectTime.Осталось только подключить к этому проекту классы удаленных интерфейсов из проекта CorrectTimeBean.
(рис 3.24) Добавление проектаДля этого выбираем в проекте CorrectTimeBeanClient в главном меню Project->Properties->Java Build Path->Projects. Щелкаем Add и добавляем проект CorrectTimeBean (Рис. 3.24).
Теперь запускаем контейнер JBoss:щелкаем правой кнопкой мыши на "вкладке" Servers->JBoss и выбираем пункт меню Start. В результате компонент будет развернут. После этого щелкаем правой кнопкой на классе CorrectTimeClient,и выбираем Run As->Java Application.
(рис 3.25) Удаленное и локальное времяЕсли все прошло удачно, то будет выведено время, получено от компонента и локальное время (Рис. 3.25).
В данном примере мы покажем разработку
В удаленном интерфейсе (Remote Interface) определим два метода - dollarToEuro (double) и dollarToYena (double). Эти методы, получив в качестве параметра сумму в долларах, переведут эту сумму по некоторому постоянному курсу и вернут сумму в евро или йенах соответственно.
public interface CurrencyRemote extends EJBObject
{
public double dollarToEuro(double dollars) throws RemoteException; public double dollarToYena(double yena) throws RemoteException;
}
Домашний интерфейс (Home Interface) состоит из одного метода.
public interface CurrencyHome extends EJBHome
{
public CurrencyRemote create() throws RemoteException, CreateException;
}
В классе компонента заданы для простоты два постоянных курса перевода валют (задание этих констант, а также основных используемых извне методов выделены шрифтом).
public class CurrencyBean implements SessionBean
{
private static final long serialVersionUID = 8897567469393327031L;
private double dollarToYenaRate = 100.25;
private double dollarToEuroRate =0.83;
public void ejbCreate()
{
}
public double dollarToEuro(double dollars)
{
return dollars * dollarToEuroRate;
}
public double dollarToYena(double dollars)
{
return dollars * dollarToYenaRate;
}
public void ejbActivate() throws EJBException, RemoteException
{
}
public void ejbPassivate() throws EJBException, RemoteException
{
}
public void ejbRemove() throws EJBException, RemoteException
{
}
public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException
{
}
}
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar id="ejb-jar ID" version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd"> <description> Second session bean </description> <display-name>CurrencyConvertorBean</display-name> <enterprise-beans> <session> <ejb-name>CurrencyBean</ejb-name> <home>currencyConvertorBean.CurrencyHome</home> <remote>currencyConvertorBean.CurrencyRemote</remote> <ejb-class>currencyConvertorBean.CurrencyBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> </ejb-jar>
В этом примере не будет рассматриваться создание простого Java -проекта для компонента, ограничимся EJB -проектом. Создаем EJB -проект CurrencyConvertorBean.Также в каталоге ejbModule создаем интерфейсы и классы, вставляем
(рис 3.26) Теперь их дваЕсли все было сделано правильно - то при запуске JBoss появится сообщение об успешном развертывании двух компонентов (компонента из предыдущего примера и текущего компонента) (Рис. 3.26).
Теперь напишем небольшое приложение, которое будет работать с компонентом CurrencyConvertorBean.Это будет графическое приложение, в котором пользователь сможет ввести наименования валют, и сумму денег в одной валюте. После этого приложение выведет сумму денег в другой валюте.
Вначале создадим Java -проект и назовем его CurrencyConvertorBeanClient.Аналогичным образом, как было описано в предыдущем примере, подключим необходимые библиотеки и выберем в качестве
public class CurrencyConvertorClient extends JFrame implements ActionListener
{
private static final long serialVersionUID = 3889223123915102442L;
private Context jndiContext;
private JLabel cur1Label;
private JTextField cur1;
private JLabel cur2Label;
private JTextField cur2;
private JLabel amountLabel;
private JTextField amount;
private JButton getRate;
private JLabel rate;
public CurrencyConvertorClient()
{
super("CurrencyConvertor");
layoutPane();
try
{
jndiContext = createJBossContext();
}
catch (NamingException e)
{
e.printStackTrace();
}
}
// Получение удаленного интерфейса
private CurrencyRemote getRemoteInterface() throws NamingException, CreateException, RemoteException
{
Object ref = jndiContext.lookup("CurrencyBean");
CurrencyHome home = (CurrencyHome)
PortableRemoteObject.narrow(ref, CurrencyHome.class);
CurrencyRemote remote = home.create();
return remote;
}
private void layoutPane()
{
GridBagConstraints gc = new GridBagConstraints();
gc.insets = new Insets(5, 5, 5, 5);
Container content = getContentPane();
content.setLayout(new GridBagLayout());
cur1Label = new JLabel("Currency 1: ");
gc.fill = GridBagConstraints.BOTH;
gc.weightx = 1.0;
gc.gridx = 0;
gc.gridy = 0;
gc.gridwidth = 2;
gc.gridheight = 1;
content.add(cur1Label, gc);
cur2Label = new JLabel("Currency 2: ");
gc.gridy = 1;
content.add(cur2Label, gc);
amountLabel = new JLabel("Amount: ");
gc.gridy = 2;
content.add(amountLabel, gc);
getRate = new JButton("Get value: ");
gc.gridy = 3;
content.add(getRate, gc);
getRate.addActionListener(this);
cur1 = new JTextField();
gc.gridx = 2;
gc.gridy = 0;
content.add(cur1, gc);
cur2 = new JTextField();
gc.gridy = 1;
content.add(cur2, gc);
amount = new JTextField();
gc.gridy = 2;
content.add(amount, gc);
rate = new JLabel("Not loaded");
gc.gridy = 3;
content.add(rate, gc);
}
public void actionPerformed(ActionEvent ae)
{
loadExchangeRate();
}
private void loadExchangeRate()
{
String currency1 = cur1.getText();
String currency2 = cur2.getText();
double am;
try
{
am = Double.parseDouble(amount.getText());
}
catch (NumberFormatException e)
{
rate.setText("Invalid number");
return;
}
try
{
CurrencyRemote remote = null;
remote = getRemoteInterface();
boolean loaded = true;
// Пока поддерживается только перевод из долларов в евро
// и долларов в йены
loaded = currency1.equals("USD");
loaded = (currency2.equals("EUR") || currency2.equals("YEN"));
if (loaded)
{
double res = 0;
if (currency2.equals("EUR"))
{
res = remote.dollarToEuro(am);
}
else if (currency2.equals("YEN"))
{
res = remote.dollarToYena(am);
}
NumberFormat nf = NumberFormat.getInstance();
nf.setMaximumFractionDigits(2);
rate.setText(nf.format(res));
}
else
{
rate.setText("Rate not found");
}
}
catch (RemoteException e)
{
e.printStackTrace();
rate.setText("Error...");
}
catch (NamingException e)
{
e.printStackTrace();
rate.setText("Error...");
}
catch (CreateException e)
{
e.printStackTrace();
rate.setText("Error...");
}
}
public static void main(String args[])
{
CurrencyConvertorClient client = new CurrencyConvertorClient();
client.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
client.setLocation(400, 400);
client.setSize(300, 200);
client.setVisible(true);
}
private static Context createJBossContext() throws NamingException
{
Properties p = new Properties();
p.put("java.naming.factory.initial",
"org.jnp.interfaces.NamingContextFactory");
p.put("java.naming.provider.url", "jnp://127.0.0.1:1099");
p.put("java.naming.factory.url.pkgs","org.jboss.naming:org.jnp.interfaces");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
}
(рис 3.27) Перевод долларов в евроВведем в качестве первой валюты доллар, в качестве второй - евро, а в качестве суммы - 100 долларов. Щелкнем кнопку Get value:, и появится сумма уже в евро - 83 евро.
(рис 3.28) Курс перевода не найденЕсли же в качестве валют для перевода указывается что-либо отличное от пар USD -> EUR или USD -> YEN, то тогда выводится сообщение Rate not found (Рис. 3.28).
Разработаем компонент с функциональностью, похожей на функциональность компонента из предыдущего примера. Но теперь компонент будет брать информацию о курсах валют не из констант классов (как это было в предыдущем примере), а из базы данных. В качестве базы данных используется Oracle 9i.
В удаленном интерфейсе (Remote Interface) определим главный метод, реализующий требуемую функциональность:
double convert(String curl, String cur2, double amount) throws RemoteException, CurrencyRateNotFoundException
Этот метод, получив на вход сумму в одной валюте, описываемой строкой cur1, переведет ее в сумму в другой валюте, описываемой строкой cur2. Если одна или обе из валют не найдены, то генерируется исключение CurrencyRateNotFoundException.
public interface DBCurrencyRemote extends EJBObject
{
public double convert(String cur1, String cur2, double amount) throws RemoteException, CurrencyRateNotFoundException;
}
Домашний интерфейс (Home Interface) по сути дела ничем не отличается от домашних интерфейсов (Home Interface) компонентов из предыдущих примеров.
public interface DBCurrencyHome extends EJBHome
{
public DBCurrencyRemote create() throws RemoteException, CreateException;
}
(рис 3.29) Мифологическая модель базы данныхТеперь необходимо создать таблицу курсов валют в базе данных, в которой будут храниться строки следующего вида: CUR1, CUR2, RATE. Строка уникальным образом определяется упорядоченной парой идентификаторов типов валют: ( CUR1, CUR2 ), то есть для двух заданных валют CUR1 и CUR2 может быть задан только один курс перевода RATE валюты CUR1 в валюту CUR2 (паре ( CUR2, CUR1 )
естественно, соответствует другой курс). Инфологическая модель этой простой базы данных представлена на Рис. 3.29. Создадим такую таблицу, и введем значения курсов для некоторых пар валют. Ниже приводится SQL-код,который создает необходимую таблицу и заполняет ее информацией.
create table CURRENCY_EXCHANGE_RATE
(
CUR1 varchar(30), CUR2 varchar(30), RATE float,
primary key (CUR1, CUR2)
);
insert into CURRENCY_EXCHANGE_RATE
(CUR1, CUR2, RATE) values ('EUR', 'USD', 1.3);
insert into CURRENCY_EXCHANGE_RATE
(CUR1, CUR2, RATE) values ('USD', 'EUR', 0.8);
insert into CURRENCY_EXCHANGE_RATE
(CUR1, CUR2, RATE) values ('EUR', 'RUR', 34.4);
insert into CURRENCY_EXCHANGE_RATE
(CUR1, CUR2, RATE) values ('USD', 'RUR', 26.5);
insert into CURRENCY_EXCHANGE_RATE
(CUR1, CUR2, RATE) values ('RUR', 'EUR', 0.33);
insert into CURRENCY_EXCHANGE_RATE
(CUR1, CUR2, RATE) values ('RUR', 'USD', 0.45);
Если таблица с таким названием уже существует в базе данных, то перед выполнением приведенного выше скрипта необходимо выполнить следующую SQL -команду:
drop table CURRENCY_EXCHANGE_RATE
(рис 3.30. ) Вход в SQL PlusТеперь необходимо выполнить этот скрипт в базе данных. Открываем приложение SQL*Plus,устанавливаемое вместе с компонентом Oracle Client СУБД Oracle 9i.Оно находится в меню "Пуск" в закладке Oracle - OraClient 9 -> Application Development -> SQL*Plus. Запускаем его. SQL*Plus предложит нам ввести имя пользователя, пароль и строку для связи с базой данных.
(рис 3.31) Консоль SQL PlusЕсли вход был выполнен удачно, то будет выведена консоль SQL*Plus,при помощи которой можно выполнять различные команды языка SQL (Рис. 3.31).
(рис 3.32) Необходимая таблица создана и заполнена даннымиТеперь можно выполнить приведенный выше SQL -код в консоли. В результате будет создана и заполнена данными таблица CURRENCY_EXCHANGE_RATE (Рис. 3.32).
Доступ к базе данных будет осуществляться посредством объекта DBQuery (файл DBQuery.java ). В нем будет реализовано два основных метода:
ResultSet selectQuery(String query). Этот метод позволяет выполнить некоторую выборку из базы данных. В качестве параметра передается SQL -запрос. К примеру - если необходимо из некоторой гипотетической базы данных выбрать информацию обо всех пользователей с именем "John", то при помощи этого метода запрос мог бы быть выполнен следующим образом: ResultSet set = query.selectQuery(" select * from USERS where NAME='John'") ;ResultSet selectQueryParam(String query, Object[] params). Этот метод также позволяет выполнить некоторую выборку из базы данных. В качестве первого параметра передается шаблон SQL -запроса, в котором некоторые параметры заменены символом '?'. В качестве второго параметра передается массив объектов (в качестве объектов обычно выступают строки, целые, а также вещественные числа). При вызове метода объект DBQuery подставит значения из массива объектов
вместо знаков '?' в запросе. На место первого вхождения знака '?' будет подставлено значение первого объекта, и т.д. Количество знаков '?' в первом параметре должно равняться количеству объектов из массива во втором параметре.Ниже приводится исходный код класса DBQuery.
package db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class DBQuery
{
private Connection connection = null;
private Statement statement = null;
public DBQuery(String driver, String url,
String username, String password) throws ClassNotFoundException, SQLException
{
Class.forName(driver);
connection = DriverManager.getConnection(url, username, password);
}
public ResultSet selectQuery(String sql) throws SQLException
{
if (connection == null)
{
return null;
}
ResultSet r = null;
statement = connection.createStatement();
r = statement.executeQuery(sql);
return r;
}
public ResultSet selectQueryParam(String sql, Object[] params) throws SQLException
{
ResultSet r = null;
PreparedStatement pst = connection.prepareStatement(sql);
statement = pst;
pst.clearParameters();
for (int i = 0; i < params.length; ш++)
{
pst.setObject(i + 1, params[i]);
}
r = pst.executeQuery();
return r;
}
public void close() throws SQLException
{
if (statement != null)
{
statement.close();
statement = null;
}
}
public void finalize()
{
try
{
close();
}
catch (SQLException e)
{
}
}
}
Помимо этого был создан класс-утилита, который несколько упрощает подключение к базам данных. При помощи его методов можно получать строку подключения к разным типам баз данных, а также получать имя класса драйвера базы данных. На данный момент реализация выполнена только для базы данных Oracle 9i.
package db;
public class DBDriverUtilities
{
public static final int ORACLE = 1;
/**
*Creates the URL for the ORACLE database
*@param host
*@param dbName
*@return
*/
public static String makeURL(String host, String dbName, int dbType)
{
if (dbType == ORACLE)
{
return "jdbc:oracle:thin:@" + host + ":1521:" + dbName;
}
else
{
return null;
}
}
public static String getDriver(int dbType)
{
if (dbType == ORACLE)
{
return "oracle.jdbc.OracleDriver";
}
else
{
return null;
}
}
}
Для того, чтобы можно было подключаться к базе данных Oracle 9i необходимо подключить библиотеку с соответствующим драйвером от Oracle.Она называется ojdbc14.jar и находится в каталоге jdbc\lib установочного каталога Oracle.Эту библиотеку нужно скопировать в каталог server\default\lib установочного каталога JBoss.Это необходимо для того,
чтобы компонент, размещенный на сервере смог использовать данную библиотеку для связи с базой данных. Подключать библиотеку к проекту в Eclipse-WTP не обязательно, хотя может оказаться полезной следующая практика - создать небольшой Java -проект, подключить к нему библиотеку доступа к базе данных и отладить все запросы в этом небольшом проекте, так как отладка непосредственно компонента на сервере - более длительный по времени процесс.
В классе компонента задан один метод, который производит выборку необходимого курса валют из базы данных. Если какая-либо из валют не найдена в базе данных, то генерируется исключение - CurrencyRateNotFoundException, которое в дальнейшем может быть обработано на стороне клиента. Выборка в базе данных производится при помощи уже реализованного класса DBQuery.
public class DBCurrencyBean implements SessionBean
{
private static final long serialVersionUID = 1858369246980240402L;
private String username = "boris";
private String password = "quasimodo";
private String host = "localhost";
private String dbName = "EJBDB";
public void ejbCreate()
{
}
public double convert(String cur1, String cur2, double amount)
throws CurrencyRateNotFoundException
{
DBQuery query = createDatabaseQuery();
double rate = 0;
try
{
ResultSet set =query.selectQueryParam
("SELECT RATE FROM " + "CURRENCY_EXCHANGE_RATE "
+ "WHERE CUR1=? AND CUR2=?", new Object[] {cur1, cur2});
if (set.next())
{
rate = set.getDouble(1);
}
else
{
throw new CurrencyRateNotFoundException(cur1, cur2);
}
query.close();
}
catch (SQLException e)
{
return -1;
}
return rate * amount;
}
private DBQuery createDatabaseQuery()
{
DBQuery query = null;
int db = DBDriverUtilities.ORACLE;
try
{
query = new DBQuery(DBDriverUtilities.getDriver(db),
DBDriverUtilities.makeURL(host, dbName, db), username, password);
}
catch (SQLException e)
{
e.printStackTrace();
}
catch (ClassNotFoundException e)
{
System.err.println("Class library not found!!!");
}
return query;
}
public void ejbActivate() throws EJBException, RemoteException
{
}
public void ejbPassivate() throws EJBException, RemoteException
{
}
public void ejbRemove() throws EJBException, RemoteException
{
}
public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException
{
}
}
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar id="ejb-jar ID" version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd"> <description> Third session bean </description> <display-name>DBCurrencyBean</display-name> <enterprise-beans> <session> <ejb-name>DBCurrencyBean</ejb-name> <home>dbCurrencyConvertorBean.DBCurrencyHome</home> <remote>dbCurrencyConvertorBean.DBCurrencyRemote</remote> <ejb-class> dbCurrencyConvertorBean.DBCurrencyBean </ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> </ejb-jar>
Клиентское приложение работает аналогично клиентскому приложению из предыдущего примера, за исключением того, что обрабатывается исключение CurrencyRateNotFoundException, и в этом случае выводится сообщение Rate not found.
public class DBCurrencyConvertorClient extends JFrame implements ActionListener
{
private static final long serialVersionUID = -7439969561347013270L;
private Context jndiContext;
private JLabel cur1Label;
private JTextField cur1;
private JLabel cur2Label;
private JTextField cur2;
private JLabel amountLabel;
private JTextField amount;
private JButton getRate;
private JLabel rate;
public DBCurrencyConvertorClient()
{
super("CurrencyConvertor");
layoutPane();
try
{
jndiContext = createJBossContext();
}
catch (NamingException e)
{
e.printStackTrace();
}
}
private DBCurrencyRemote getRemoteInterface() throws NamingException,
CreateException, RemoteException
{
Object ref = jndiContext.lookup("DBCurrencyBean");
DBCurrencyHome home = (DBCurrencyHome)
PortableRemoteObject.narrow(ref, DBCurrencyHome.class);
DBCurrencyRemote remote = home.create();
return remote;
}
private void layoutPane()
{
GridBagConstraints gc = new GridBagConstraints();
gc.insets = new Insets(5, 5, 5, 5);
Container content = getContentPane();
content.setLayout(new GridBagLayout());
cur1Label = new JLabel("Currency 1: ");
gc.fill = GridBagConstraints.BOTH;
gc.weightx = 1.0;
gc.gridx = 0;
gc.gridy = 0;
gc.gridwidth = 2;
gc.gridheight = 1;
content.add(cur1Label, gc);
cur2Label = new JLabel("Currency 2: ");
gc.gridy = 1;
content.add(cur2Label, gc);
amountLabel = new JLabel("Amount: ");
gc.gridy = 2;
content.add(amountLabel, gc);
getRate = new JButton("Get value: ");
gc.gridy = 3;
content.add(getRate, gc);
getRate.addActionListener(this);
cur1 = new JTextField();
gc.gridx = 2;
gc.gridy = 0;
content.add(cur1, gc);
cur2 = new JTextField();
gc.gridy = 1;
content.add(cur2, gc);
amount = new JTextField();
gc.gridy = 2;
content.add(amount, gc);
rate = new JLabel("Not loaded");
gc.gridy = 3;
content.add(rate, gc);
}
public void actionPerformed(ActionEvent ae)
{
loadExchangeRate();
}
private void loadExchangeRate()
{
String currency1 = cur1.getText();
String currency2 = cur2.getText();
double am;
try
{
am = Double.parseDouble(amount.getText());
}
catch (NumberFormatException e)
{
rate.setText("Invalid number"); return;
}
try
{
DBCurrencyRemote remote = getRemoteInterface();
double res = 0;
res = remote.convert(currency1, currency2, am);
NumberFormat nf = NumberFormat.getInstance();
nf.setMaximumFractionDigits(2);
rate.setText(nf.format(res));
}
catch (CurrencyRateNotFoundException e)
{
rate.setText("Rate not found");
}
catch (RemoteException e)
{
e.printStackTrace();
rate.setText("Error...");
}
catch (NamingException e)
{
e.printStackTrace();
rate.setText("Error...");
}
catch (CreateException e)
{
e.printStackTrace();
rate.setText("Error...");
}
}
public static void main(String args[])
{
DBCurrencyConvertorClient client = new
DBCurrencyConvertorClient();
client.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
client.setLocation(400, 400);
client.setSize(300, 200);
client.setVisible(true);
}
private static Context createJBossContext() throws NamingException
{
Properties p = new Properties();
p.put("java.naming.factory.initial",
"org.jnp.interfaces.NamingContextFactory");
p.put("java.naming.provider.url", "jnp://127.0.0.1:1099");
p.put("java.naming.factory.url.pkgs",
"org.jboss.naming:org.jnp.interfaces");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
}
Два проекта - DBCurrencyConvertorBean и DBCurrencyConvertorBeanClient создаются аналогично проектам из предыдущего примера. Добавляем проект с компонентой к JBoss и запускаем JBoss.
(рис 3.33) Компонент развернут на сервере
(рис 3.34) Получение информации о курсах валютИнтерфейс клиентского приложения аналогичен интерфейсу из предыдущего примера (Рис. 3.34).
(рис 35) Курс не найденПри возникновении CurrencyRateNotFoundException, оно обрабатывается и выводится сообщение Rate not found.
Предыдущий пример можно несколько упростить при помощи встроенных возможностей JBoss.Сервер JBoss поддерживает встроенный пул соединений к базам данных. Эти соединения можно получать из компонента EJB при помощи JNDI.Для того чтобы сконфигурировать подключение к новой базе данных можно для начала посмотреть файлов конфигураций для различных
баз данных. Они находятся в каталоге docs/examples/ установочного каталога JBoss.Нас интересует файл oracle-ds.xml. Здесь уже представлен вариант этого файла, в который вставлены значения, соответствующие подключению к нашей базе данных.
Сделать копию проекта в Eclipse можно щелкнув правой кнопкой мыши на проекте слева и выбрав в меню пункт Copy.
<?xml version="1.0" encoding="UTF-8"?> <datasources> <local-tx-datasource> <!-JNDI имя, по которому мы будет получать подключение--> <jndi-name>OracleDS</jndi-name> <!-Строка подключения к БД--> <connection-url> jdbc:oracle:thin:@localhost:1521:ejbdb </connection-url> <!-Класс драйвера БД--> <driver-class>oracle.jdbc.OracleDriver</driver-class> <!-Имя пользователя в БД и пароль--> <user-name>boris</user-name> <password>quasimodo</password> <exception-sorter-class-name> org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter </exception-sorter-class-name> <!-Название описания соответствий типов--> <metadata> <type-mapping>Oracle9i</type-mapping> </metadata> </local-tx-datasource> </datasources>
Далее этот файл необходимо скопировать в каталог server\default\deploy установочного каталога JBoss.
Для того, чтобы использовать новый способ подключения к базам данных требуется изменить только класс компонента. В нем необходимо переопределить метод setSessionContext(SessionContext arg) следующим образом:
public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException
{
try
{
InitialContext ctx = new InitialContext();
if (dataSource == null)
// Получаем объект DataSource из JNDI контекста
dataSource = (DataSource) ctx.lookup("java:/OracleDS");
System.out.println("Data source aquired");
}
catch (NamingException ex)
{
throw new EJBException(ex);
}
}
Теперь метод convert будет выглядеть так:
public double convert(String cur1, String cur2, double amount)
throws CurrencyRateNotFoundUpgException
{
if (dataSource == null)
{
return -1;
}
try
{
// Получаем подключение к базе данных из пула соединений
Connection conn = dataSource.getConnection();
// Готовим SQL-команду к выполнению
PreparedStatement p = conn.prepareStatement
("SELECT RATE FROM CURRENCY_EXCHANGE_RATE " + "WHERE CUR1NAME=? AND CUR2NAME=?");
p.setString(1, cur1);
p.setString(2, cur2);
ResultSet r = p.executeQuery();
// Обрабатываем полученный результат
if (r.next())
{
double rate = r.getDouble(1);
return rate * amount;
}
else
{
throw new
CurrencyRateNotFoundUpgException(cur1, cur2);
}
}
catch (SQLException e)
{
e.printStackTrace();
}
return -1;
}
Размещаем компонент на сервере JBoss и запускаем клиентское приложение.
(рис 3.36) Получение информации о курсах валютКак видно на Рис. 3.36,система из клиентского приложения и компоненты EJB работает так же, как и раньше.
Теперь будет рассмотрен несколько более сложный пример, чем предыдущие примеры. В нем будет продемонстрирована работа такого компонента, имеющегося у большинства интернет магазинов как корзина с покупками. Пользователь, зайдя на страницу нашего магазина сможет залогиниться, введя свое имя, просмотреть список доступных товаров, добавить необходимые ему товары в корзину, сделать заказ, пересчитать количество отдельных товаров в заказе, а также просмотреть свои предыдущие заказы. Торговать интернет магазине будет сигаретами.
(рис 3.37) Инфологическая модель базы данных Список товаров, сделанные заказы и их состав будут храниться в базе данных, инфологическая модель которой представлена на Рис. 3.37. Эта база данных состоит из трех основных таблиц:
COMMODITIESLIST, в которой хранится информация о всех доступных товарах в магазине. Поле NAME - название товара, DESCRIPTION - его описание, PRICE - цена в долларах США, а необязательное поле IMAGEPATH - идентификатор изображения, соответствующее товару (используется при отображении списка товаров в интернете).ORDERS, в которой хранится информация о заказе. DATE_CREATED - дата, когда заказ был сделан и USERNAME - имя пользователя, сделавшего заказ.COMMODITYORDER, которая описывает товары, из которых состоит заказ. ORDERID - идентификатор заказа, COMMODITYID -идентификатор товара, QUANTITY - количество экземпляров этого товара в заказе. ORDERID И COMMODITYID связаны с таблицами COMMODITIES_LIST и ORDERS по внешнему ключу.Для того, чтобы создать таблицы в базе данных Oracle 9i,необходимо выполнить следующий SQL-код.Помимо создания таблиц в базе данных, данный SQL-код заполняет таблицы некоторым набором значений по умолчанию. Для каждой таблицы определен триггер, автоматически генерирующий поле ID для каждой добавленной строки таблицы. Вначале создается пакет ORDER_ID_PKG,
в котором определена переменная, хранящая целое значение, и функция ее возвращающая. Триггер, генерирующий поле ID для таблицы ORDERS, сохраняет новое сгенерированное значение в поле LAST_ID пакета ORDER_ID_PKG. Это сделано для того, чтобы получить ID последнего добавленного заказа и использовать его при добавлении строк в таблицу COMMODITY_ORDER.
create or replace package ORDER_ID_PKG as LAST_ID integer;
function GET_LAST_ID return integer;
end ORDER_ID_PKG;
/
create or replace package body ORDER_ID_PKG as function GET_LAST_ID return integer is
begin
return LAST_ID; end GET_LAST_ID;
end ORDER_ID_PKG;
/
create table COMMODITIES_LIST
(
ID integer not null,
NAME varchar(20) not null,
DESCRIPTION varchar(50) not null,
PRICE float not null,
IMAGEPATH varchar(30), primary key(ID));
create sequence COMMODITIES_SEQ start with 1 increment by 1 nomaxvalue;
create trigger COMMODITIES_TRIGGER
before insert on COMMODITIES_LIST
for each row
begin
select COMMODITIES_SEQ.nextval into :new.id from dual;
end;
/
insert into COMMODITIES_LIST(NAME, DESCRIPTION, PRICE, IMAGEPATH)
values('Kent 1', 'Normal cigarettes', 1.99, 'img/kent1.jpg');
insert into COMMODITIES_LIST(NAME, DESCRIPTION, PRICE, IMAGEPATH)
values('Kent 4', 'Light cigarettes', 1.99, 'img/kent4.jpg');
insert into COMMODITIES_LIST(NAME, DESCRIPTION, PRICE, IMAGEPATH)
values('Kent 8', 'Very light cigarettes', 1.99, 'img/kent8.jpg');
insert into COMMODITIES_LIST(NAME, DESCRIPTION, PRICE, IMAGEPATH)
values('Lucky Strike', 'Pure American Taste', 2.30, 'img/lucky.jpg');
insert into COMMODITIES_LIST(NAME, DESCRIPTION, PRICE, IMAGEPATH)
values('Portugas', 'Fine Cuban Cigar', 10.95, 'img/portugas.jpg'); select * from COMMODITIES_LIST;
/
create table ORDERS
(
ID integer not null,
DATE_CREATED DATE not null,
USERNAME varchar(20) not null, primary key(ID)
);
create sequence ORDERS_SEQ start with 1 increment by 1 nomaxvalue;
create trigger ORDERS_TRIGGER before insert on ORDERS for each row
begin
select ORDERS_SEQ.nextval into :new.id from dual;
end;
/
create table COMMODITY_ORDER
(
ID integer not null, ORDER_ID integer not null, COMMODITY_ID integer not null, QUANTITY integer not null, primary key(ID)
);
alter table COMMODITY_ORDER add constraint FK_ORDER foreign key (ORDER_ID) references ORDERS(ID);
alter table COMMODITY_ORDER add constraint FK_COMMODITIES foreign key(COMMODITY_ID) references COMMODITIES_LIST(ID);
create sequence COMMODITY_ORDER_SEQ start with 1 increment by 1 nomaxvalue;
create trigger COMMODITY_ORDER_TRIGGER
before insert on COMMODITY_ORDER
for each row
begin
select COMMODITY_ORDER_SEQ.nextval into :new.id from dual;
end;
/
Если таблицы уже созданы в базе данных, то необходимо предварительно выполнить следующий SQL-код,который удалит эти таблицы и последовательности при помощи которых генерировалось автоматически значение основного ключа строки в таблице.
drop table COMMODITY_ORDER; drop table ORDERS; drop table COMMODITIES_LIST; drop sequence COMMODITY_ORDER_SEQ; drop sequence COMMODITIES_SEQ; drop sequence ORDERS_SEQ;
Стоит сделать несколько замечаний - для таблицы COMMODITYORDER выполняется операция drop в самом начале кода. Это сделано из-за того, что таблица COMMODITYORDER связана внешним ключом с двумя другими таблицами. БД Oracle не даст нам выполнить операцию drop для двух других таблиц, пока связь посредством внешнего ключа с таблицей COMMODITYORDER.
Для того чтобы соотнести объекты из базы данных с Java -классами был создан класс абстрактный класс DataItem. В нем есть поле id, которое соответствует ID объекта в базе данных (как было описано выше, у любого объекта в этой базе данных есть свой ID ). Класс находится в пакете dataObjects.
public abstract class DataItem implements Serializable
{
protected int id = -1;
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
}
От класса DataItem наследуются три других класса - Commodity, CommodityOrder и Order.Commodity соответствует товару из таблицы COMMODITY_LIST, CommodityOrder - товару из заказа в таблице COMMODITY_ORDERS, Order - заказу из таблицы ORDERS.
Исходный код класса Commodity представлен далее:
public class Commodity extends DataItem
{
private static final long serialVersionUID = -7004067843847157531L;
private String name;
private String description;
private String imagePath;
private double price;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getDescription()
{
return description;
}
public void setDescription(String description)
{
this.description = description;
}
public double getPrice()
{
return price;
}
public void setPrice(double price)
{
this.price = price;
}
public String getImagePath()
{
return imagePath;
}
public void setImagePath(String imagePath)
{
this.imagePath = imagePath;
}
public String toString()
{
return "ID=" + id + "; NAME=" + name + "; DESCRIPTION" + description + "; PRICE=" + price + "; IMAGEPATH=" + imagePath;
}
}
Исходный код класса CommodityOrder представлен далее:
public class CommodityOrder extends DataItem
{
private static final long serialVersionUID = -592875988844838475L;
private int commodityId;
private int commodityQuantity;
public int getCommodityId()
{
return commodityId;
}
public void setCommodityId(int commodityId)
{
this.commodityId = commodityId;
}
public int getCommodityQuantity()
{
return commodityQuantity;
}
public void setCommodityQuantity(int commodityQuantity)
{
this.commodityQuantity = commodityQuantity;
}
}
Исходный код класса Order представлен далее:
public class Order extends DataItem
{
private static final long serialVersionUID = -3276613990865210869L;
private String name; private Date date;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Date getDate()
{
return date;
}
public void setDate(Date date)
{
this.date = date;
}
}
Для реализации заявленной функциональности будут использоваться три
CommoditiesListBean используется для поиска товара в базе данных по ID товара, а также для получения всех товаров в базе данных.
OrdersListBean используется для составления списка сделанных заказов по имени пользователя, а также для получения списка товаров, входящих в заказ, описываемый при помощи ID.
ShoppingBasketBean используется для реализации логики корзины в интернет магазине. При помощи него можно добавлять товары в корзину, пересчитывать количество товаров и делать заказ.
В удаленном интерфейсе (Remote Interface) определим два метода:
public List getAvailableCommodities()throws RemoteException, SQLException. Этот метод возвращает список из объектов класса Commodity, которые соответствуют записям в таблице COMMODITIES_LIST.public Commodity getCommodityById(int id) throws RemoteException, SQLException. Этот метод по id возвращает объект класса Commodity, соответствующий товару в базе данных.public interface CommoditiesListRemote extends EJBObject
{
public List getAvailableCommodities() throws RemoteException,
SQLException;
public Commodity getCommodityById(int id) throws RemoteException, SQLException;
}
Этот интерфейс аналогичен домашним интерфейсам из предыдущих примеров.
public interface CommoditiesListHome extends EJBHome
{
public CommoditiesListRemote create() throws RemoteException, CreateException;
}
В классе компонента заданы два основных метода - ListgetAvailableCommodities() и Commodity getCommodityById(int id). В первом методе выполняется SELECT запрос в базе данных, после чего на основе сгенерированного ResultSet создается список из объектов класса Commodity. Выполнение запроса к базе данных, а также формирование объектов Commodity выделено шрифтом.
public class CommoditiesListBean implements SessionBean
{
private static final long serialVersionUID = -8641939595462379799L;
private DataSource dataSource = null;
public List getAvailableCommodities() throws SQLException
{
if (dataSource == null)
throw new SQLException("Datasource not loaded");
Connection conn = dataSource.getConnection();
PreparedStatement p = conn.prepareStatement("SELECT * FROM " + " COMMODITIES_LIST");
ResultSet r = p.executeQuery();
List arrayList = new ArrayList();
while (r.next())
{
Commodity commodity = new Commodity();
commodity.setId(r.getInt("ID"));
commodity.setName(r.getString("NAME"));
commodity.setDescription(r.getString("DESCRIPTION"));
commodity.setPrice(r.getDouble("PRICE"));
commodity.setImagePath(r.getString("IMAGEPATH"));
arrayList.add(commodity);
}
conn.close();
return arrayList;
}
/*
Во втором методе выполняется поиск товара по его ID. Если такой товар найден, то возвращается
созданный по ResultSet объект Commodity.
*/
public Commodity getCommodityById(int id) throws SQLException
{
if (dataSource == null)
throw new SQLException("Datasource not loaded");
Connection conn = dataSource.getConnection();
PreparedStatement p = conn.prepareStatement
("SELECT * FROM " + " COMMODITIES_LIST WHERE ID=?");
p.setInt(1, id);
ResultSet r = p.executeQuery();
Commodity commodity = new Commodity();
if (r.next())
{
commodity.setId(r.getInt("ID"));
commodity.setName(r.getString("NAME"));
commodity.setDescription(r.getString("DESCRIPTION"));
commodity.setPrice(r.getDouble("PRICE"));
commodity.setImagePath(r.getString("IMAGEPATH"));
}
conn.close(); return commodity;
}
public void ejbCreate()
{
}
public void ejbActivate() throws EJBException, RemoteException
{
}
public void ejbPassivate() throws EJBException, RemoteException
{
}
public void ejbRemove() throws EJBException, RemoteException
{
}
public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException
{
try
{
InitialContext ctx = new InitialContext();
if (dataSource == null)
dataSource = (DataSource) ctx.lookup("java:/OracleDS");
}
catch (NamingException ex)
{
throw new EJBException(ex);
}
}
}
Для того чтобы проверить работоспособность компонента CommoditiesListBean, было написано тестовое консольное клиентское приложение. В нем выполнялся вызов обеих методов компонента, а результат выводился в консоль. Код этого клиента приводится далее.
public class TestClient
{
public static void main(String args[])
{
try
{
Context jndiContext = createJBossContext();
Object ref = jndiContext.lookup("CommoditiesListBean");
CommoditiesListHome home =
(CommoditiesListHome) PortableRemoteObject.narrow(ref, CommoditiesListHome.class);
CommoditiesListRemote remote = home.create();
System.out.println("There are following items available " +" in stock:");
List list = remote.getAvailableCommodities();
Iterator iter = list.iterator();
while (iter.hasNext())
{
Commodity commodity = (Commodity) iter.next();
System.out.println(commodity.toString());
}
Commodity commodity = remote.getCommodityById(2);
System.out.println("Single commotidy - " + commodity.toString());
}
catch (RemoteException e)
{
e.printStackTrace();
}
catch (NamingException e)
{
e.printStackTrace();
}
catch (CreateException e)
{
e.printStackTrace();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
public static Context createJBossContext() throws NamingException
{
Properties p = new Properties();
p.put("java.naming.factory.initial",
"org.jnp.interfaces.NamingContextFactory");
p.put("java.naming.provider.url", "jnp://127.0.0.1:1099");
p.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
}
Для тестового клиента был создан отдельный Java -проект в Eclipse.
(рис 3.38) Консольный вывод тестового клиентаВывод тестового клиента показан на Рис. 3.38.
В удаленном интерфейсе определим два метода:
List getOrdersForUser(String username) throws RemoteException, SQLException. Этот метод возвращает список из объектов Order для пользователя с именем username.List getCommodityOrdersForOrder(int orderId) throws RemoteException, SQLException. Этот метод возвращает список из объектов CommodityOrder для заказа с ID = orderId.public interface OrdersListRemote extends EJBObject
{
public List getOrdersForUser(String username) throws RemoteException, SQLException;
public List getCommodityOrdersForOrder(int orderId) throws RemoteException, SQLException;
}
Этот интерфейс аналогичен домашним интерфейсам из предыдущих примеров.
public interface OrdersListHome extends EJBHome
{
public OrdersListRemote create() throws RemoteException, CreateException;
}
Выполнение запроса к базе данных и формирование объектов Order и CommodityOrder выделено шрифтом.
public class OrdersListBean implements SessionBean
{
private static final long serialVersionUID = 2767604097827994556L;
private DataSource dataSource = null;
public List getOrdersForUser(String username) throws SQLException
{
if (dataSource == null)
throw new SQLException("Datasource not loaded");
Connection conn = dataSource.getConnection();
PreparedStatement p = conn.prepareStatement
("SELECT * FROM " + " ORDERS WHERE USERNAME=?");
p.setString(1, username);
ResultSet r = p.executeQuery();
List arrayList = new ArrayList();
while(r.next())
{
Order order = new Order();
order.setId(r.getInt("ID"));
order.setName(r.getString("USERNAME"));
order.setDate(r.getDate("DATE_CREATED"));
arrayList.add(order);
}
conn.close();
return arrayList;
}
public List getCommodityOrdersForOrder(int orderId) throws SQLException
{
if (dataSource == null)
throw new SQLException("Datasource not loaded");
Connection conn = dataSource.getConnection();
PreparedStatement p = conn.prepareStatement
("SELECT * FROM " + " COMMODITY_ORDER WHERE ORDER_ID=?");
p.setInt(1, orderId);
ResultSet r = p.executeQuery();
List arrayList = new ArrayList();
while(r.next())
{
CommodityOrder order = new CommodityOrder();
order.setId(r.getInt("ID"));
order.setCommodityId(r.getInt("COMMODITY_ID"));
order.setCommodityQuantity(r.getInt("QUANTITY"));
arrayList.add(order);
}
conn.close();
return arrayList;
}
public void ejbCreate() {}
public void ejbActivate() throws EJBException, RemoteException {}
public void ejbPassivate() throws EJBException, RemoteException {}
public void ejbRemove() throws EJBException, RemoteException {}
public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException
{
try
{
InitialContext ctx = new InitialContext();
if (dataSource == null)
dataSource = (DataSource) ctx.lookup("java:/OracleDS");
System.out.println("Data source aquired");
}
catch (NamingException ex)
{
throw new EJBException(ex);
}
}
}
Для того чтобы проверить работоспособность компонента OrdersListBean, было написано тестовое консольное клиентское приложение. В нем выполнялся вызов обеих методов компонента, а результат выводился в консоль. Код этого клиента приводится далее.
public class TestClient3
{
public static void main(String args[])
{
try
{
Context jndiContext = createJBossContext();
Object ref = jndiContext.lookup("OrdersListBean");
OrdersListHome home = (OrdersListHome) PortableRemoteObject.narrow(ref, OrdersListHome.class);
OrdersListRemote remote = home.create();
List orders = remote.getOrdersForUser("Boris");
System.out.println("Orders for user Boris");
for (Iterator iter = orders.iterator(); iter.hasNext();)
{
Order order = (Order) iter.next();
List commodityList =remote.
getCommodityOrdersForOrder(order.getId());
System.out.println("Commodities for order " +
order.getId() + " " + order.getDate().toString());
for (Iterator iter2 = commodityList.iterator(); iter2.hasNext();)
{
CommodityOrder cOrder = (CommodityOrder) iter2.next();
System.out.println(cOrder.getCommodityId() + " " + cOrder.getCommodityQuantity());
}
}
}
catch (RemoteException e)
{
e.printStackTrace();
}
catch (NamingException e)
{
e.printStackTrace();
}
catch (CreateException e)
{
e.printStackTrace();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
public static Context createJBossContext() throws NamingException
{
Properties p = new Properties();
p.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
p.put("java.naming.provider.url", "jnp://127.0.0.1:1099");
p.put("java.naming.factory.url.pkgs",
"org.jboss.naming:org.jnp.interfaces");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
}
(рис 3.39) Консольный вывод тестового клиентаВывод тестового клиента показан на Рис. 3.39.
Интерфейс этого
public List getCurrentCommoditiesList() throws RemoteException. Этот метод возвращает список из объектов CommodityOrder, соответствующих товарам в корзине.public boolean isEmpty() throws RemoteException. Этот метод возвращает булевское значение true, если корзина пуста.public addCommodity(int commodityId) throws RemoteException. Этот метод добавляет одну единицу товара с ID = commodityId в корзину.public recalculateCommodity(int commodityId, int newCount) throws RemoteException. Этот метод изменяет количество товаров с ID = commodityId в корзине на newCount.public deleteCommodity(int commodityId) throws RemoteException. Этот метод удаляет товар из корзины.public void processOrder() throws RemoteException, SQLException. Этот метод создает новый объект в таблице ORDERS базы данных, а затем добавляет записи с ORDERID, соответствущему только что добавленному заказу, в таблицу COMMODITYORDERS.Как вы уже, наверное, успели убедиться, вызов каждого из этих методов может менять состояние компонента ShoppingBasketBean.
public interface ShoppingBasketRemote extends EJBObject
{
public List getCurrentCommoditiesList() throws RemoteException;
public boolean isEmpty() throws RemoteException;
public void addCommodity(int commodityId) throws RemoteException;
public void recalculateCommodity(int commodityId, int newCount)
throws RemoteException;
public void deleteCommodity(int commodityId) throws RemoteException;
public void processOrder() throws RemoteException, SQLException;
}
Этот интерфейс отличается от домашних интерфейсов предыдущих компонентов. Компонент ShoppingBasketBean создается отдельно для каждого пользователя, поэтому удобно в него передать имя пользователя. Имя пользователя будет использоваться при выполнении запроса в методе processOrder (). В метод create(String username) домашнего интерфейса передается имя пользователя.
public interface ShoppingBasketHome extends EJBHome
{
public ShoppingBasketRemote create(String username) throws RemoteException, CreateException;
}
Компонент ShoppingBasketBean должен постоянно поддерживать текущий список товаров. В классе компонента это делается посредством списка из объектов класса CommodityOrder. Все операции по добавлению, пересчету, и удалению товаров из списка текущих товаров в корзине сводятся к операциям поиска, удаления и добавления соответствующего объекта CommodityOrder в этом списке.
Определен дополнительный вспомогательный метод private CommodityOrder findOrderById(int commodityId). Он ищет соответствующий объект в списке и возвращает либо его, либо значение null, если он найден не был. Код метода, осуществляющий поиск по commodityId представлен далее.
Iterator iter = commodityOrderList.iterator();
CommodityOrder order = null;
while (iter.hasNext())
{
CommodityOrder currentOrder = (CommodityOrder) iter.next();
if (currentOrder.getCommodityId() == commodityId)
{
order = currentOrder;
break;
}
}
return order;
Так как теперь компонент при создании должен получать имя пользователя, метод public void ejbCreate(String username) также несколько изменился. Теперь он сохраняет переданное имя пользователя во внутреннее поле String username, а также создает список, в котором в дальнейшем будет храниться текущий список товаров в корзине. Код этого метода представлен далее.
this.username = username; commodityOrderList = new ArrayList();
В методе public void processOrder() throws SQLException выполняется создание заказа в базе данных по товарам, которые есть в корзине на данный момент. Если в корзине нет товаром, то этот метод не делает ничего. В противном случае он сначала создает объект в таблице ORDERS посредством следующего запроса:
PreparedStatement p = conn.prepareStatement("insert into " "ORDERS(DATE_CREATED, USERNAME) values (?, ?)");
p.setDate(1,new java.sql.Date(System.currentTimeMillis()));
p.setString(2,username);
p.executeUpdate();
Теперь необходимо получить ID этого объекта. В Интернете много рекомендаций на тему того, что это необходимо делать путем выборки максимального ID из таблицы в базе данных.
select max(ID) from ORDERS where USERNAME = 'BORIS'
Не используйте этот подход! Во-первых, он будет работать только тогда, когда генерируемый индекс получается путем увеличения предыдущего, а во-вторых, если один пользователь попробует оформить два заказа одновременно, то может быть выбран ID другого оформляемого в этот момент заказа, и список товаров далее будет добавлен не к тому заказу.
Поэтому в базе данных был создан пакет, в который посредством триггера сохранялось значение поля ID последнего добавленного объекта в таблицу ORDERS. Стоит отметить, что пакеты в СУБД Oracle сохраняют свое состояние для каждой сессии (от создания подключения до его закрытия), поэтому можно не опасаться, что в пакете окажется ID другого заказа.
p = conn.prepareStatement("select ORDER_ID_PKG.GET_LAST_ID from dual");
ResultSet r = p.executeQuery();
int orderId = -1;
if (r.next())
{
orderId = r.getInt(1);
}
То есть выбирается максимальное значение ID для текущего пользователя. Учитывая, что в нашем магазине не может быть создано двух корзин одновременно для одного пользователя, такое решение будет корректно работать.
Затем для каждого товара в корзине добавляется по записи в таблице COMMODITYORDER.
while (iter.hasNext())
{
CommodityOrder currentOrder = (CommodityOrder) iter.next();
p = conn.prepareStatement("insert into COMMODITY_ORDER
(ORDER_ID, " + "COMMODITY_ID, QUANTITY) values (?, ?, ?)");
p.setInt(1, orderId);
p.setInt(2, currentOrder.getCommodityId());
p.setInt(3, currentOrder.getCommodityQuantity());
p.execute();
}
Метод void deleteCommdity(int commodityId) сводится к вызову метода recalculateCommodity(commodityId, 0).
Метод void addCommodity(int commodityId) вначале проверяет, есть ли товар с ID=commodityId в корзине, и либо добавляет новый объект CommodityOrder в список, либо просто увеличивает количество экземпляров в корзине товара на один.
CommodityOrder order = findOrderById(commodityId);
if (order == null)
{
order = new CommodityOrder();
order.setCommodityId(commodityId);
order.setCommodityQuantity(1);
commodityOrderList.add(order);
}
else
{
order.setCommodityQuantity(order.getCommodityQuantity() + 1);
}
Метод void recalculateCommodity(int commodityId,int newCount) изменяет количество экземпляров товара c ID=commodityId на newCount. Если newCount = 0, то товар удаляется из списка. Если товара не было в списке, то он туда добавляется.
CommodityOrder order = findOrderById(commodityId);
if (order == null)
{
if (newCount > 0)
{
order = new CommodityOrder();
order.setCommodityId(commodityId);
order.setCommodityQuantity(1);
commodityOrderList.add(order);
}
}
else
{
if (newCount > 0)
{
order.setCommodityQuantity(newCount);
}
else
{
commodityOrderList.remove(order);
}
}
Код класса компонента ShoppingBasketBean приводится далее.
public class ShoppingBasketBean implements SessionBean
{
private static final long serialVersionUID = 8173942031447022589L;
private String username = null;
private DataSource dataSource = null;
private List commodityOrderList;
public void ejbCreate(String username)
{
this.username = username;
commodityOrderList = new ArrayList();
}
private CommodityOrder findOrderById(int commodityId)
{
Iterator iter = commodityOrderList.iterator();
CommodityOrder order = null;
while (iter.hasNext())
{
CommodityOrder currentOrder = (CommodityOrder) iter.next();
if (currentOrder.getCommodityId() == commodityId)
{
order = currentOrder; break;
}
}
return order;
}
public List getCurrentCommoditiesList()
{
return commodityOrderList;
}
public boolean isEmpty()
{
return (commodityOrderList.size() == 0);
}
public void addCommodity(int commodityId)
{
CommodityOrder order = findOrderById(commodityId);
if (order == null)
{
// Creating new order
order = new CommodityOrder();
order.setCommodityId(commodityId);
order.setCommodityQuantity(1);
commodityOrderList.add(order);
}
else
{
order.setCommodityQuantity(order.getCommodityQuantity() + 1);
}
}
public void recalculateCommodity(int commodityId, int newCount)
{
CommodityOrder order = findOrderById(commodityId);
if (order == null)
{
if (newCount > 0)
{
// Creating new order
order = new CommodityOrder();
order.setCommodityId(commodityId);
order.setCommodityQuantity(1);
commodityOrderList.add(order);
}
}
else
{
if (newCount > 0)
{
order.setCommodityQuantity(newCount);
}
else
{
commodityOrderList.remove(order);
}
}
}
public void deleteCommodity(int commodityId)
{
recalculateCommodity(commodityId, 0);
}
public void processOrder() throws SQLException
{
if (dataSource == null)
throw new SQLException("Datasource not loaded");
Connection conn = dataSource.getConnection();
if (commodityOrderList.size() == 0)
return;
PreparedStatement p = conn.prepareStatement
("insert into " + "ORDERS(DATE_CREATED, USERNAME) " + "values (?, ?)");
p.setDate(1, new java.sql.Date(System.currentTimeMillis()));
p.setString(2, username);
p.executeUpdate();
p = conn.prepareStatement("select max(ID) from ORDERS where " + "USERNAME = ?");
p.setString(1, username);
ResultSet r = p.executeQuery();
int orderId = -1;
if (r.next())
{
orderId = r.getInt(1);
}
Iterator iter = commodityOrderList.iterator();
while (iter.hasNext())
{
CommodityOrder currentOrder = (CommodityOrder) iter.next();
p = conn.prepareStatement
("insert into COMMODITY_ORDER
(" + " ORDER_ID, COMMODITY_ID, QUANTITY) " + "values (?, ?, ?)");
p.setInt(1, orderId);
p.setInt(2, currentOrder.getCommodityId());
p.setInt(3, currentOrder.getCommodityQuantity());
p.execute();
}
commodityOrderList.clear(); conn.close();
}
public void ejbActivate() throws EJBException, RemoteException
{}
public void ejbPassivate() throws EJBException, RemoteException
{}
public void ejbRemove() throws EJBException, RemoteException
{}
public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException
{
try
{
InitialContext ctx = new InitialContext();
if (dataSource == null)
dataSource = (DataSource)
ctx.lookup("java:/OracleDS");
System.out.println("Data source aquired");
}
catch (NamingException ex)
{
throw new EJBException(ex);
}
}
}
Тестовый клиент для компонента ShoppingBasketBean создает один экземпляр компонента и делает тестовый заказ.
public class TestClient2
{
public static void main(String args[])
{
try
{
Context jndiContext = createJBossContext();
Object ref = jndiContext.lookup("ShoppingBasketBean");
ShoppingBasketHome home = (ShoppingBasketHome) PortableRemoteObject.narrow(ref, ShoppingBasketHome.class);
ShoppingBasketRemote remote = home.create("Boris");
System.out.println("Starting to work with the shop");
remote.addCommodity(2);
remote.addCommodity(3);
remote.addCommodity(4);
remote.processOrder();
}
catch (RemoteException e)
{
e.printStackTrace();
}
catch (NamingException e)
{
e.printStackTrace();
}
catch (CreateException e)
{
e.printStackTrace();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
public static Context createJBossContext() throws NamingException
{
Properties p = new Properties();
p.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
p.put("java.naming.provider.url", "jnp://127.0.0.1:1099");
p.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
}
В консоль данный тестовый клиент ничего не выводит, но можно посмотреть результат его работы, выполнив два запроса в базе данных в SQL*PLUS:
(рис 3.40) Новый заказ добавленРезультаты выполнения запросов показаны на Рис. 3.40. Как видно, в таблице ORDERS появилась информация о новом заказе, а в таблице COMMODITYORDERS - информация о составе заказа.
ejb-jar.xml. Дескрипторы session-type
будет стоять вместо .
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar id="ejb-jar ID" version="2.1" xmlns = "http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd"> <description>Beans for the Shopping Basket</description> <display-name>ShoppingBasketBean</display-name> <enterprise-beans> <session> <ejb-name>CommoditiesListBean</ejb-name> <home>shoppingBasket.CommoditiesListHome</home> <remote>shoppingBasket.CommoditiesListRemote</remote> <ejb-class>shoppingBasket.CommoditiesListBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> <session> <ejb-name>OrdersListBean</ejb-name> <home>shoppingBasket.OrdersListHome</home> <remote>shoppingBasket.OrdersListRemote</remote> <ejb-class>shoppingBasket.OrdersListBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> <session> <ejb-name>ShoppingBasketBean</ejb-name> <home>shoppingBasket.ShoppingBasketHome</home> <remote>shoppingBasket.ShoppingBasketRemote</remote> <ejb-class>shoppingBasket.ShoppingBasketBean</ejb-class> <session-type>Stateful</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> </ejb-jar>
В этом примере в качестве клиентского приложения будет выступать веб-приложение, написанное при помощи сервлетов и JSP.В данном пособии не ставиться цель научить с нуля программировать на сервлетах и JSP. Поэтому предполагается, что некоторые знания по этой теме у читателя уже есть.
Логика работы данного веб-приложения будет заключаться в следующем: пользователю отображается некоторая веб-страница, генерируемая при помощи JSP.После чего пользователь с этой веб-страницы может перейти на другую веб-страницу, либо послать данные на обработку некоторому сервлету (либо при помощи формы, либо просто посредством ссылки). Компоненты EJB хранятся в
контексте JSP -сессии (Session).Помимо этого в контексте сессии хранится имя пользователя, под которым он вошел в систему. Для любого веб-магазина характерны следующие страницы:
index.jsp.goodlist.jsp.basket.jsp.thankyou.jsp.prevorders.jsp.Помимо страниц, необходимы еще сервлеты, которые будут обрабатывать действия пользователя. Сервлеты необходимы для:
Помимо сервлетов, будет использоваться фильтр сервлетов. Если пользователь не вошел в магазин, этот фильтр будет перенаправлять его на страницу входа index.jsp, в случае если он вдруг попытается запросить какую-либо другую страницу или сервлет. Фильтр называется AuthFilter.
Рассмотрим страницу входа в магазин.
<%@ page language="java" contentType="text/html; charset=cp1251"
pageEncoding="cp1251"%>
<%String username = (String) session.getAttribute("username");
// Пользователь уже вошел в магазин - отправляем его на страницу
// со списком товаров
if (username != null) {
application.getRequestDispatcher("/goodlist.jsp").forward(request, response);
}
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=cp1251">
<title>Login</title>
</head>
<body>
// Данные отправляются сервлету LoginServlet. В URL-адресах ему поставлен
// в соответствие адрес, заканчивающийся на login.
// Стоит отметить, что мы не используем пароли для пользователей.
// Каждый волен входить под тем именем, которое ему нравится.
<form action="login" method="post"> <table>
<tr>
<td> Login:
</td> <td>
<input type="text" name="username"></input>
</td>
</tr> <tr>
<td>
<input type="SUBMIT" value="Login"></input> </td>
</tr> </table> </form>
</body>
</html>
(рис 3.41) Страница входа в системуСтраница входа в систему показана на Рис. 3.41.
Теперь рассмотрим сервлет входа в систему. Он обрабатывает только Р0SТ -запросы. Данный сервлет, получив данные формы, сохраняет имя пользователя в контексте сессии. Затем он получает удаленные интерфейсы трех ЕJB -компонентов и также сохраняет их в контексте сессии. Теперь они будут доступны на всех остальных страницах в магазине.
public class LoginServlet extends HttpServlet
{
private Context jndiContext = null;
private static final long serialVersionUID = 4562488849398632419L;
public LoginServlet()
{
super();
try
{
jndiContext = createJBossContext();
}
catch (NamingException e)
{
e.printStackTrace();
}
}
private static Context createJBossContext() throws NamingException
{
Properties p = new Properties();
p.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
p.put("java.naming.provider.url", "jnp://127.0.0.1:1099");
p.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
protected void doPost(HttpServletRequest req, HttpServletResponse res)
{
String loginName = req.getParameter("username");
HttpSession session = req.getSession();
// Если пользователь еще не вошел в магазин
if (session.getAttribute("username") == null)
{
// Сохраняем имя пользователя в контексте сессии
session.setAttribute("username", loginName);
Object ref;
try
{
// Получаем и сохраняем три удаленных интерфейса
// компонентов EJB в сессии.
ref = jndiContext.lookup("ShoppingBasketBean");
ShoppingBasketHome home = (ShoppingBasketHome) PortableRemoteObject.narrow
(ref, ShoppingBasketHome.class);
ShoppingBasketRemote remote = home.create(loginName);
session.setAttribute("basketbean", remote);
ref = jndiContext.lookup("CommoditiesListBean");
CommoditiesListHome homeCom = (CommoditiesListHome)
PortableRemoteObject.narrow(ref, CommoditiesListHome.class);
CommoditiesListRemote remoteCom = homeCom.create();
session.setAttribute("commoditiesbean", remoteCom);
ref = jndiContext.lookup("OrdersListBean");
OrdersListHome homeOrd = (OrdersListHome)
PortableRemoteObject.narrow(ref, OrdersListHome.class);
OrdersListRemote remoteOrd = homeOrd.create();
session.setAttribute("ordersbean", remoteOrd);
}
catch (NamingException e)
{
e.printStackTrace();
}
catch (CreateException e)
{
e.printStackTrace();
}
catch (RemoteException e)
{
e.printStackTrace();
}
}
// Отправляем пользователя на страницу с товарами
RequestDispatcher reqDispatcher = getServletContext().getRequestDispatcher("/goodlist.jsp");
try
{
reqDispatcher.forward(req, res);
}
catch (java.io.IOException e)
{
e.printStackTrace();
}
catch (javax.servlet.ServletException e)
{
e.printStackTrace();
}
}
}
Перейдем к странице с товарами. Здесь вся логика сводится к получению компонента CommoditiesListBean из контекста сессии, и отображения доступных товаров. Для каждого товара определена ссылка на сервлет добавления товара AddToCartServlet.В таблице COMMODITIESLIST у каждого объекта было определено такое поле как IMAGEPATH - в нем
хранится путь до изображения, которое соответствует товару. При помощи Google находим фотографии различных пачек сигарет и поместим их в каталог img.
<%@ page language="java" contentType="text/html;
charset=cp1251" pageEncoding="cp1251"%>
<%@page import="shoppingBasket.CommoditiesListRemote"%>
<%@page import="java.util.List"%>
<%@page import="java.util.Iterator"%>
<%@page import="dataObjects.Commodity"%>
<%@page import="java.text.NumberFormat"%>
<%@page import="java.util.Locale"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html> <head>
<meta http-equiv="Content-Type" content="text/html; charset=cp1251">
<title>Cigarette store</title>
</head>
<body>
<h1>Welcome to the finest internet cigarettes store!</h1>
<h2>Here is the list of commodities available for purchase</h2>
// Переход к корзине с товарами
<a href="basket.jsp">Goto Basket</a><br>
// Переход к предыдущим заказам
<a href="prevorders.jsp">View previous orders</a><br>
<table border="0" cellpadding="0" cellspacing="0">
<%
// Получаем удаленный интерфейс ComoditiesListBean
CommoditiesListRemote remote = (CommoditiesListRemote)
session.getAttribute("commoditiesbean");
List list = remote.getAvailableCommodities();
NumberFormat format = NumberFormat.getCurrencyInstance(Locale.US);
for (Iterator iter = list.iterator(); iter.hasNext();)
{ Commodity commodity = (Commodity) iter.next();
%>
<tr>
<td style="padding-left:10px; padding-right:10px;">
<%=commodity.getName()%>
</td>
<td style="padding-left:10px; padding-right:10px;">
<%=commodity.getDescription()%> </td>
<td style="padding-left:10px; padding-right:10px;">
<%=format.format(commodity.getPrice())%> </td>
// Ссылка на сервлет AddToServlet
<td style="padding-left:10px; padding-right:10px;"> <a
href="addtocart?good id=<%=commodity.getId()%>">Add to cart</a> </td>
<td style="padding-left:10px; padding-right:10px;">
<img src="<%=commodity.getImagePath()%>"/></td>
</tr>
<%
}
%>
</table>
// Переход к странице с корзиной для того, чтобы оформить заказ
<a href="basket.jsp">Make order</a><br>
// Выход из системы посредством LogoutServlet
<a href="logout">Logout</a>
</body> </html>
(рис 3.42) Доступные товарыСтраница с фотографиями товаров (сигарет) изображена на Рис. 3.42. Сервлет AddToCartServlet получает в качестве параметра ID товара, из контекста сессии берет удаленный интерфейс ShoppingListBean,а затем выполняет вызов addCommodity (goodId).
Стоит отметить, что ни сервлет, не компонент не проверяют факта существования товара с таким ID. Поэтому пользователь может "обмануть" магазин передав заведомо неправильный good_id в сервлет.
public class AddToCartServlet extends HttpServlet
{
private static final long serialVersionUID = -666156591413576322L;
public AddToCartServlet()
{
super();
}
protected void doPost(HttpServletRequest req, HttpServletResponse res)
{
doGet(req, res);
}
protected void doGet(HttpServletRequest req, HttpServletResponse res)
{
HttpSession session = req.getSession();
ShoppingBasketRemote remote = (ShoppingBasketRemote)
session.getAttribute("basketbean");
String strGoodId = req.getParameter("good id");
int goodId = 0;
try
{
goodId = Integer.parseInt(strGoodId);
}
catch (NumberFormatException e)
{
goodId = 0;
}
if (goodId > 0 remote != null)
{
try
{
remote.addCommodity(goodId);
}
catch (RemoteException e)
{
e.printStackTrace();
}
}
RequestDispatcher reqDispatcher = getServletContext().getRequestDispatcher("/goodlist.jsp");
try
{
reqDispatcher.forward(req, res);
}
catch (java.io.IOException e)
{
e.printStackTrace();
}
catch (javax.servlet.ServletException e)
{
e.printStackTrace();
}
}
}
Теперь рассмотрим страницу, в которой отображается содержимое корзины. На ней при помощи компонентов ShoppingBasketBean и CommoditiesListBean генерируется список из текущих товаров в корзине. Также снизу отображается суммарная стоимость товаров в заказе. Пользователь также может пересчитать количество каждого товара в корзине.
Либо удалить его совсем из корзины, введя "0" в соответствующее текстовое поле. Суммарная стоимость считается прямо на странице (что не очень хорошо, это будет сделано далее непосредственно в компоненте).
<%@ page language="java" contentType="text/html; charset=cp1251"
pageEncoding="cp1251"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%@page import="shoppingBasket.ShoppingBasketRemote"%>
<%@page import="java.util.List"%>
<%@page import="java.util.Iterator"%>
<%@page import="dataObjects.CommodityOrder"%>
<%@page import="shoppingBasket.CommoditiesListRemote"%>
<%@page import="dataObjects.Commodity"%>
<%@page import="java.text.NumberFormat"%>
<%@page import="java.util.Locale"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Shopping Basket</title>
</head>
<body>
<h1>Welcome to the finest internet cigarettes store!</h1>
<h2>This is the current state of the shopping basket</h2>
<a href="goodlist.jsp">Goto commodities list</a><br>
<a href="prevorders.jsp">View previous orders</a><br>
<table border="0" cellpadding="0" cellspacing="0">
<%
ShoppingBasketRemote remote =
(ShoppingBasketRemote) session.getAttribute("basketbean");
List commodities = remote.getCurrentCommoditiesList();
CommoditiesListRemote commRemote =
(CommoditiesListRemote) session.getAttribute("commoditiesbean");
NumberFormat format = NumberFormat.getCurrencyInstance(Locale.US);
double price = 0;
if (commodities.size() != 0)
{
for (Iterator iter = commodities.iterator(); iter.hasNext();)
{ // Getting the commodity order
CommodityOrder order = (CommodityOrder) iter.next();
Commodity commodity = commRemote.getCommodityById(order.getCommodityId());
// Подсчет суммарной стоимости заказа
price += commodity.getPrice() * order.getCommodityQuantity();
%><tr>
<!-Форма для запроса к сервлету, осуществляющему
пересчет товаров в корзине-->
<form action="recalculate" method="post">
<input type="hidden" name="good id"
value="<%=order.getCommodityId()%>">
</input> <td style="padding-left:10px; padding-right:10px;">
<%=commodity.getName()%></td>
<td style="padding-left:10px; padding-right:10px;">
<%=format.format(commodity.getPrice())%></td>
<td style="padding-left:10px; padding-right:10px;">
Amount: <input type="text" name="amount" value="
<%=order.getCommodityQuantity()%>"></input>
</td>
<td><input type="SUBMIT"
value="Recalculate"></input></td>
</form>
</tr>
<%
}
%>
<tr><td>The total price is <%=format.format(price)%>
</td></tr>
<%
} else {
%>
<!-Товаров в корзине нет ->
<tr><td><h3>No commodities in basket!</h3>
</td></tr>
<%
}
%>
</table>
<a href="makeorder">Make order</a><br>
<a href="logout">Logout</a><br>
</body>
</html>
(рис 3.43) Корзина с товарамиСтраница с корзиной изображена на Рис. 3.43.
RecalculateServlet вызывает метод recalculateCommodity(id, amount) с ID товара и новым количеством этого товара, после чего переходит обратно на страницу с корзиной. Опять таки, факт наличия товара с соответствующим ID нигде не проверяется.
public class RecalculateServlet extends HttpServlet
{
private static final long serialVersionUID = 4151274707994513136L;
public RecalculateServlet()
{
super();
}
public void doPost(HttpServletRequest req, HttpServletResponse res)
{
HttpSession session = req.getSession();
ShoppingBasketRemote remote =
(ShoppingBasketRemote) session.getAttribute("basketbean");
String strGoodId = req.getParameter("good id");
String strAmount = req.getParameter("amount");
int goodId = 0;
int amount = -1;
try
{
goodId = Integer.parseInt(strGoodId);
amount = Integer.parseInt(strAmount);
}
catch (NumberFormatException e)
{
goodId = 0;
amount = -1;
}
if (goodId > 0 remote != null amount >= 0)
{
try
{
remote.recalculateCommodity(goodId, amount);
}
catch (RemoteException e)
{
e.printStackTrace();
}
}
RequestDispatcher reqDispatcher = getServletContext().getRequestDispatcher("/basket.jsp");
try
{
reqDispatcher.forward(req, res);
}
catch (java.io.IOException e)
{
e.printStackTrace();
}
catch (javax.servlet.ServletException e)
{
e.printStackTrace();
}
}
}
MakeOrderServlet получаем удаленный интерфейс компонента ShoppingBasketBean и вызывает у него метод void processOrder(). После чего переходит на страницу с, где пользователя благодарят за покупки.
public class MakeOrderServlet extends HttpServlet
{
private static final long serialVersionUID = -171986817882942920L;
public MakeOrderServlet()
{
super();
}
public void doGet(HttpServletRequest req, HttpServletResponse res)
{
HttpSession session = req.getSession();
ShoppingBasketRemote remote = (ShoppingBasketRemote) session.getAttribute("basketbean");
if (remote != null)
{
try
{
remote.processOrder();
}
catch (RemoteException e)
{ e.printStackTrace();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
RequestDispatcher reqDispatcher =
getServletContext().getRequestDispatcher("/thankyou.jsp");
try
{
reqDispatcher.forward(req, res);
}
catch (java.io.IOException e)
{
e.printStackTrace();
}
catch (javax.servlet.ServletException e)
{
e.printStackTrace();
}
}
public void doPost(HttpServletRequest req, HttpServletResponse res)
{
doGet(req, res);
}
}
На странице благодарности за покупки расположении три ссылки:
<%@ page language="java" contentType="text/html; charset=cp1251" pageEncoding="cp1251"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=cp1251"> <title>Cigarette store</title> </head> <body> <h1>Thank you for making purchases in our store!</h1> <h2>From here you can: </h2> <ul> <!- Продолжить делать покупки --> <li>Continue shopping: <a href="goodlist.jsp">Goto shopping</a></li> <!- Просмотреть заказы --> <li>View your orders: <a href="prevorders.jsp">View orders</a></li> <!- Выход из магазина --> <li>Logout: <a href="logout">Logout</a></li> </ul> </body> </html>
(рис 3.44) Спасибо за покупкуСтраница с благодарностью за покупку изображена на Рис. 3.44."/>
Сервлет выхода их магазина убирает из контекста сессии все атрибуты (компоненты EJB и имя пользователя).
public class LogoutServlet extends HttpServlet
{
private static final long serialVersionUID = 4562488849398632419L;
public LogoutServlet()
{
super();
}
protected void doGet(HttpServletRequest req, HttpServletResponse res)
{
doPost(req, res);
}
protected void doPost(HttpServletRequest req, HttpServletResponse res)
{
HttpSession session = req.getSession();
session.removeAttribute("username");
session.removeAttribute("basketbean");
session.removeAttribute("commoditiesbean");
session.removeAttribute("ordersbean");
RequestDispatcher reqDispatcher =
getServletContext().getRequestDispatcher("/index.jsp");
try
{
reqDispatcher.forward(req, res);
}
catch (java.io.IOException e)
{
e.printStackTrace();
}
catch (javax.servlet.ServletException e)
{
e.printStackTrace();
}
}
}
На странице со всеми заказами, сделанными пользователем, используются две компоненты CommoditiesListBean и OrdersListBean.Посредством метода List getOrdersForUser (String username) компонента OrdersListBean сначала создается список из всех заказов пользователя,
после этого для каждого заказа посредством метода List getCommodityOrdersForOrder(int orderId) создается список из объектов CommodityOrder. Далее для каждого объекта CommodityOrder вызывается метод Commodity getCommodityById (int commodityId), и формируется описание товара в заказе. Стоимость заказа формируется способом, аналогичным способу на странице с корзиной.
<%@ page language="java" contentType="text/html; charset=cp1251"
pageEncoding="cp1251"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%@page import="java.util.List"%>
<%@page import="java.util.Iterator"%>
<%@page import="shoppingBasket.OrdersListRemote"%>
<%@page import="dataObjects.Order"%>
<%@page import="dataObjects.CommodityOrder"%>
<%@page import="shoppingBasket.CommoditiesListRemote"%>
<%@page import="dataObjects.Commodity"%>
<%@page import="java.text.NumberFormat"%>
<%@page import="java.util.Locale"%>
<tml> <head>
<meta http-equiv="Content-Type" content="text/html;
charset=ISO-8859-1">
<title>Previous Orders</title>
</head>
<body>
<h1>Welcome to the finest internet cigarettes store!</h1>
<h2>These are your orders</h2>
<a href="goodlist.jsp">Goto commodities list</a><br>
<a href="basket.jsp">Goto Basket</a><br>
<table border="0" cellpadding="0" cellspacing="0">
<%
OrdersListRemote remote = (OrdersListRemote) session.getAttribute("ordersbean");
String username = (String) session.getAttribute("username");
CommoditiesListRemote commRemote =
(CommoditiesListRemote) session.getAttribute("commoditiesbean");
NumberFormat format = NumberFormat.getCurrencyInstance(Locale.US);
// Получаем список заказов для пользователя
List orders = remote.getOrdersForUser(username);
if (orders.size() != 0)
{
for (Iterator iter = orders.iterator(); iter.hasNext(); )
{
// Getting the commodity order
Order order = (Order) iter.next();
// Для каждого заказа получаем список товаров в нем
List commodityOrders = remote.getCommodityOrdersForOrder(order.getId());
double price = 0;
%><tr>
<td>Order done on: <%=order.getDate()%></td>
</tr>
<tr>
<td style="padding-left: 20px;">
<ul>
<%for (Iterator iter2 = commodityOrders.iterator();
iter2.hasNext();)
{ CommodityOrder cOrder = (CommodityOrder) iter2.next();
// Получаем сам товар с описанием
Commodity commodity = commRemote.getCommodityById(cOrder.getCommodityId());
price += commodity.getPrice() * cOrder.getCommodityQuantity();
%>//Выводим информацию о товаре
<li>Commodity: <%=commodity.getName()%>, Price:
<%=format.format(commodity.getPrice()) %>, Quantity:
<%=cOrder.getCommodityQuantity()%> </li>
<%
}
%>
</ul> </td>
</tr> <tr>
<td style="padding-left: 10px;">Total price is:
<%=format.format(price)%>
</td>
</tr> <tr>
<td style="padding-top: 20px;"></td>
</tr>
<%
}
} else {
%>
<tr>
<td>
<h3>No orders!</h3>
</td>
</tr>
<%
}
%>
</table>
<a href="logout">Logout</a>
</body>
</html>
(рис 3.45) Страница с заказами Страница с заказами изображена на Рис. 3.45.
Фильтр в данном веб-приложении один - он осуществляет контроль над несанкционированным доступом к персональным страницам. Проще говоря, если пользователь не ввел свое имя, то этот фильтр на даст ему возможности открыть страницу basket.jsp, prevorder.jsp, thankyou.jsp, goodlist.jsp а также все сервлеты, кроме сервлета входа в магазин LoginServlet.
Делает он это просто проверяя наличие атрибута с именем пользователя в контексте сессии.
public class AuthFilter implements Filter
{
private FilterConfig fc;
public void init(FilterConfig filterConfig) throws ServletException
{
fc = filterConfig;
}
public void destroy()
{
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (req.getSession().getAttribute("username") == null)
{
// Имя пользователя не обнаружено
RequestDispatcher reqDispatcher = fc.getServletContext().
getRequestDispatcher("/index.jsp");
try
{
reqDispatcher.forward(req, res);
}
catch (java.io.IOException e)
{
e.printStackTrace();
}
catch (javax.servlet.ServletException e)
{
e.printStackTrace();
}
}
else
{
// Все в порядке, страница или сервлет доступен
chain.doFilter(req, res);
}
}
}
Файл конфигурации web.xml описывает все сервлеты и фильтры, используемые в веб-приложении, а также прописывает URL -адреса привязки сервлетов и фильтров. Помимо этого задаются названия файлов, открываемых по умолчанию ( index.html, index.jsp и т.д.).
<?xml version="1.0" encoding="UTF-8"?> <web-app id="WebApp ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2 0 01/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app 2 4.xsd"> <display-name> ShoppingBasketWebClient </display-name> <!- Список имен файлов, открывающихся по умолчанию --> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <servlet> <description>The servlet used to login</description> <display-name>LoginServlet</display-name> <servlet-name>LoginServlet</servlet-name> <servlet-class>webclient.servlets.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping> <servlet> <description>The servlet used to add items to cart</description> <display-name>AddToCartServlet</display-name> <servlet-name>AddToCartServlet</servlet-name> <servlet-class>webclient.servlets.AddToCartServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>AddToCartServlet</servlet-name> <url-pattern>/addtocart</url-pattern> </servlet-mapping> <servlet> <description> The servlet used to recalculate items in cart </description> <display-name>RecalculateServlet</display-name> <servlet-name>RecalculateServlet</servlet-name> <servlet-class> webclient.servlets.RecalculateServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>RecalculateServlet</servlet-name> <url-pattern>/recalculate</url-pattern> </servlet-mapping> <servlet> <description>The servlet used to make order</description> <display-name>MakeOrderServlet</display-name> <servlet-name>MakeOrderServlet</servlet-name> <servlet-class>webclient.servlets.MakeOrderServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>MakeOrderServlet</servlet-name> <url-pattern>/makeorder</url-pattern> </servlet-mapping> <servlet> <description>The servlet used to logout</description> <display-name>LogoutServlet</display-name> <servlet-name>LogoutServlet</servlet-name> <servlet-class>webclient.servlets.LogoutServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LogoutServlet</servlet-name> <url-pattern>/logout</url-pattern> </servlet-mapping> <filter> <filter-name>AuthFilter</filter-name> <filter-class>webclient.filter.AuthFilter</filter-class> </filter> <filter-mapping> <filter-name>AuthFilter</filter-name> <servlet-name>AddToCartServlet</servlet-name> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <servlet-name>LogoutServlet</servlet-name> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <servlet-name>MakeOrderServlet</servlet-name> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <servlet-name>RecalculateServlet</servlet-name> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/basket.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/goodlist.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/prevorders.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/thankyou.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/img/</url-pattern> </filter-mapping> </web-app>
Для компонентов EJB следует создать проект EJB,аналогичный проектам из предыдущих примеров. Назовем его ShoppingBasketBean.Для тестовых примеров создаются обычные Java -проекты, к ним еще подключаются библиотеки jbossall-client.jar и jboss-j2ee.jar.
(рис 3.46) Динамический веб-проектДля веб-приложения, следует выбрать тип проекта Web -> Dynamic Web Project (Рис. 3.46). Далее на следующем окне в качестве Target Runtime выбираем JBoss 4.0,в EAR (Enterprise Application Project) пока не добавляем (можно, конечно, было вначале создать EAR -проект -это ничего не меняет).
После этого будет создан проект для динамического веб-приложение - то что нам и нужно.
(рис 3.47) Структура веб-приложенияВ проекте будут созданы два основных каталога - src и WebContent. В src следует помещать все Java -классы, используемые в веб-приложении - сервлеты, фильтры и другие классы. Фильтр поместим в пакет webclient.filter, а сервлеты в пакет webclient. (Рис. 3.47). JSP страницы и
файл конфигурации веб-приложения web.xml помещаюся в каталог WebContent. Помимо этого в этом каталоге создадим каталог img, в котором поместим фотографии пачек сигарет, доступных к продаже в магазине. Страницы JSP помещаем в каталог WebContent, фотографии сигарет в каталог img, файл web.xml в каталог WEB-INF.
Теперь осталось создать EAR -проект - это такой тип проекта, в который можно подключить несколько различных проектов EJB,а также разнообразные клиентские проекты, после этого при развертке этого проекта, он также корректно развернет все проекты, которые к нему подключены (по крайней мере так предполагается). Создаем проект J2EE -> Enterprise Application Project. Опять в качестве Target Runtime выбираем JBoss 4.0.
(рис 3.48) Подключение проектов к EAR-проектуНа последнем этапе следует подключить в EAR -проекту два наших проекта - EJB и веб-проект (Рис. 3.48).
Теперь, когда все проекты готовы, следую подключить EAR -проект к JBoss ( Servers -> JBoss -> Add and Remove Projects ), причем входящие в него проекты подключать не надо, иначе будет ошибка.
Если все было сделано правильно, то при запуске JBoss оба проекта будут корректно развернуты на сервере JBoss, а веб-приложение будет доступно по адресу http : //localhost :8080/ ShoppingBasketWebClient (последняя часть URL соответствует имени проекта). Скриншоты веб-приложения уже приводились при обсуждении страниц JSP и здесь они приводиться не будут.
Последний пример уже был достаточно сложен. Возможность отладки приложения становится все нужнее. К счастью Eclipse- дает возможность как отладки компонентов, развернутых на сервере приложений, так и отладки JSP -страниц и сервлетов. Для этого надо при запуске сервера приложений (в нашем случае JBoss) запустить его не при помощи команды Run, а при помощи команды Debug.
(рис 3.49) Запуск сервера в режиме DebugЗапуск сервера в режиме Debug показан на Рис. 3.49. После этого можно выполнять отладку как JSP -страниц, так и компонентов.
(рис 50) Установка breakpoint'aBreakpoint как для JSP -страниц, так и для Java -классов, устанавливается посредством щелчка правой кнопкой мыши рядом со строчкой кода. В меню можно выбрать операцию с breakpoint - установить, удалить, сделать неактивным. Теперь перед выполнением этой строки кода будет произведена остановка.
Отлаживать лучше в Debug -перспективе - Window -> Open perspective -> Debug. В этой перспективе есть все возможности для отладки - Watch, Breakpoints, вычисление различных выражений и т.д. Для более подробного изучения процесса отладки в Eclipse следует обратиться к документации по нему.
У примера можно выделить несколько недостатков. Перечислим некоторые из них:
Commodity
CommodityListBean.getCommodityByld (int id)) - это крайне неэкономично;Commodity CommodityListBean.getCommodityByld(int id)) - тоже неэкономично;SQL -запросы выполняются непосредственно из кода Эти недостатки будут, по возможности, исправлены в примере из следующего раздела. В нем рассматриваются объектные компоненты, которые позволяют сильно упростить разработку
В данном разделе будут рассмотрены сеансовые (Session),объектные (Entity) и управляемые сообщениями (Message-driven) компоненты (Beans) EJB.Все примеры разработаны в среде Windows XP SP2.Несмотря на это, все используемые компоненты являются кросс-платформенными, так что примеры должны работать так же в Linux, Solaris или Mac OS X.Для демонстрации примеров необходимо следующее ПО:
EJB ) - открытый сервер приложений JBoss (http : //www .jboss. org/);Eclipse, в которую добавлена широкая поддержка веб-приложений и EJB. Скачать ее можно также на сайте Eclipse - http://www. eclipse. org/.web -приложений, взаимодействующих через EJB с базой данных будут использоваться веб-сервер Apache (http : //www . apache.org/), и контейнер JSP Tomcat (http://tomcat. apache. org/).Серверные компоненты Enterprise Java Beans бывают трех принципиально разных типов:
Сеансовые и объектные компоненты являются серверными компонентами, основанными на RMI,к которым можно получить доступ при помощи распределенных объектных протоколов. Компоненты, управляемые сообщениями, появившиеся в EJB 2.0 - это асинхронные серверные компоненты, которые отвечают на асинхронные сообщения JMS и других протоколов.
Хорошее эмпирическое правило при конструировании компонентов EJB состоит в том, что объектные компоненты моделируют прикладные понятия, которые могут быть выражены существительными. Например, объектный компонент может представлять покупателя, часть оборудования, элемент инвентаря или даже место. Другими словами, объектные компоненты моделируют объекты реального мира. Эти объекты являются обычными постоянными записями в какой-либо базе данных.
Действия, которые принимают
Главное различие компонентов в том, что объектные компоненты имеют устойчивое состояние, а
Для реализации объектного или
В данной главе будут сначала рассмотрены примеры
create () его домашнего интерфейса (Home Interface).EJB, которые могут использоваться другими компонентами, размещенными в том же контейнере EJB,но не внешними по отношению к контейнеру EJB приложениями. Он позволяет компонентам, находящимся в одном контейнере EJB,взаимодействовать
без дополнительных затрат, связанных с применением протокола распределенных объектов, что повышает их производительность. Локальный интерфейс расширяет (extends) интерфейс javax.ejb.EJBLocalObject.Внешние по отношению к EJB компоненты, находящиеся в том же самом контейнере, работают с методами локального интерфейса, а получают эти методы они при помощи локального
домашнего интерфейса (Local Home Interface)Важным отличием между удаленным и локальным интерфейсом является то, что вызовы методов локального интерфейса не используют RMI и не генерируют RemoteException.Для корректной работы не обязательно предоставлять классу компонента все четыре интерфейса. К примеру, если компонент не используется никаким клиентским приложением (а нужен только другим компонентам, работающим под управлением того же EJB контейнера), то нет необходимости предоставлять удаленный интерфейс. То есть в зависимости от тех задач, которые решает данный компонент, ему может быть предоставлен либо локальные, либо удаленные, либо и те и другие интерфейсы.
ejbCreate(), соответствующие некоторым методам в обоих, домашнем (Home Interface) и локальном домашнем (Local Home Interface),интерфейсах. Объектные компоненты должны реализовывать интерфейс javax.ejb.EntityBean,а onMessage (). Этот метод вызывается при прибытии нового сообщения.У компонентов, управляемых сообщениями нет никаких компонентных интерфейсов, но, несмотря на это, они могут быть клиентами любых сеансовых или объектных компонентов и взаимодействовать с этими компонентами через их интерфейсы. Объектные и
byte, boolean, char, int, long, short, double, float ;java .io.Serializable ;java.rmi.RemoteException.При попытке передать через удаленный метод объект, не относящийся к одному из трех типов, будет сгенерировано исключение java.rmi.RemoteException. Каждый метод в удаленном интерфейсе (Remote Interface) должен генерировать исключение java.rmi.Remote. Оно генерируется при проблемах с передачей данных в сети или невозможности найти требуемый сервер. Помимо этого разработчики могут
использовать любые другие исключения при разработке компонент EJB.
Протокол Java RMI IIOP также накладывает ограничения на удаленные интерфейсы. Этих ограничений много, но реально используется только два из этих ограничений.
Существует много инструментальных средств, которые позволяют сильно упростить работу с EJB.В частности одним из средств, которое также предоставляет платформа Eclipse-WTP,является средство создания связей между объектными компонентами и базой данных. В Eclipse-WTP это делается при помощи пакета XDoclet.Каким образом это делается, будет рассмотрено далее.
Class.newInstance () создается экземпляр класса компонента.SessionBean.setSessionContext( SessionContext context). При помощи этого EJBContext, предоставляющий интерфейс для взаимодействия с контейнером EJB (в частности, в этом объекте есть метод lookup(), позволяющий искать другие объекты по путям JNDI).ejbCreate (). У При запуске контейнер обычно создает несколько экземпляров ejbRemove(). В этом методе следует закрыть все открытые подключения к различным ресурсам.
То, каким образом и когда контейнер EJB должен создавать и удалять экземпляры EJB. В каждом из существующих контейнеров EJB этот механизм реализован по-разному.
create() у домашнего интерфейса (Home Interface).После этого созданный компонент переходит в состояние готовых методов. В этом состоянии он готов выполнять свои методы, вызываемые клиентом через удаленную ссылку. Компонент в это время может хранить открытые
ресурсы (базы данных, TCP/IP сессии и др.), а также хранить посредством своих переменных определенное состояние, которое может изменяться при вызове клиентом различных его методов. Последовательность методов, вызываемых контейнером при создании
При нахождении компонента в состоянии готовых методов, он может либо оставаться в этом состоянии, либо переведен в пассивное состояние (при этом будет вызван метод ejbPassivate () - см. далее), либо быть удаленным. То, при каких условиях компонент будет переведен в пассивное состояние, в каждом из существующих контейнеров определяется по-разному. При активизации компонента (вызывается метод ejbActivate () - см. далее),
компонент переводится из пассивного состояние в состояние готовых методом. При этом значение всех внутренних переменных и состояние диалога сохраняется.
Компонент может быть удален при вызове соответствующего метода remove() домашнего интерфейса, либо контейнером после определенного промежутка времени, в течение которого компонент "бездействовал". Компонент не может быть удален при выполнении транзакции. При удалении у компонента будет вызван метод ejbRemove().
Разработаем correctTimeBean.
Сначала определим удаленный интерфейс (Remote Interface) разрабатываемого компонента в файле CorrectTimeRemote.java. Все виды удаленных интерфейсов расширяют (extends) интерфейс javax.ejb.EJBObject.
package correctTimeBean;
import java.rmi.RemoteException;
import java.util.Date;
import javax.ejb.EJBObject;
public interface CorrectTimeRemote extends EJBObject
{
public Date obtainCorrectTime() throws RemoteException;
}
Здесь присутствует единственный метод obtainCorrectTime() - он возвращает текущее время сервера. Этот метод может генерировать исключение RemoteExcepton,требуемое для всех методов удаленных интерфейсов.
Теперь определим домашний интерфейс (Home Interface) в файле CorrectTimeHomejava.
package correctTimeBean;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface CorrectTimeHome extends EJBHome
{
public CorrectTimeRemote create() throws RemoteException, CreateException;
}
Метод create () отвечает за create() может быть перегружен (overloaded),то есть, определены методы create с возможно другим списком формальных параметров. Вызов метода create() возвращает удаленную ссылку компонента CorrectTimeBean.
Теперь рассмотрим класс, реализующий разрабатываемый компонент (файл CorrectTimeBean.java ).
package correctTimeBean;
import java.rmi.RemoteException;
import java.util.Date;
import javax.ejb.EJBException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
public class CorrectTimeBean implements SessionBean
{
public static final long serialVersionUID = 6066529880637710711L;
public void ejbCreate()
{
}
public Date obtainCorrectTime()
{ return new Date();
}
public void ejbActivate() throws EJBException, RemoteException
{
}
public void ejbPassivate() throws EJBException, RemoteException
{
}
public void ejbRemove() throws EJBException, RemoteException
{
}
public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException
{
}
}
В этом классе определен метод obtainCorrectTime(). Он возвращает текущее время на сервере. Помимо этого, в данном компоненте определены еще несколько методов.
void ejbCreate(). Компоненты EJB никогда не должны определять конструкторы. Необходимая инициализация проводится в методе ejbCreate (). Для ejbCreate() не вызывается.void ejbActivate() throws EJBException, RemoteException. Этот метод используется для void ejbPassivate() throws EJBException, RemoteException. Этот метод используется для void ejbRemove() throws EJBException, RemoteException. Этот метод для void setSessionContext(SessionContext arg0).Также определено поле public static final long serialVersionUID. Начиная с Java 1.5 во всех классах, которые имплементируют (implements) интерфейс java.io.Serializable, рекомендуется определить это поле с уникальным 64-битным ключом. В случае если это поле не будет определено, то Java -машине придется
генерировать его при serialVersionUID может быть сгенерировано при помощи встроенного в Eclipse-WTP механизма (см. далее), либо при помощи утилиты serialver
, входящей в любой Java Development Kit.Более подробно про механизм сериализации можно прочитать в Java Object Serialization Specification.
Теперь необходимо определить XML -XML -файлами. Для EJB 2.1
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar id="ejb-jar ID" version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2 0 01/XMLSchema-instance" xsi:schemaLocation= "http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar 2 1.xsd"> <description>First session bean</description> <display-name>CorrectTimeProject</display-name> <enterprise-beans> <session> <ejb-name>CorrectTimeBean</ejb-name> <home>correctTimeBean.CorrectTimeHome</home> <remote>correctTimeBean.CorrectTimeRemote</remote> <ejb-class>correctTimeBean.CorrectTimeBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> </ejb-jar>
В этом дескрипторе развертывания имеются следующие элементы:
<ejb-jar> - корневой элемент. В нем обязательно должен быть один элемент <enterprise-beans>, а также дополнительные элементы.<description> - словесное описание.<display-name> - этот элемент представляет собой идентификатор, который могут использовать некоторые программы, работающие с <enterprise-beans> - содержит объявления всех компонентов, описываемых этим <session> - в этом элементе описывается <ejb-name> - имя компонента.<home> - полное имя класса домашнего интерфейса.<remote> - полное имя класса удаленного интерфейса.<ejb-class> - полное имя класса компонента. Этот класс реализует прикладные методы компонента.<session-type> - тип Stateless - без состояния, Stateful - с состоянием.<transaction-type> - определяет, управляет ли EJB. Значение Container - управляет контейнер, значение Bean - управляет компонент.Теперь создадим клиентское приложение (файл CorrectTimeClient.java ), которое будет подключаться к контейнеру EJB,получать ссылку на удаленный интерфейс компонента, вызывать метод получения серверного времени, а также печатать это время вместе со временем на клиентском компьютере.
public class CorrectTimeClient
{
public static void main(String[] args)
{
try
{
Context jndiContext = createJBossContext();
Object ref = jndiContext.lookup("CorrectTimeBean");
CorrectTimeHome home = (CorrectTimeHome) PortableRemoteObject.narrow(ref, CorrectTimeHome.class);
CorrectTimeRemote remote = home.create();
Date localDate = new Date();
Date remoteDate = remote.obtainCorrectTime();
System.out.println("Remote time is " +remoteDate.toString()+
"\nCurrent time is " + localDate.toString());
}
catch (RemoteException e)
{
e.printStackTrace();
}
catch (NamingException e)
{
e.printStackTrace();
}
catch (CreateException e)
{
e.printStackTrace();
}
}
public static Context createJBossContext() throws NamingException
{
Properties p = new Properties();
p.put("java.naming.factory.initial",
"org.jnp.interfaces.NamingContextFactory");
p.put("java.naming.provider.url", "jnp://127.0.0.1:1099");
p.put("java.naming.factory.url.pkgs",
"org.jboss.naming:org.jnp.interfaces");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
}
Клиент, собирающийся обратиться к компоненту, начинает с использования пакета JNDI (Java Naming and Directory Interface) для получения соединения с контейнером компонента. JNDI - это независимый от реализации API для систем имен и каталогов. Каждый производитель EJB должен предоставить -совместимую службу каталогов.
При помощи JNDI клиент получает удаленные ссылки на компоненты, размешенные в контейнере EJB. Метод createJBossContext() содержит логику, основывающуюся на JNDI для получения сетевого соединения с сервером JBoss.Для разных контейнеров EJB этот код будет разным, например, для контейнера WebSphere код выглядит иначе:
public static Context createWebSphereContext() throws NamingException
{
Properties p = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY,
"com.ibm.websphere.naming.WsnInitialContextFactory");
props.put(Context.PROVIDER_URL,"iiop://localhost:900");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
Подробнее JNDI будет рассмотрен далее в отдельной главе.
Параметр java.naming.provider.urI в теле метода createJBossContext () задает URL сервера приложений JBoss.В данном примере этот параметр равен jnp://127.0.0.1:1099/,что соответствует JNDI сервису от JBoss,запущенного на локальной машине. После установления -соединения и получения контекста из метода createJBossContext ()
этот контекст можно использовать для поиска внутреннего объекта CorrectTimeBean.
Object ref = jndiContext.lookup("CorrectTimeBean");
В нашем клиентском приложении используется метод
PortableRemoteObject.
PortableRemoteObject.
Имя, по которому выполняется поиск внутреннего объекта компонента CorrectTimeBean,задается во время развертывания. Оно может совпадать с именем компонента в XML- дескрипторе, либо быть чем-то совершенно другим.
Получив удаленную ссылку на внутренний объект, мы может использовать ее для создания нового объекта компонента CorrectTimeBean.
CorrectTimeRemote remote = home.create();
На самом деле, как уже говорилось раньше, create() - у контейнера EJB есть пул уже готовых компонентов без состояния. На вызов create() он просто возвращает один из них. Теперь мы можем вызвать метод obtainCorrectTime (), в результате чего будет вызван через RMI соответствующий метод у компонента из контейнера EJB.
Date remoteDate = remote.obtainCorrectTime();
Все готово, чтобы реализовать код в проекте Eclipse-WTP.Будут рассмотрены две реализации данного компонента - одна только при помощи технического средства Ant,встроенного в EcIipse-WTP,а другая - при помощи внутренних средств EcIipse-WTP,предназначенных для развертывания компонентов на различных контейнерах EJB.Начнем с создания проекта. Выберем закладку File->New->Project в главном меню.
(рис 3.1) Выбор типа проектаEclipse-WTP позволяет создавать специальные типы проектов для EJB.Но для того, чтобы лучше понять сам процесс создания и развертывания компонент EJB,сделаем обычный Java- проект (Рис. 3.1).
(рис 3.2) Название проекта Зададим проекту название CorrectTime (Рис. 3.2).
(рис 3.3) Выбор каталога с исходными кодамиСоздаем каталог ejbModuIe для исходных кодов EJB -компонента. Делается это при помощи опции Create new source folder (Рис. 3.3).
(рис 3.4) Создаем пакет для классов компонента CorrectTimeBeanПосле того, как создан каталог с исходным кодом, создается пакет для классов компонента. Делается это при помощи щелчка правой кнопкой мыши на папке с исходными кодами и выбора пункта меню New->Package (Рис. 3.4).
(рис 3.5) Выбор библиотек JBossПосле этого необходимо подключить необходимые библиотеки JBoss - jbossall-client.jar и jboss-j2ee.jar. Они находятся в подкаталоге client каталога, в который был установлен JBoss.Необходимые библиотеки можно добавить, войдя в закладку Project главного меню: Project -> Properties -> Java Build Path -> Add External Jars, и затем выбрав в появившейся
форме необходимые библиотеки (Рис. 3.5).
Необходимые приготовления выполнены, теперь можно приступать непосредственно к написанию кода компонента и клиента.
(рис 3.6) Меню создания интерфейсаВначале создаем удаленный интерфейс. Делается это правым щелчком мыши на соответствующем пакете и выборе New -> Interface. В появившемся меню (Рис. 3.6) можно выбрать имя интерфейса, а также те интерфейсы, от которых он наследуется (extends).В качестве альтернативы, можно явным образом задать наследуемые интерфейсы прямо в коде.
(рис 3.7) Удаленный интерфейс
(рис 3.8) Метод удаленного интерфейсаТеперь в созданном удаленном интерфейсе задаем единственный метод obtainCorrectTime() компонента CorrectTimeBean (Рис. 3.7, Рис. 3.8).
(рис 3.9) Домашний интерфейс (Home Interface)Аналогичным образом создаем домашний интерфейс (Home Interface) (Рис. 3.9).
(рис 3.10) Класс компонента После этого создается непосредственно класс компонента (Рис. 3.10).
(рис 3.11) Создание файла дескриптора развертыванияТеперь необходимо задать
(рис 3.12) Файл дескриптора компонентаJAR -архива с EJB -компонентом. Этот файл выглядит следующим образом:
Manifest-Version: 1.0
Class-Path:
Теперь займемся созданием кода клиента. Вначале создадим еще один каталог для исходных кодов клиента в корневом каталоге проекта. Назовем этот каталог client. Внутри него создадим пакет с названием correctTimeBeanClient,и в него уже поместим класс CorrectTimeClient. Клиентское приложение также будет упаковано в JAR -архив. Так что для него тоже нужно будет сделать файл MANIFEST.MF. Вот как он будет выглядеть:
Manifest-Version: 1.0
Main-class: correctTimeBeanClient.CorrectTimeClient
Class-path: CorrectTimeBean.jar jbossall-client.jar jboss-j2ee.jar
Main-class - это класс, который содержит в себе метод main(String args[]). В Class-path указываются библиотеки, которые необходимо подключить. Помимо jboss-client.jar и jboss-j2ee.jar,туда добавлен архив и с самим компонентом - это сделано для того, чтобы клиент смог использовать удаленные интерфейсы компонента. Можно было бы, конечно, поместить классы удаленных интерфейсов в отдельный архив, или добавить их непосредственно в архив к клиенту, но в этом примере для простоты было сделано так.
(рис 3.13) Класс клиентаТеперь займемся написанием ANT -скрипта для сборки компонента, клиента, и размещению компонента на контейнере JBoss.В Eclipse-WTP есть встроенная поддержка ANT. Создаем в корневом каталоге проекта два файла - build.xml и build.properties. Файл buildproperties выглядит следующим образом:
# Отключает часть логов Ant verbose=no # Компилятор не будет добавлять debug-информацию в классы compile.debug=off # Путь до jboss jboss.server.home=C:\\ejb\\jboss\\server\\default
А вот так выглядит файл build.xml:
<?xml version="1.0"?>
<project name="CorrectTimeEJB" default="all" basedir=".">
<property name="app" value="${basedir}"/>
<property name="bean.src" value="${app}/ejbModule"/>
<property name="client.src" value="${app}/client"/>
<property name="bean.lib" value="${app}/lib"/>
<property name="client.lib" value="${app}/lib"/>
<property name="bean.config" value="${bean.src}/META-INF"/>
<property name="bean.name"value="CorrectTimeBean"/>
<property name="client.name" value="CorrectTimeClient"/>
<property name="client.deploy.dir" value="client-deploy"/>
<property name="build" value="build"/>
<property name="build.classesdir" value="${build}/classes"/>
<property name="build.client.classesdir" value="${build}/classes-client"/>
<property name="assemble" value="${basedir}/assembled"/>
<property name="assemble-client" value="${basedir}/assembled-client"/>
<target name="init">
<property file="${basedir}/build.properties"/>
<path id="build.classpath">
<pathelement path="${build.classesdir}"/>
<fileset dir="${bean.lib}" includes="*.jar"/>
</path>
<path id="build.client.classpath">
<pathelement path="${build.classesdir}"/>
<pathelement path="${build.client.classesdir}"/>
<fileset dir="${client.lib}" includes="*.jar"/>
</path>
</target>
<target name="compile" depends="init">
<echo message="Compiling sources."/>
<delete dir="${build.classesdir}"/>
<mkdir dir="${build.classesdir}"/>
<delete dir="${build.client.classesdir}"/>
<mkdir dir="${build.client.classesdir}"/>
<!--Compiling the java bean classes-->
<javac srcdir="${bean.src}"
destdir="${build.classesdir}"
verbose="${verbose}"
debug = "${compile.debug}"
deprecation="yes">
<classpath refid="build.classpath"/>
</javac>
<!--Compiling the client classes--> <javac srcdir="${client.src}"
destdir="${build.client.classesdir}"
verbose="${verbose}"
debug = "${compile.debug}" deprecation="yes">
<classpath refid="build.client.classpath"/>
</javac>
</target>
<target name="ejbjar" depends="compile">
<echo message="Assembling JBoss ${bean.name} module." />
<delete dir="${assemble}"/> <mkdir dir="${assemble}"/>
<jar destfile="${assemble}/${bean.name}.jar">
<!-- classes -- >
<fileset dir="${build.classesdir}">
<include name="**/*.class" /> </fileset>
<!-- Descriptors -->
<metainf dir="${bean.config}" includes="ejb-jar.xml"/>
</jar>
</target>
<target name="clientjar" depends="compile">
<echo message="Assembling JBoss ${bean.name} module." />
<delete dir="${assemble-client}"/> <mkdir dir="${assemble-client}"/>
<jar manifest="${client.src}/MANIFEST.MF"
destfile="${assemble-client}/${client.name}.jar"> <!-- classes -- >
<fileset dir="${build.client.classesdir}">
<include name="**/*.class" />
</fileset>
</jar>
</target>
<target name="ejbdeploy" depends="ejbjar">
<echo message="Deploying ${bean.name} package to JBoss." />
<copy file="${assemble}/${bean.name}.jar"
todir="${jboss.server.home}/deploy" />
</target>
<target name="clientdeploy" depends="clientjar, ejbjar">
<delete dir="${client.deploy.dir}"/>
<mkdir dir="${client.deploy.dir}"/>
<echo message="Deploying ${client.name} package"/>
<copy file="${assemble-client}/${client.name}.jar"
todir="${client.deploy.dir}" />
<copy file="${assemble}/${bean.name}.jar"
todir="${client.deploy.dir}"/>
<copy todir="${client.deploy.dir}">
<fileset dir="${client.lib}"/>
</copy>
</target>
<target name="all" depends="clientdeploy, ejbdeploy">
<echo message="Building project CorrectTime..."/>
</target>
</project>
Скрипт build.xml под управлением ANT сначала компилирует файлы компонента и клиента в два разных каталога. После этого, используя XML -дескриптор и файлы MANIFEST.MF,создает два JAR -архива - один архив компонента, а другой - архив клиента. Архив клиента он помещает в каталог развертывания клиента client-deploy (то есть тот каталог, в котором размещается JAR-архив клиентского
приложения вместе с библиотеками и архивом компонента). Архив компонента помещается в каталог C:\ejb\jboss\server\default\deploy. На этом развертывание компонентов и клиента можно считать завершенным.
(рис 3.14) Запуск АNT-скриптаЗапустить скрипт из Eclipse-WTP на исполнение, можно, открыв файл build.xml, а затем щелкнув правой кнопкой на имени проекта справа и выборе пункта Run As->Ant Build (Рис. 3.14).
(рис 3.15) Результат работы АNT-скриптаЕсли все прошло успешно, то в нижней части экрана должно появиться соответствующее сообщение (Рис. 3.15).
Теперь можно запустить контейнер приложений JBoss.Стоит написать bat -файл для запуска JBoss,в котором будут выставляться значения переменных PATH и JAVAHOME,соответствующие JDK 1.4 или JDK 1.5.Это следует сделать из-за того,
что БД Oracle также устанавливает свою версию JDK и прописывает все пути по умолчанию к своей версии JDK. А с этой версией JDK JBoss работать не будет.
set PATH=C:\Program Files\Java\jdk1.5.0_02\bin\ set JAVA_HOME=C:\Program Files\Java\jdk1.5.0_02 run
Запускаем JBoss посредством bat-файла из каталога bin.
(рис 3.16) Компонент развернутЕсли развертывание прошло успешно, то в конце загрузки появится название компонента и его -имя (Рис. 3.16).
Теперь запускаем клиентское приложение. Для него тоже стоит создать bat -файл для запуска.
set PATH=C:\Program Files\Java\jdk1.5.0_02\bin\ set JAVA_HOME=C:\Program Files\Java\jdk1.5.0_02 java -jar CorrectTimeClient.jar
(рис 3.17) Точное времяЕсли все было сделано правильно, но на экране появится точное время, полученное от компоненты с контейнера JBoss (Рис. 3.17).
В предыдущем разделе был рассмотрено создание проекта и его развертывание только при помощи средства ANT. Однако при помощи Eclipse-WTP создание EJB -компонент можно сильно упростить (напомним, что WTP расшифровывается как Web Tools Platform).Рассмотрим, как это сделать.
Вначале создаем специализированный проект для EJB компонента. Выбираем File -> New -> New Project -> EJB -> EJB Project.
(рис 3.18) Создание EJB-проектаНазовем проект CorrectTimeBean (названия EJB -проектов должны обязательно оканчиваться на суффикс Bean).Одной из полезных возможностей Eclipse-WTP является возможность
непосредственного подключения EJB -контейнера к проекту. Сделать это можно, щелкнув на кнопку New рядом с параметром Target Runtime (Рис. 3.18).
(рис 3.19) Путь к EJB-контейнеруДалее в следующем меню выбираем используемый EJB -контейнер - JBoss 4.0 (Рис. 3.19). Теперь необходимо задать путь к EJB -контейнеру и JDK,используемый для запуска контейнера. После создания проекта будет предложено выбрать "J2EE-перспективу" (J2EE perspective ) - то есть режим раскладки окон на экране, при помощи которых удобно работать с EJB -контейнерами и компонентами. Выбираем J2EE -перспективу.
После того, как проект был создан, в корне проекта уже появился каталог ejbModule,а в нем каталог META-INF и пустой файл ejb-jar.xml для XML -дескриптора.
Переносим исходные файлы с классами компонента из проекта CorrectTime в каталог ejbModule,а XML -дескриптор в файл ejb-jar.xml.
(рис 3.20) Дерево проектовДерево проектов будет выглядеть примерно так, как на Рис. 3.20.
Теперь для того, чтобы запустить EJB -контейнер с созданным компонентом, следует выбрать вкладку Servers в нижней части экрана, щелкнуть правой кнопкой мыши и выбрать New Server.
(рис 3.21) Добавление EJB-контейнера в проектВыбираем среди Server runtime JBoss 4.0 (он там пока всего один - других мы еще не установили) и щелкаем Finish (Рис. 3.21).
(рис 3.22) Добавление проекта к ЕJВ-контейнеруТеперь осталось добавить наш проект к только что сконфигурированному контейнеру. Щелкаем правой кнопкой на строчке с именем контейнера (JBoss 4.0) и выбираем пункт Add and Remove Projects. После чего добавляем наш проект к серверу (Рис. 3.22). Щелкнув правой кнопкой на строчке контейнера JBoss,а затем, выбрав вкладку Start,можно запустить
контейнер JBoss,при этом созданный компонент из проекта будет при необходимости скомпилирован, а затем автоматически развернут.
(рис 3.23) Компонент развернут на контейнере JBossТеперь создадим отдельный проект для клиента. Выбираем пункт меню File -> New -> Project -> Java Project. Называем проект CorrectTimeBeanClient.При создании проекта Eclipse предложит перейти в перспективу с раскладкой окон для обычной, не Enterprise, Java.Это делать не обязательно. Добавляем в проект две внешних библиотеки - jbossall-client.jar и jboss-j2ee.jar.
В корне проекта создаем каталог для исходных кодов и копируем туда пакет с классом клиента из проекта CorrectTime.Осталось только подключить к этому проекту классы удаленных интерфейсов из проекта CorrectTimeBean.
(рис 3.24) Добавление проектаДля этого выбираем в проекте CorrectTimeBeanClient в главном меню Project->Properties->Java Build Path->Projects. Щелкаем Add и добавляем проект CorrectTimeBean (Рис. 3.24).
Теперь запускаем контейнер JBoss:щелкаем правой кнопкой мыши на "вкладке" Servers->JBoss и выбираем пункт меню Start. В результате компонент будет развернут. После этого щелкаем правой кнопкой на классе CorrectTimeClient,и выбираем Run As->Java Application.
(рис 3.25) Удаленное и локальное времяЕсли все прошло удачно, то будет выведено время, получено от компонента и локальное время (Рис. 3.25).
В данном примере мы покажем разработку
В удаленном интерфейсе (Remote Interface) определим два метода - dollarToEuro (double) и dollarToYena (double). Эти методы, получив в качестве параметра сумму в долларах, переведут эту сумму по некоторому постоянному курсу и вернут сумму в евро или йенах соответственно.
public interface CurrencyRemote extends EJBObject
{
public double dollarToEuro(double dollars) throws RemoteException; public double dollarToYena(double yena) throws RemoteException;
}
Домашний интерфейс (Home Interface) состоит из одного метода.
public interface CurrencyHome extends EJBHome
{
public CurrencyRemote create() throws RemoteException, CreateException;
}
В классе компонента заданы для простоты два постоянных курса перевода валют (задание этих констант, а также основных используемых извне методов выделены шрифтом).
public class CurrencyBean implements SessionBean
{
private static final long serialVersionUID = 8897567469393327031L;
private double dollarToYenaRate = 100.25;
private double dollarToEuroRate =0.83;
public void ejbCreate()
{
}
public double dollarToEuro(double dollars)
{
return dollars * dollarToEuroRate;
}
public double dollarToYena(double dollars)
{
return dollars * dollarToYenaRate;
}
public void ejbActivate() throws EJBException, RemoteException
{
}
public void ejbPassivate() throws EJBException, RemoteException
{
}
public void ejbRemove() throws EJBException, RemoteException
{
}
public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException
{
}
}
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar id="ejb-jar ID" version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd"> <description> Second session bean </description> <display-name>CurrencyConvertorBean</display-name> <enterprise-beans> <session> <ejb-name>CurrencyBean</ejb-name> <home>currencyConvertorBean.CurrencyHome</home> <remote>currencyConvertorBean.CurrencyRemote</remote> <ejb-class>currencyConvertorBean.CurrencyBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> </ejb-jar>
В этом примере не будет рассматриваться создание простого Java -проекта для компонента, ограничимся EJB -проектом. Создаем EJB -проект CurrencyConvertorBean.Также в каталоге ejbModule создаем интерфейсы и классы, вставляем
(рис 3.26) Теперь их дваЕсли все было сделано правильно - то при запуске JBoss появится сообщение об успешном развертывании двух компонентов (компонента из предыдущего примера и текущего компонента) (Рис. 3.26).
Теперь напишем небольшое приложение, которое будет работать с компонентом CurrencyConvertorBean.Это будет графическое приложение, в котором пользователь сможет ввести наименования валют, и сумму денег в одной валюте. После этого приложение выведет сумму денег в другой валюте.
Вначале создадим Java -проект и назовем его CurrencyConvertorBeanClient.Аналогичным образом, как было описано в предыдущем примере, подключим необходимые библиотеки и выберем в качестве
public class CurrencyConvertorClient extends JFrame implements ActionListener
{
private static final long serialVersionUID = 3889223123915102442L;
private Context jndiContext;
private JLabel cur1Label;
private JTextField cur1;
private JLabel cur2Label;
private JTextField cur2;
private JLabel amountLabel;
private JTextField amount;
private JButton getRate;
private JLabel rate;
public CurrencyConvertorClient()
{
super("CurrencyConvertor");
layoutPane();
try
{
jndiContext = createJBossContext();
}
catch (NamingException e)
{
e.printStackTrace();
}
}
// Получение удаленного интерфейса
private CurrencyRemote getRemoteInterface() throws NamingException, CreateException, RemoteException
{
Object ref = jndiContext.lookup("CurrencyBean");
CurrencyHome home = (CurrencyHome)
PortableRemoteObject.narrow(ref, CurrencyHome.class);
CurrencyRemote remote = home.create();
return remote;
}
private void layoutPane()
{
GridBagConstraints gc = new GridBagConstraints();
gc.insets = new Insets(5, 5, 5, 5);
Container content = getContentPane();
content.setLayout(new GridBagLayout());
cur1Label = new JLabel("Currency 1: ");
gc.fill = GridBagConstraints.BOTH;
gc.weightx = 1.0;
gc.gridx = 0;
gc.gridy = 0;
gc.gridwidth = 2;
gc.gridheight = 1;
content.add(cur1Label, gc);
cur2Label = new JLabel("Currency 2: ");
gc.gridy = 1;
content.add(cur2Label, gc);
amountLabel = new JLabel("Amount: ");
gc.gridy = 2;
content.add(amountLabel, gc);
getRate = new JButton("Get value: ");
gc.gridy = 3;
content.add(getRate, gc);
getRate.addActionListener(this);
cur1 = new JTextField();
gc.gridx = 2;
gc.gridy = 0;
content.add(cur1, gc);
cur2 = new JTextField();
gc.gridy = 1;
content.add(cur2, gc);
amount = new JTextField();
gc.gridy = 2;
content.add(amount, gc);
rate = new JLabel("Not loaded");
gc.gridy = 3;
content.add(rate, gc);
}
public void actionPerformed(ActionEvent ae)
{
loadExchangeRate();
}
private void loadExchangeRate()
{
String currency1 = cur1.getText();
String currency2 = cur2.getText();
double am;
try
{
am = Double.parseDouble(amount.getText());
}
catch (NumberFormatException e)
{
rate.setText("Invalid number");
return;
}
try
{
CurrencyRemote remote = null;
remote = getRemoteInterface();
boolean loaded = true;
// Пока поддерживается только перевод из долларов в евро
// и долларов в йены
loaded = currency1.equals("USD");
loaded = (currency2.equals("EUR") || currency2.equals("YEN"));
if (loaded)
{
double res = 0;
if (currency2.equals("EUR"))
{
res = remote.dollarToEuro(am);
}
else if (currency2.equals("YEN"))
{
res = remote.dollarToYena(am);
}
NumberFormat nf = NumberFormat.getInstance();
nf.setMaximumFractionDigits(2);
rate.setText(nf.format(res));
}
else
{
rate.setText("Rate not found");
}
}
catch (RemoteException e)
{
e.printStackTrace();
rate.setText("Error...");
}
catch (NamingException e)
{
e.printStackTrace();
rate.setText("Error...");
}
catch (CreateException e)
{
e.printStackTrace();
rate.setText("Error...");
}
}
public static void main(String args[])
{
CurrencyConvertorClient client = new CurrencyConvertorClient();
client.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
client.setLocation(400, 400);
client.setSize(300, 200);
client.setVisible(true);
}
private static Context createJBossContext() throws NamingException
{
Properties p = new Properties();
p.put("java.naming.factory.initial",
"org.jnp.interfaces.NamingContextFactory");
p.put("java.naming.provider.url", "jnp://127.0.0.1:1099");
p.put("java.naming.factory.url.pkgs","org.jboss.naming:org.jnp.interfaces");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
}
(рис 3.27) Перевод долларов в евроВведем в качестве первой валюты доллар, в качестве второй - евро, а в качестве суммы - 100 долларов. Щелкнем кнопку Get value:, и появится сумма уже в евро - 83 евро.
(рис 3.28) Курс перевода не найденЕсли же в качестве валют для перевода указывается что-либо отличное от пар USD -> EUR или USD -> YEN, то тогда выводится сообщение Rate not found (Рис. 3.28).
Разработаем компонент с функциональностью, похожей на функциональность компонента из предыдущего примера. Но теперь компонент будет брать информацию о курсах валют не из констант классов (как это было в предыдущем примере), а из базы данных. В качестве базы данных используется Oracle 9i.
В удаленном интерфейсе (Remote Interface) определим главный метод, реализующий требуемую функциональность:
double convert(String curl, String cur2, double amount) throws RemoteException, CurrencyRateNotFoundException
Этот метод, получив на вход сумму в одной валюте, описываемой строкой cur1, переведет ее в сумму в другой валюте, описываемой строкой cur2. Если одна или обе из валют не найдены, то генерируется исключение CurrencyRateNotFoundException.
public interface DBCurrencyRemote extends EJBObject
{
public double convert(String cur1, String cur2, double amount) throws RemoteException, CurrencyRateNotFoundException;
}
Домашний интерфейс (Home Interface) по сути дела ничем не отличается от домашних интерфейсов (Home Interface) компонентов из предыдущих примеров.
public interface DBCurrencyHome extends EJBHome
{
public DBCurrencyRemote create() throws RemoteException, CreateException;
}
(рис 3.29) Мифологическая модель базы данныхТеперь необходимо создать таблицу курсов валют в базе данных, в которой будут храниться строки следующего вида: CUR1, CUR2, RATE. Строка уникальным образом определяется упорядоченной парой идентификаторов типов валют: ( CUR1, CUR2 ), то есть для двух заданных валют CUR1 и CUR2 может быть задан только один курс перевода RATE валюты CUR1 в валюту CUR2 (паре ( CUR2, CUR1 )
естественно, соответствует другой курс). Инфологическая модель этой простой базы данных представлена на Рис. 3.29. Создадим такую таблицу, и введем значения курсов для некоторых пар валют. Ниже приводится SQL-код,который создает необходимую таблицу и заполняет ее информацией.
create table CURRENCY_EXCHANGE_RATE
(
CUR1 varchar(30), CUR2 varchar(30), RATE float,
primary key (CUR1, CUR2)
);
insert into CURRENCY_EXCHANGE_RATE
(CUR1, CUR2, RATE) values ('EUR', 'USD', 1.3);
insert into CURRENCY_EXCHANGE_RATE
(CUR1, CUR2, RATE) values ('USD', 'EUR', 0.8);
insert into CURRENCY_EXCHANGE_RATE
(CUR1, CUR2, RATE) values ('EUR', 'RUR', 34.4);
insert into CURRENCY_EXCHANGE_RATE
(CUR1, CUR2, RATE) values ('USD', 'RUR', 26.5);
insert into CURRENCY_EXCHANGE_RATE
(CUR1, CUR2, RATE) values ('RUR', 'EUR', 0.33);
insert into CURRENCY_EXCHANGE_RATE
(CUR1, CUR2, RATE) values ('RUR', 'USD', 0.45);
Если таблица с таким названием уже существует в базе данных, то перед выполнением приведенного выше скрипта необходимо выполнить следующую SQL -команду:
drop table CURRENCY_EXCHANGE_RATE
(рис 3.30. ) Вход в SQL PlusТеперь необходимо выполнить этот скрипт в базе данных. Открываем приложение SQL*Plus,устанавливаемое вместе с компонентом Oracle Client СУБД Oracle 9i.Оно находится в меню "Пуск" в закладке Oracle - OraClient 9 -> Application Development -> SQL*Plus. Запускаем его. SQL*Plus предложит нам ввести имя пользователя, пароль и строку для связи с базой данных.
(рис 3.31) Консоль SQL PlusЕсли вход был выполнен удачно, то будет выведена консоль SQL*Plus,при помощи которой можно выполнять различные команды языка SQL (Рис. 3.31).
(рис 3.32) Необходимая таблица создана и заполнена даннымиТеперь можно выполнить приведенный выше SQL -код в консоли. В результате будет создана и заполнена данными таблица CURRENCY_EXCHANGE_RATE (Рис. 3.32).
Доступ к базе данных будет осуществляться посредством объекта DBQuery (файл DBQuery.java ). В нем будет реализовано два основных метода:
ResultSet selectQuery(String query). Этот метод позволяет выполнить некоторую выборку из базы данных. В качестве параметра передается SQL -запрос. К примеру - если необходимо из некоторой гипотетической базы данных выбрать информацию обо всех пользователей с именем "John", то при помощи этого метода запрос мог бы быть выполнен следующим образом: ResultSet set = query.selectQuery(" select * from USERS where NAME='John'") ;ResultSet selectQueryParam(String query, Object[] params). Этот метод также позволяет выполнить некоторую выборку из базы данных. В качестве первого параметра передается шаблон SQL -запроса, в котором некоторые параметры заменены символом '?'. В качестве второго параметра передается массив объектов (в качестве объектов обычно выступают строки, целые, а также вещественные числа). При вызове метода объект DBQuery подставит значения из массива объектов
вместо знаков '?' в запросе. На место первого вхождения знака '?' будет подставлено значение первого объекта, и т.д. Количество знаков '?' в первом параметре должно равняться количеству объектов из массива во втором параметре.Ниже приводится исходный код класса DBQuery.
package db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class DBQuery
{
private Connection connection = null;
private Statement statement = null;
public DBQuery(String driver, String url,
String username, String password) throws ClassNotFoundException, SQLException
{
Class.forName(driver);
connection = DriverManager.getConnection(url, username, password);
}
public ResultSet selectQuery(String sql) throws SQLException
{
if (connection == null)
{
return null;
}
ResultSet r = null;
statement = connection.createStatement();
r = statement.executeQuery(sql);
return r;
}
public ResultSet selectQueryParam(String sql, Object[] params) throws SQLException
{
ResultSet r = null;
PreparedStatement pst = connection.prepareStatement(sql);
statement = pst;
pst.clearParameters();
for (int i = 0; i < params.length; ш++)
{
pst.setObject(i + 1, params[i]);
}
r = pst.executeQuery();
return r;
}
public void close() throws SQLException
{
if (statement != null)
{
statement.close();
statement = null;
}
}
public void finalize()
{
try
{
close();
}
catch (SQLException e)
{
}
}
}
Помимо этого был создан класс-утилита, который несколько упрощает подключение к базам данных. При помощи его методов можно получать строку подключения к разным типам баз данных, а также получать имя класса драйвера базы данных. На данный момент реализация выполнена только для базы данных Oracle 9i.
package db;
public class DBDriverUtilities
{
public static final int ORACLE = 1;
/**
*Creates the URL for the ORACLE database
*@param host
*@param dbName
*@return
*/
public static String makeURL(String host, String dbName, int dbType)
{
if (dbType == ORACLE)
{
return "jdbc:oracle:thin:@" + host + ":1521:" + dbName;
}
else
{
return null;
}
}
public static String getDriver(int dbType)
{
if (dbType == ORACLE)
{
return "oracle.jdbc.OracleDriver";
}
else
{
return null;
}
}
}
Для того, чтобы можно было подключаться к базе данных Oracle 9i необходимо подключить библиотеку с соответствующим драйвером от Oracle.Она называется ojdbc14.jar и находится в каталоге jdbc\lib установочного каталога Oracle.Эту библиотеку нужно скопировать в каталог server\default\lib установочного каталога JBoss.Это необходимо для того,
чтобы компонент, размещенный на сервере смог использовать данную библиотеку для связи с базой данных. Подключать библиотеку к проекту в Eclipse-WTP не обязательно, хотя может оказаться полезной следующая практика - создать небольшой Java -проект, подключить к нему библиотеку доступа к базе данных и отладить все запросы в этом небольшом проекте, так как отладка непосредственно компонента на сервере - более длительный по времени процесс.
В классе компонента задан один метод, который производит выборку необходимого курса валют из базы данных. Если какая-либо из валют не найдена в базе данных, то генерируется исключение - CurrencyRateNotFoundException, которое в дальнейшем может быть обработано на стороне клиента. Выборка в базе данных производится при помощи уже реализованного класса DBQuery.
public class DBCurrencyBean implements SessionBean
{
private static final long serialVersionUID = 1858369246980240402L;
private String username = "boris";
private String password = "quasimodo";
private String host = "localhost";
private String dbName = "EJBDB";
public void ejbCreate()
{
}
public double convert(String cur1, String cur2, double amount)
throws CurrencyRateNotFoundException
{
DBQuery query = createDatabaseQuery();
double rate = 0;
try
{
ResultSet set =query.selectQueryParam
("SELECT RATE FROM " + "CURRENCY_EXCHANGE_RATE "
+ "WHERE CUR1=? AND CUR2=?", new Object[] {cur1, cur2});
if (set.next())
{
rate = set.getDouble(1);
}
else
{
throw new CurrencyRateNotFoundException(cur1, cur2);
}
query.close();
}
catch (SQLException e)
{
return -1;
}
return rate * amount;
}
private DBQuery createDatabaseQuery()
{
DBQuery query = null;
int db = DBDriverUtilities.ORACLE;
try
{
query = new DBQuery(DBDriverUtilities.getDriver(db),
DBDriverUtilities.makeURL(host, dbName, db), username, password);
}
catch (SQLException e)
{
e.printStackTrace();
}
catch (ClassNotFoundException e)
{
System.err.println("Class library not found!!!");
}
return query;
}
public void ejbActivate() throws EJBException, RemoteException
{
}
public void ejbPassivate() throws EJBException, RemoteException
{
}
public void ejbRemove() throws EJBException, RemoteException
{
}
public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException
{
}
}
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar id="ejb-jar ID" version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd"> <description> Third session bean </description> <display-name>DBCurrencyBean</display-name> <enterprise-beans> <session> <ejb-name>DBCurrencyBean</ejb-name> <home>dbCurrencyConvertorBean.DBCurrencyHome</home> <remote>dbCurrencyConvertorBean.DBCurrencyRemote</remote> <ejb-class> dbCurrencyConvertorBean.DBCurrencyBean </ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> </ejb-jar>
Клиентское приложение работает аналогично клиентскому приложению из предыдущего примера, за исключением того, что обрабатывается исключение CurrencyRateNotFoundException, и в этом случае выводится сообщение Rate not found.
public class DBCurrencyConvertorClient extends JFrame implements ActionListener
{
private static final long serialVersionUID = -7439969561347013270L;
private Context jndiContext;
private JLabel cur1Label;
private JTextField cur1;
private JLabel cur2Label;
private JTextField cur2;
private JLabel amountLabel;
private JTextField amount;
private JButton getRate;
private JLabel rate;
public DBCurrencyConvertorClient()
{
super("CurrencyConvertor");
layoutPane();
try
{
jndiContext = createJBossContext();
}
catch (NamingException e)
{
e.printStackTrace();
}
}
private DBCurrencyRemote getRemoteInterface() throws NamingException,
CreateException, RemoteException
{
Object ref = jndiContext.lookup("DBCurrencyBean");
DBCurrencyHome home = (DBCurrencyHome)
PortableRemoteObject.narrow(ref, DBCurrencyHome.class);
DBCurrencyRemote remote = home.create();
return remote;
}
private void layoutPane()
{
GridBagConstraints gc = new GridBagConstraints();
gc.insets = new Insets(5, 5, 5, 5);
Container content = getContentPane();
content.setLayout(new GridBagLayout());
cur1Label = new JLabel("Currency 1: ");
gc.fill = GridBagConstraints.BOTH;
gc.weightx = 1.0;
gc.gridx = 0;
gc.gridy = 0;
gc.gridwidth = 2;
gc.gridheight = 1;
content.add(cur1Label, gc);
cur2Label = new JLabel("Currency 2: ");
gc.gridy = 1;
content.add(cur2Label, gc);
amountLabel = new JLabel("Amount: ");
gc.gridy = 2;
content.add(amountLabel, gc);
getRate = new JButton("Get value: ");
gc.gridy = 3;
content.add(getRate, gc);
getRate.addActionListener(this);
cur1 = new JTextField();
gc.gridx = 2;
gc.gridy = 0;
content.add(cur1, gc);
cur2 = new JTextField();
gc.gridy = 1;
content.add(cur2, gc);
amount = new JTextField();
gc.gridy = 2;
content.add(amount, gc);
rate = new JLabel("Not loaded");
gc.gridy = 3;
content.add(rate, gc);
}
public void actionPerformed(ActionEvent ae)
{
loadExchangeRate();
}
private void loadExchangeRate()
{
String currency1 = cur1.getText();
String currency2 = cur2.getText();
double am;
try
{
am = Double.parseDouble(amount.getText());
}
catch (NumberFormatException e)
{
rate.setText("Invalid number"); return;
}
try
{
DBCurrencyRemote remote = getRemoteInterface();
double res = 0;
res = remote.convert(currency1, currency2, am);
NumberFormat nf = NumberFormat.getInstance();
nf.setMaximumFractionDigits(2);
rate.setText(nf.format(res));
}
catch (CurrencyRateNotFoundException e)
{
rate.setText("Rate not found");
}
catch (RemoteException e)
{
e.printStackTrace();
rate.setText("Error...");
}
catch (NamingException e)
{
e.printStackTrace();
rate.setText("Error...");
}
catch (CreateException e)
{
e.printStackTrace();
rate.setText("Error...");
}
}
public static void main(String args[])
{
DBCurrencyConvertorClient client = new
DBCurrencyConvertorClient();
client.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
client.setLocation(400, 400);
client.setSize(300, 200);
client.setVisible(true);
}
private static Context createJBossContext() throws NamingException
{
Properties p = new Properties();
p.put("java.naming.factory.initial",
"org.jnp.interfaces.NamingContextFactory");
p.put("java.naming.provider.url", "jnp://127.0.0.1:1099");
p.put("java.naming.factory.url.pkgs",
"org.jboss.naming:org.jnp.interfaces");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
}
Два проекта - DBCurrencyConvertorBean и DBCurrencyConvertorBeanClient создаются аналогично проектам из предыдущего примера. Добавляем проект с компонентой к JBoss и запускаем JBoss.
(рис 3.33) Компонент развернут на сервере
(рис 3.34) Получение информации о курсах валютИнтерфейс клиентского приложения аналогичен интерфейсу из предыдущего примера (Рис. 3.34).
(рис 35) Курс не найденПри возникновении CurrencyRateNotFoundException, оно обрабатывается и выводится сообщение Rate not found.
Предыдущий пример можно несколько упростить при помощи встроенных возможностей JBoss.Сервер JBoss поддерживает встроенный пул соединений к базам данных. Эти соединения можно получать из компонента EJB при помощи JNDI.Для того чтобы сконфигурировать подключение к новой базе данных можно для начала посмотреть файлов конфигураций для различных
баз данных. Они находятся в каталоге docs/examples/ установочного каталога JBoss.Нас интересует файл oracle-ds.xml. Здесь уже представлен вариант этого файла, в который вставлены значения, соответствующие подключению к нашей базе данных.
Сделать копию проекта в Eclipse можно щелкнув правой кнопкой мыши на проекте слева и выбрав в меню пункт Copy.
<?xml version="1.0" encoding="UTF-8"?> <datasources> <local-tx-datasource> <!-JNDI имя, по которому мы будет получать подключение--> <jndi-name>OracleDS</jndi-name> <!-Строка подключения к БД--> <connection-url> jdbc:oracle:thin:@localhost:1521:ejbdb </connection-url> <!-Класс драйвера БД--> <driver-class>oracle.jdbc.OracleDriver</driver-class> <!-Имя пользователя в БД и пароль--> <user-name>boris</user-name> <password>quasimodo</password> <exception-sorter-class-name> org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter </exception-sorter-class-name> <!-Название описания соответствий типов--> <metadata> <type-mapping>Oracle9i</type-mapping> </metadata> </local-tx-datasource> </datasources>
Далее этот файл необходимо скопировать в каталог server\default\deploy установочного каталога JBoss.
Для того, чтобы использовать новый способ подключения к базам данных требуется изменить только класс компонента. В нем необходимо переопределить метод setSessionContext(SessionContext arg) следующим образом:
public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException
{
try
{
InitialContext ctx = new InitialContext();
if (dataSource == null)
// Получаем объект DataSource из JNDI контекста
dataSource = (DataSource) ctx.lookup("java:/OracleDS");
System.out.println("Data source aquired");
}
catch (NamingException ex)
{
throw new EJBException(ex);
}
}
Теперь метод convert будет выглядеть так:
public double convert(String cur1, String cur2, double amount)
throws CurrencyRateNotFoundUpgException
{
if (dataSource == null)
{
return -1;
}
try
{
// Получаем подключение к базе данных из пула соединений
Connection conn = dataSource.getConnection();
// Готовим SQL-команду к выполнению
PreparedStatement p = conn.prepareStatement
("SELECT RATE FROM CURRENCY_EXCHANGE_RATE " + "WHERE CUR1NAME=? AND CUR2NAME=?");
p.setString(1, cur1);
p.setString(2, cur2);
ResultSet r = p.executeQuery();
// Обрабатываем полученный результат
if (r.next())
{
double rate = r.getDouble(1);
return rate * amount;
}
else
{
throw new
CurrencyRateNotFoundUpgException(cur1, cur2);
}
}
catch (SQLException e)
{
e.printStackTrace();
}
return -1;
}
Размещаем компонент на сервере JBoss и запускаем клиентское приложение.
(рис 3.36) Получение информации о курсах валютКак видно на Рис. 3.36,система из клиентского приложения и компоненты EJB работает так же, как и раньше.
Теперь будет рассмотрен несколько более сложный пример, чем предыдущие примеры. В нем будет продемонстрирована работа такого компонента, имеющегося у большинства интернет магазинов как корзина с покупками. Пользователь, зайдя на страницу нашего магазина сможет залогиниться, введя свое имя, просмотреть список доступных товаров, добавить необходимые ему товары в корзину, сделать заказ, пересчитать количество отдельных товаров в заказе, а также просмотреть свои предыдущие заказы. Торговать интернет магазине будет сигаретами.
(рис 3.37) Инфологическая модель базы данных Список товаров, сделанные заказы и их состав будут храниться в базе данных, инфологическая модель которой представлена на Рис. 3.37. Эта база данных состоит из трех основных таблиц:
COMMODITIESLIST, в которой хранится информация о всех доступных товарах в магазине. Поле NAME - название товара, DESCRIPTION - его описание, PRICE - цена в долларах США, а необязательное поле IMAGEPATH - идентификатор изображения, соответствующее товару (используется при отображении списка товаров в интернете).ORDERS, в которой хранится информация о заказе. DATE_CREATED - дата, когда заказ был сделан и USERNAME - имя пользователя, сделавшего заказ.COMMODITYORDER, которая описывает товары, из которых состоит заказ. ORDERID - идентификатор заказа, COMMODITYID -идентификатор товара, QUANTITY - количество экземпляров этого товара в заказе. ORDERID И COMMODITYID связаны с таблицами COMMODITIES_LIST и ORDERS по внешнему ключу.Для того, чтобы создать таблицы в базе данных Oracle 9i,необходимо выполнить следующий SQL-код.Помимо создания таблиц в базе данных, данный SQL-код заполняет таблицы некоторым набором значений по умолчанию. Для каждой таблицы определен триггер, автоматически генерирующий поле ID для каждой добавленной строки таблицы. Вначале создается пакет ORDER_ID_PKG,
в котором определена переменная, хранящая целое значение, и функция ее возвращающая. Триггер, генерирующий поле ID для таблицы ORDERS, сохраняет новое сгенерированное значение в поле LAST_ID пакета ORDER_ID_PKG. Это сделано для того, чтобы получить ID последнего добавленного заказа и использовать его при добавлении строк в таблицу COMMODITY_ORDER.
create or replace package ORDER_ID_PKG as LAST_ID integer;
function GET_LAST_ID return integer;
end ORDER_ID_PKG;
/
create or replace package body ORDER_ID_PKG as function GET_LAST_ID return integer is
begin
return LAST_ID; end GET_LAST_ID;
end ORDER_ID_PKG;
/
create table COMMODITIES_LIST
(
ID integer not null,
NAME varchar(20) not null,
DESCRIPTION varchar(50) not null,
PRICE float not null,
IMAGEPATH varchar(30), primary key(ID));
create sequence COMMODITIES_SEQ start with 1 increment by 1 nomaxvalue;
create trigger COMMODITIES_TRIGGER
before insert on COMMODITIES_LIST
for each row
begin
select COMMODITIES_SEQ.nextval into :new.id from dual;
end;
/
insert into COMMODITIES_LIST(NAME, DESCRIPTION, PRICE, IMAGEPATH)
values('Kent 1', 'Normal cigarettes', 1.99, 'img/kent1.jpg');
insert into COMMODITIES_LIST(NAME, DESCRIPTION, PRICE, IMAGEPATH)
values('Kent 4', 'Light cigarettes', 1.99, 'img/kent4.jpg');
insert into COMMODITIES_LIST(NAME, DESCRIPTION, PRICE, IMAGEPATH)
values('Kent 8', 'Very light cigarettes', 1.99, 'img/kent8.jpg');
insert into COMMODITIES_LIST(NAME, DESCRIPTION, PRICE, IMAGEPATH)
values('Lucky Strike', 'Pure American Taste', 2.30, 'img/lucky.jpg');
insert into COMMODITIES_LIST(NAME, DESCRIPTION, PRICE, IMAGEPATH)
values('Portugas', 'Fine Cuban Cigar', 10.95, 'img/portugas.jpg'); select * from COMMODITIES_LIST;
/
create table ORDERS
(
ID integer not null,
DATE_CREATED DATE not null,
USERNAME varchar(20) not null, primary key(ID)
);
create sequence ORDERS_SEQ start with 1 increment by 1 nomaxvalue;
create trigger ORDERS_TRIGGER before insert on ORDERS for each row
begin
select ORDERS_SEQ.nextval into :new.id from dual;
end;
/
create table COMMODITY_ORDER
(
ID integer not null, ORDER_ID integer not null, COMMODITY_ID integer not null, QUANTITY integer not null, primary key(ID)
);
alter table COMMODITY_ORDER add constraint FK_ORDER foreign key (ORDER_ID) references ORDERS(ID);
alter table COMMODITY_ORDER add constraint FK_COMMODITIES foreign key(COMMODITY_ID) references COMMODITIES_LIST(ID);
create sequence COMMODITY_ORDER_SEQ start with 1 increment by 1 nomaxvalue;
create trigger COMMODITY_ORDER_TRIGGER
before insert on COMMODITY_ORDER
for each row
begin
select COMMODITY_ORDER_SEQ.nextval into :new.id from dual;
end;
/
Если таблицы уже созданы в базе данных, то необходимо предварительно выполнить следующий SQL-код,который удалит эти таблицы и последовательности при помощи которых генерировалось автоматически значение основного ключа строки в таблице.
drop table COMMODITY_ORDER; drop table ORDERS; drop table COMMODITIES_LIST; drop sequence COMMODITY_ORDER_SEQ; drop sequence COMMODITIES_SEQ; drop sequence ORDERS_SEQ;
Стоит сделать несколько замечаний - для таблицы COMMODITYORDER выполняется операция drop в самом начале кода. Это сделано из-за того, что таблица COMMODITYORDER связана внешним ключом с двумя другими таблицами. БД Oracle не даст нам выполнить операцию drop для двух других таблиц, пока связь посредством внешнего ключа с таблицей COMMODITYORDER.
Для того чтобы соотнести объекты из базы данных с Java -классами был создан класс абстрактный класс DataItem. В нем есть поле id, которое соответствует ID объекта в базе данных (как было описано выше, у любого объекта в этой базе данных есть свой ID ). Класс находится в пакете dataObjects.
public abstract class DataItem implements Serializable
{
protected int id = -1;
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
}
От класса DataItem наследуются три других класса - Commodity, CommodityOrder и Order.Commodity соответствует товару из таблицы COMMODITY_LIST, CommodityOrder - товару из заказа в таблице COMMODITY_ORDERS, Order - заказу из таблицы ORDERS.
Исходный код класса Commodity представлен далее:
public class Commodity extends DataItem
{
private static final long serialVersionUID = -7004067843847157531L;
private String name;
private String description;
private String imagePath;
private double price;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getDescription()
{
return description;
}
public void setDescription(String description)
{
this.description = description;
}
public double getPrice()
{
return price;
}
public void setPrice(double price)
{
this.price = price;
}
public String getImagePath()
{
return imagePath;
}
public void setImagePath(String imagePath)
{
this.imagePath = imagePath;
}
public String toString()
{
return "ID=" + id + "; NAME=" + name + "; DESCRIPTION" + description + "; PRICE=" + price + "; IMAGEPATH=" + imagePath;
}
}
Исходный код класса CommodityOrder представлен далее:
public class CommodityOrder extends DataItem
{
private static final long serialVersionUID = -592875988844838475L;
private int commodityId;
private int commodityQuantity;
public int getCommodityId()
{
return commodityId;
}
public void setCommodityId(int commodityId)
{
this.commodityId = commodityId;
}
public int getCommodityQuantity()
{
return commodityQuantity;
}
public void setCommodityQuantity(int commodityQuantity)
{
this.commodityQuantity = commodityQuantity;
}
}
Исходный код класса Order представлен далее:
public class Order extends DataItem
{
private static final long serialVersionUID = -3276613990865210869L;
private String name; private Date date;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Date getDate()
{
return date;
}
public void setDate(Date date)
{
this.date = date;
}
}
Для реализации заявленной функциональности будут использоваться три
CommoditiesListBean используется для поиска товара в базе данных по ID товара, а также для получения всех товаров в базе данных.
OrdersListBean используется для составления списка сделанных заказов по имени пользователя, а также для получения списка товаров, входящих в заказ, описываемый при помощи ID.
ShoppingBasketBean используется для реализации логики корзины в интернет магазине. При помощи него можно добавлять товары в корзину, пересчитывать количество товаров и делать заказ.
В удаленном интерфейсе (Remote Interface) определим два метода:
public List getAvailableCommodities()throws RemoteException, SQLException. Этот метод возвращает список из объектов класса Commodity, которые соответствуют записям в таблице COMMODITIES_LIST.public Commodity getCommodityById(int id) throws RemoteException, SQLException. Этот метод по id возвращает объект класса Commodity, соответствующий товару в базе данных.public interface CommoditiesListRemote extends EJBObject
{
public List getAvailableCommodities() throws RemoteException,
SQLException;
public Commodity getCommodityById(int id) throws RemoteException, SQLException;
}
Этот интерфейс аналогичен домашним интерфейсам из предыдущих примеров.
public interface CommoditiesListHome extends EJBHome
{
public CommoditiesListRemote create() throws RemoteException, CreateException;
}
В классе компонента заданы два основных метода - ListgetAvailableCommodities() и Commodity getCommodityById(int id). В первом методе выполняется SELECT запрос в базе данных, после чего на основе сгенерированного ResultSet создается список из объектов класса Commodity. Выполнение запроса к базе данных, а также формирование объектов Commodity выделено шрифтом.
public class CommoditiesListBean implements SessionBean
{
private static final long serialVersionUID = -8641939595462379799L;
private DataSource dataSource = null;
public List getAvailableCommodities() throws SQLException
{
if (dataSource == null)
throw new SQLException("Datasource not loaded");
Connection conn = dataSource.getConnection();
PreparedStatement p = conn.prepareStatement("SELECT * FROM " + " COMMODITIES_LIST");
ResultSet r = p.executeQuery();
List arrayList = new ArrayList();
while (r.next())
{
Commodity commodity = new Commodity();
commodity.setId(r.getInt("ID"));
commodity.setName(r.getString("NAME"));
commodity.setDescription(r.getString("DESCRIPTION"));
commodity.setPrice(r.getDouble("PRICE"));
commodity.setImagePath(r.getString("IMAGEPATH"));
arrayList.add(commodity);
}
conn.close();
return arrayList;
}
/*
Во втором методе выполняется поиск товара по его ID. Если такой товар найден, то возвращается
созданный по ResultSet объект Commodity.
*/
public Commodity getCommodityById(int id) throws SQLException
{
if (dataSource == null)
throw new SQLException("Datasource not loaded");
Connection conn = dataSource.getConnection();
PreparedStatement p = conn.prepareStatement
("SELECT * FROM " + " COMMODITIES_LIST WHERE ID=?");
p.setInt(1, id);
ResultSet r = p.executeQuery();
Commodity commodity = new Commodity();
if (r.next())
{
commodity.setId(r.getInt("ID"));
commodity.setName(r.getString("NAME"));
commodity.setDescription(r.getString("DESCRIPTION"));
commodity.setPrice(r.getDouble("PRICE"));
commodity.setImagePath(r.getString("IMAGEPATH"));
}
conn.close(); return commodity;
}
public void ejbCreate()
{
}
public void ejbActivate() throws EJBException, RemoteException
{
}
public void ejbPassivate() throws EJBException, RemoteException
{
}
public void ejbRemove() throws EJBException, RemoteException
{
}
public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException
{
try
{
InitialContext ctx = new InitialContext();
if (dataSource == null)
dataSource = (DataSource) ctx.lookup("java:/OracleDS");
}
catch (NamingException ex)
{
throw new EJBException(ex);
}
}
}
Для того чтобы проверить работоспособность компонента CommoditiesListBean, было написано тестовое консольное клиентское приложение. В нем выполнялся вызов обеих методов компонента, а результат выводился в консоль. Код этого клиента приводится далее.
public class TestClient
{
public static void main(String args[])
{
try
{
Context jndiContext = createJBossContext();
Object ref = jndiContext.lookup("CommoditiesListBean");
CommoditiesListHome home =
(CommoditiesListHome) PortableRemoteObject.narrow(ref, CommoditiesListHome.class);
CommoditiesListRemote remote = home.create();
System.out.println("There are following items available " +" in stock:");
List list = remote.getAvailableCommodities();
Iterator iter = list.iterator();
while (iter.hasNext())
{
Commodity commodity = (Commodity) iter.next();
System.out.println(commodity.toString());
}
Commodity commodity = remote.getCommodityById(2);
System.out.println("Single commotidy - " + commodity.toString());
}
catch (RemoteException e)
{
e.printStackTrace();
}
catch (NamingException e)
{
e.printStackTrace();
}
catch (CreateException e)
{
e.printStackTrace();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
public static Context createJBossContext() throws NamingException
{
Properties p = new Properties();
p.put("java.naming.factory.initial",
"org.jnp.interfaces.NamingContextFactory");
p.put("java.naming.provider.url", "jnp://127.0.0.1:1099");
p.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
}
Для тестового клиента был создан отдельный Java -проект в Eclipse.
(рис 3.38) Консольный вывод тестового клиентаВывод тестового клиента показан на Рис. 3.38.
В удаленном интерфейсе определим два метода:
List getOrdersForUser(String username) throws RemoteException, SQLException. Этот метод возвращает список из объектов Order для пользователя с именем username.List getCommodityOrdersForOrder(int orderId) throws RemoteException, SQLException. Этот метод возвращает список из объектов CommodityOrder для заказа с ID = orderId.public interface OrdersListRemote extends EJBObject
{
public List getOrdersForUser(String username) throws RemoteException, SQLException;
public List getCommodityOrdersForOrder(int orderId) throws RemoteException, SQLException;
}
Этот интерфейс аналогичен домашним интерфейсам из предыдущих примеров.
public interface OrdersListHome extends EJBHome
{
public OrdersListRemote create() throws RemoteException, CreateException;
}
Выполнение запроса к базе данных и формирование объектов Order и CommodityOrder выделено шрифтом.
public class OrdersListBean implements SessionBean
{
private static final long serialVersionUID = 2767604097827994556L;
private DataSource dataSource = null;
public List getOrdersForUser(String username) throws SQLException
{
if (dataSource == null)
throw new SQLException("Datasource not loaded");
Connection conn = dataSource.getConnection();
PreparedStatement p = conn.prepareStatement
("SELECT * FROM " + " ORDERS WHERE USERNAME=?");
p.setString(1, username);
ResultSet r = p.executeQuery();
List arrayList = new ArrayList();
while(r.next())
{
Order order = new Order();
order.setId(r.getInt("ID"));
order.setName(r.getString("USERNAME"));
order.setDate(r.getDate("DATE_CREATED"));
arrayList.add(order);
}
conn.close();
return arrayList;
}
public List getCommodityOrdersForOrder(int orderId) throws SQLException
{
if (dataSource == null)
throw new SQLException("Datasource not loaded");
Connection conn = dataSource.getConnection();
PreparedStatement p = conn.prepareStatement
("SELECT * FROM " + " COMMODITY_ORDER WHERE ORDER_ID=?");
p.setInt(1, orderId);
ResultSet r = p.executeQuery();
List arrayList = new ArrayList();
while(r.next())
{
CommodityOrder order = new CommodityOrder();
order.setId(r.getInt("ID"));
order.setCommodityId(r.getInt("COMMODITY_ID"));
order.setCommodityQuantity(r.getInt("QUANTITY"));
arrayList.add(order);
}
conn.close();
return arrayList;
}
public void ejbCreate() {}
public void ejbActivate() throws EJBException, RemoteException {}
public void ejbPassivate() throws EJBException, RemoteException {}
public void ejbRemove() throws EJBException, RemoteException {}
public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException
{
try
{
InitialContext ctx = new InitialContext();
if (dataSource == null)
dataSource = (DataSource) ctx.lookup("java:/OracleDS");
System.out.println("Data source aquired");
}
catch (NamingException ex)
{
throw new EJBException(ex);
}
}
}
Для того чтобы проверить работоспособность компонента OrdersListBean, было написано тестовое консольное клиентское приложение. В нем выполнялся вызов обеих методов компонента, а результат выводился в консоль. Код этого клиента приводится далее.
public class TestClient3
{
public static void main(String args[])
{
try
{
Context jndiContext = createJBossContext();
Object ref = jndiContext.lookup("OrdersListBean");
OrdersListHome home = (OrdersListHome) PortableRemoteObject.narrow(ref, OrdersListHome.class);
OrdersListRemote remote = home.create();
List orders = remote.getOrdersForUser("Boris");
System.out.println("Orders for user Boris");
for (Iterator iter = orders.iterator(); iter.hasNext();)
{
Order order = (Order) iter.next();
List commodityList =remote.
getCommodityOrdersForOrder(order.getId());
System.out.println("Commodities for order " +
order.getId() + " " + order.getDate().toString());
for (Iterator iter2 = commodityList.iterator(); iter2.hasNext();)
{
CommodityOrder cOrder = (CommodityOrder) iter2.next();
System.out.println(cOrder.getCommodityId() + " " + cOrder.getCommodityQuantity());
}
}
}
catch (RemoteException e)
{
e.printStackTrace();
}
catch (NamingException e)
{
e.printStackTrace();
}
catch (CreateException e)
{
e.printStackTrace();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
public static Context createJBossContext() throws NamingException
{
Properties p = new Properties();
p.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
p.put("java.naming.provider.url", "jnp://127.0.0.1:1099");
p.put("java.naming.factory.url.pkgs",
"org.jboss.naming:org.jnp.interfaces");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
}
(рис 3.39) Консольный вывод тестового клиентаВывод тестового клиента показан на Рис. 3.39.
Интерфейс этого
public List getCurrentCommoditiesList() throws RemoteException. Этот метод возвращает список из объектов CommodityOrder, соответствующих товарам в корзине.public boolean isEmpty() throws RemoteException. Этот метод возвращает булевское значение true, если корзина пуста.public addCommodity(int commodityId) throws RemoteException. Этот метод добавляет одну единицу товара с ID = commodityId в корзину.public recalculateCommodity(int commodityId, int newCount) throws RemoteException. Этот метод изменяет количество товаров с ID = commodityId в корзине на newCount.public deleteCommodity(int commodityId) throws RemoteException. Этот метод удаляет товар из корзины.public void processOrder() throws RemoteException, SQLException. Этот метод создает новый объект в таблице ORDERS базы данных, а затем добавляет записи с ORDERID, соответствущему только что добавленному заказу, в таблицу COMMODITYORDERS.Как вы уже, наверное, успели убедиться, вызов каждого из этих методов может менять состояние компонента ShoppingBasketBean.
public interface ShoppingBasketRemote extends EJBObject
{
public List getCurrentCommoditiesList() throws RemoteException;
public boolean isEmpty() throws RemoteException;
public void addCommodity(int commodityId) throws RemoteException;
public void recalculateCommodity(int commodityId, int newCount)
throws RemoteException;
public void deleteCommodity(int commodityId) throws RemoteException;
public void processOrder() throws RemoteException, SQLException;
}
Этот интерфейс отличается от домашних интерфейсов предыдущих компонентов. Компонент ShoppingBasketBean создается отдельно для каждого пользователя, поэтому удобно в него передать имя пользователя. Имя пользователя будет использоваться при выполнении запроса в методе processOrder (). В метод create(String username) домашнего интерфейса передается имя пользователя.
public interface ShoppingBasketHome extends EJBHome
{
public ShoppingBasketRemote create(String username) throws RemoteException, CreateException;
}
Компонент ShoppingBasketBean должен постоянно поддерживать текущий список товаров. В классе компонента это делается посредством списка из объектов класса CommodityOrder. Все операции по добавлению, пересчету, и удалению товаров из списка текущих товаров в корзине сводятся к операциям поиска, удаления и добавления соответствующего объекта CommodityOrder в этом списке.
Определен дополнительный вспомогательный метод private CommodityOrder findOrderById(int commodityId). Он ищет соответствующий объект в списке и возвращает либо его, либо значение null, если он найден не был. Код метода, осуществляющий поиск по commodityId представлен далее.
Iterator iter = commodityOrderList.iterator();
CommodityOrder order = null;
while (iter.hasNext())
{
CommodityOrder currentOrder = (CommodityOrder) iter.next();
if (currentOrder.getCommodityId() == commodityId)
{
order = currentOrder;
break;
}
}
return order;
Так как теперь компонент при создании должен получать имя пользователя, метод public void ejbCreate(String username) также несколько изменился. Теперь он сохраняет переданное имя пользователя во внутреннее поле String username, а также создает список, в котором в дальнейшем будет храниться текущий список товаров в корзине. Код этого метода представлен далее.
this.username = username; commodityOrderList = new ArrayList();
В методе public void processOrder() throws SQLException выполняется создание заказа в базе данных по товарам, которые есть в корзине на данный момент. Если в корзине нет товаром, то этот метод не делает ничего. В противном случае он сначала создает объект в таблице ORDERS посредством следующего запроса:
PreparedStatement p = conn.prepareStatement("insert into " "ORDERS(DATE_CREATED, USERNAME) values (?, ?)");
p.setDate(1,new java.sql.Date(System.currentTimeMillis()));
p.setString(2,username);
p.executeUpdate();
Теперь необходимо получить ID этого объекта. В Интернете много рекомендаций на тему того, что это необходимо делать путем выборки максимального ID из таблицы в базе данных.
select max(ID) from ORDERS where USERNAME = 'BORIS'
Не используйте этот подход! Во-первых, он будет работать только тогда, когда генерируемый индекс получается путем увеличения предыдущего, а во-вторых, если один пользователь попробует оформить два заказа одновременно, то может быть выбран ID другого оформляемого в этот момент заказа, и список товаров далее будет добавлен не к тому заказу.
Поэтому в базе данных был создан пакет, в который посредством триггера сохранялось значение поля ID последнего добавленного объекта в таблицу ORDERS. Стоит отметить, что пакеты в СУБД Oracle сохраняют свое состояние для каждой сессии (от создания подключения до его закрытия), поэтому можно не опасаться, что в пакете окажется ID другого заказа.
p = conn.prepareStatement("select ORDER_ID_PKG.GET_LAST_ID from dual");
ResultSet r = p.executeQuery();
int orderId = -1;
if (r.next())
{
orderId = r.getInt(1);
}
То есть выбирается максимальное значение ID для текущего пользователя. Учитывая, что в нашем магазине не может быть создано двух корзин одновременно для одного пользователя, такое решение будет корректно работать.
Затем для каждого товара в корзине добавляется по записи в таблице COMMODITYORDER.
while (iter.hasNext())
{
CommodityOrder currentOrder = (CommodityOrder) iter.next();
p = conn.prepareStatement("insert into COMMODITY_ORDER
(ORDER_ID, " + "COMMODITY_ID, QUANTITY) values (?, ?, ?)");
p.setInt(1, orderId);
p.setInt(2, currentOrder.getCommodityId());
p.setInt(3, currentOrder.getCommodityQuantity());
p.execute();
}
Метод void deleteCommdity(int commodityId) сводится к вызову метода recalculateCommodity(commodityId, 0).
Метод void addCommodity(int commodityId) вначале проверяет, есть ли товар с ID=commodityId в корзине, и либо добавляет новый объект CommodityOrder в список, либо просто увеличивает количество экземпляров в корзине товара на один.
CommodityOrder order = findOrderById(commodityId);
if (order == null)
{
order = new CommodityOrder();
order.setCommodityId(commodityId);
order.setCommodityQuantity(1);
commodityOrderList.add(order);
}
else
{
order.setCommodityQuantity(order.getCommodityQuantity() + 1);
}
Метод void recalculateCommodity(int commodityId,int newCount) изменяет количество экземпляров товара c ID=commodityId на newCount. Если newCount = 0, то товар удаляется из списка. Если товара не было в списке, то он туда добавляется.
CommodityOrder order = findOrderById(commodityId);
if (order == null)
{
if (newCount > 0)
{
order = new CommodityOrder();
order.setCommodityId(commodityId);
order.setCommodityQuantity(1);
commodityOrderList.add(order);
}
}
else
{
if (newCount > 0)
{
order.setCommodityQuantity(newCount);
}
else
{
commodityOrderList.remove(order);
}
}
Код класса компонента ShoppingBasketBean приводится далее.
public class ShoppingBasketBean implements SessionBean
{
private static final long serialVersionUID = 8173942031447022589L;
private String username = null;
private DataSource dataSource = null;
private List commodityOrderList;
public void ejbCreate(String username)
{
this.username = username;
commodityOrderList = new ArrayList();
}
private CommodityOrder findOrderById(int commodityId)
{
Iterator iter = commodityOrderList.iterator();
CommodityOrder order = null;
while (iter.hasNext())
{
CommodityOrder currentOrder = (CommodityOrder) iter.next();
if (currentOrder.getCommodityId() == commodityId)
{
order = currentOrder; break;
}
}
return order;
}
public List getCurrentCommoditiesList()
{
return commodityOrderList;
}
public boolean isEmpty()
{
return (commodityOrderList.size() == 0);
}
public void addCommodity(int commodityId)
{
CommodityOrder order = findOrderById(commodityId);
if (order == null)
{
// Creating new order
order = new CommodityOrder();
order.setCommodityId(commodityId);
order.setCommodityQuantity(1);
commodityOrderList.add(order);
}
else
{
order.setCommodityQuantity(order.getCommodityQuantity() + 1);
}
}
public void recalculateCommodity(int commodityId, int newCount)
{
CommodityOrder order = findOrderById(commodityId);
if (order == null)
{
if (newCount > 0)
{
// Creating new order
order = new CommodityOrder();
order.setCommodityId(commodityId);
order.setCommodityQuantity(1);
commodityOrderList.add(order);
}
}
else
{
if (newCount > 0)
{
order.setCommodityQuantity(newCount);
}
else
{
commodityOrderList.remove(order);
}
}
}
public void deleteCommodity(int commodityId)
{
recalculateCommodity(commodityId, 0);
}
public void processOrder() throws SQLException
{
if (dataSource == null)
throw new SQLException("Datasource not loaded");
Connection conn = dataSource.getConnection();
if (commodityOrderList.size() == 0)
return;
PreparedStatement p = conn.prepareStatement
("insert into " + "ORDERS(DATE_CREATED, USERNAME) " + "values (?, ?)");
p.setDate(1, new java.sql.Date(System.currentTimeMillis()));
p.setString(2, username);
p.executeUpdate();
p = conn.prepareStatement("select max(ID) from ORDERS where " + "USERNAME = ?");
p.setString(1, username);
ResultSet r = p.executeQuery();
int orderId = -1;
if (r.next())
{
orderId = r.getInt(1);
}
Iterator iter = commodityOrderList.iterator();
while (iter.hasNext())
{
CommodityOrder currentOrder = (CommodityOrder) iter.next();
p = conn.prepareStatement
("insert into COMMODITY_ORDER
(" + " ORDER_ID, COMMODITY_ID, QUANTITY) " + "values (?, ?, ?)");
p.setInt(1, orderId);
p.setInt(2, currentOrder.getCommodityId());
p.setInt(3, currentOrder.getCommodityQuantity());
p.execute();
}
commodityOrderList.clear(); conn.close();
}
public void ejbActivate() throws EJBException, RemoteException
{}
public void ejbPassivate() throws EJBException, RemoteException
{}
public void ejbRemove() throws EJBException, RemoteException
{}
public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException
{
try
{
InitialContext ctx = new InitialContext();
if (dataSource == null)
dataSource = (DataSource)
ctx.lookup("java:/OracleDS");
System.out.println("Data source aquired");
}
catch (NamingException ex)
{
throw new EJBException(ex);
}
}
}
Тестовый клиент для компонента ShoppingBasketBean создает один экземпляр компонента и делает тестовый заказ.
public class TestClient2
{
public static void main(String args[])
{
try
{
Context jndiContext = createJBossContext();
Object ref = jndiContext.lookup("ShoppingBasketBean");
ShoppingBasketHome home = (ShoppingBasketHome) PortableRemoteObject.narrow(ref, ShoppingBasketHome.class);
ShoppingBasketRemote remote = home.create("Boris");
System.out.println("Starting to work with the shop");
remote.addCommodity(2);
remote.addCommodity(3);
remote.addCommodity(4);
remote.processOrder();
}
catch (RemoteException e)
{
e.printStackTrace();
}
catch (NamingException e)
{
e.printStackTrace();
}
catch (CreateException e)
{
e.printStackTrace();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
public static Context createJBossContext() throws NamingException
{
Properties p = new Properties();
p.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
p.put("java.naming.provider.url", "jnp://127.0.0.1:1099");
p.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
}
В консоль данный тестовый клиент ничего не выводит, но можно посмотреть результат его работы, выполнив два запроса в базе данных в SQL*PLUS:
(рис 3.40) Новый заказ добавленРезультаты выполнения запросов показаны на Рис. 3.40. Как видно, в таблице ORDERS появилась информация о новом заказе, а в таблице COMMODITYORDERS - информация о составе заказа.
ejb-jar.xml. Дескрипторы session-type
будет стоять вместо .
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar id="ejb-jar ID" version="2.1" xmlns = "http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd"> <description>Beans for the Shopping Basket</description> <display-name>ShoppingBasketBean</display-name> <enterprise-beans> <session> <ejb-name>CommoditiesListBean</ejb-name> <home>shoppingBasket.CommoditiesListHome</home> <remote>shoppingBasket.CommoditiesListRemote</remote> <ejb-class>shoppingBasket.CommoditiesListBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> <session> <ejb-name>OrdersListBean</ejb-name> <home>shoppingBasket.OrdersListHome</home> <remote>shoppingBasket.OrdersListRemote</remote> <ejb-class>shoppingBasket.OrdersListBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> <session> <ejb-name>ShoppingBasketBean</ejb-name> <home>shoppingBasket.ShoppingBasketHome</home> <remote>shoppingBasket.ShoppingBasketRemote</remote> <ejb-class>shoppingBasket.ShoppingBasketBean</ejb-class> <session-type>Stateful</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> </ejb-jar>
В этом примере в качестве клиентского приложения будет выступать веб-приложение, написанное при помощи сервлетов и JSP.В данном пособии не ставиться цель научить с нуля программировать на сервлетах и JSP. Поэтому предполагается, что некоторые знания по этой теме у читателя уже есть.
Логика работы данного веб-приложения будет заключаться в следующем: пользователю отображается некоторая веб-страница, генерируемая при помощи JSP.После чего пользователь с этой веб-страницы может перейти на другую веб-страницу, либо послать данные на обработку некоторому сервлету (либо при помощи формы, либо просто посредством ссылки). Компоненты EJB хранятся в
контексте JSP -сессии (Session).Помимо этого в контексте сессии хранится имя пользователя, под которым он вошел в систему. Для любого веб-магазина характерны следующие страницы:
index.jsp.goodlist.jsp.basket.jsp.thankyou.jsp.prevorders.jsp.Помимо страниц, необходимы еще сервлеты, которые будут обрабатывать действия пользователя. Сервлеты необходимы для:
Помимо сервлетов, будет использоваться фильтр сервлетов. Если пользователь не вошел в магазин, этот фильтр будет перенаправлять его на страницу входа index.jsp, в случае если он вдруг попытается запросить какую-либо другую страницу или сервлет. Фильтр называется AuthFilter.
Рассмотрим страницу входа в магазин.
<%@ page language="java" contentType="text/html; charset=cp1251"
pageEncoding="cp1251"%>
<%String username = (String) session.getAttribute("username");
// Пользователь уже вошел в магазин - отправляем его на страницу
// со списком товаров
if (username != null) {
application.getRequestDispatcher("/goodlist.jsp").forward(request, response);
}
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=cp1251">
<title>Login</title>
</head>
<body>
// Данные отправляются сервлету LoginServlet. В URL-адресах ему поставлен
// в соответствие адрес, заканчивающийся на login.
// Стоит отметить, что мы не используем пароли для пользователей.
// Каждый волен входить под тем именем, которое ему нравится.
<form action="login" method="post"> <table>
<tr>
<td> Login:
</td> <td>
<input type="text" name="username"></input>
</td>
</tr> <tr>
<td>
<input type="SUBMIT" value="Login"></input> </td>
</tr> </table> </form>
</body>
</html>
(рис 3.41) Страница входа в системуСтраница входа в систему показана на Рис. 3.41.
Теперь рассмотрим сервлет входа в систему. Он обрабатывает только Р0SТ -запросы. Данный сервлет, получив данные формы, сохраняет имя пользователя в контексте сессии. Затем он получает удаленные интерфейсы трех ЕJB -компонентов и также сохраняет их в контексте сессии. Теперь они будут доступны на всех остальных страницах в магазине.
public class LoginServlet extends HttpServlet
{
private Context jndiContext = null;
private static final long serialVersionUID = 4562488849398632419L;
public LoginServlet()
{
super();
try
{
jndiContext = createJBossContext();
}
catch (NamingException e)
{
e.printStackTrace();
}
}
private static Context createJBossContext() throws NamingException
{
Properties p = new Properties();
p.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
p.put("java.naming.provider.url", "jnp://127.0.0.1:1099");
p.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces");
Context jndiContext = new InitialContext(p);
return jndiContext;
}
protected void doPost(HttpServletRequest req, HttpServletResponse res)
{
String loginName = req.getParameter("username");
HttpSession session = req.getSession();
// Если пользователь еще не вошел в магазин
if (session.getAttribute("username") == null)
{
// Сохраняем имя пользователя в контексте сессии
session.setAttribute("username", loginName);
Object ref;
try
{
// Получаем и сохраняем три удаленных интерфейса
// компонентов EJB в сессии.
ref = jndiContext.lookup("ShoppingBasketBean");
ShoppingBasketHome home = (ShoppingBasketHome) PortableRemoteObject.narrow
(ref, ShoppingBasketHome.class);
ShoppingBasketRemote remote = home.create(loginName);
session.setAttribute("basketbean", remote);
ref = jndiContext.lookup("CommoditiesListBean");
CommoditiesListHome homeCom = (CommoditiesListHome)
PortableRemoteObject.narrow(ref, CommoditiesListHome.class);
CommoditiesListRemote remoteCom = homeCom.create();
session.setAttribute("commoditiesbean", remoteCom);
ref = jndiContext.lookup("OrdersListBean");
OrdersListHome homeOrd = (OrdersListHome)
PortableRemoteObject.narrow(ref, OrdersListHome.class);
OrdersListRemote remoteOrd = homeOrd.create();
session.setAttribute("ordersbean", remoteOrd);
}
catch (NamingException e)
{
e.printStackTrace();
}
catch (CreateException e)
{
e.printStackTrace();
}
catch (RemoteException e)
{
e.printStackTrace();
}
}
// Отправляем пользователя на страницу с товарами
RequestDispatcher reqDispatcher = getServletContext().getRequestDispatcher("/goodlist.jsp");
try
{
reqDispatcher.forward(req, res);
}
catch (java.io.IOException e)
{
e.printStackTrace();
}
catch (javax.servlet.ServletException e)
{
e.printStackTrace();
}
}
}
Перейдем к странице с товарами. Здесь вся логика сводится к получению компонента CommoditiesListBean из контекста сессии, и отображения доступных товаров. Для каждого товара определена ссылка на сервлет добавления товара AddToCartServlet.В таблице COMMODITIESLIST у каждого объекта было определено такое поле как IMAGEPATH - в нем
хранится путь до изображения, которое соответствует товару. При помощи Google находим фотографии различных пачек сигарет и поместим их в каталог img.
<%@ page language="java" contentType="text/html;
charset=cp1251" pageEncoding="cp1251"%>
<%@page import="shoppingBasket.CommoditiesListRemote"%>
<%@page import="java.util.List"%>
<%@page import="java.util.Iterator"%>
<%@page import="dataObjects.Commodity"%>
<%@page import="java.text.NumberFormat"%>
<%@page import="java.util.Locale"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html> <head>
<meta http-equiv="Content-Type" content="text/html; charset=cp1251">
<title>Cigarette store</title>
</head>
<body>
<h1>Welcome to the finest internet cigarettes store!</h1>
<h2>Here is the list of commodities available for purchase</h2>
// Переход к корзине с товарами
<a href="basket.jsp">Goto Basket</a><br>
// Переход к предыдущим заказам
<a href="prevorders.jsp">View previous orders</a><br>
<table border="0" cellpadding="0" cellspacing="0">
<%
// Получаем удаленный интерфейс ComoditiesListBean
CommoditiesListRemote remote = (CommoditiesListRemote)
session.getAttribute("commoditiesbean");
List list = remote.getAvailableCommodities();
NumberFormat format = NumberFormat.getCurrencyInstance(Locale.US);
for (Iterator iter = list.iterator(); iter.hasNext();)
{ Commodity commodity = (Commodity) iter.next();
%>
<tr>
<td style="padding-left:10px; padding-right:10px;">
<%=commodity.getName()%>
</td>
<td style="padding-left:10px; padding-right:10px;">
<%=commodity.getDescription()%> </td>
<td style="padding-left:10px; padding-right:10px;">
<%=format.format(commodity.getPrice())%> </td>
// Ссылка на сервлет AddToServlet
<td style="padding-left:10px; padding-right:10px;"> <a
href="addtocart?good id=<%=commodity.getId()%>">Add to cart</a> </td>
<td style="padding-left:10px; padding-right:10px;">
<img src="<%=commodity.getImagePath()%>"/></td>
</tr>
<%
}
%>
</table>
// Переход к странице с корзиной для того, чтобы оформить заказ
<a href="basket.jsp">Make order</a><br>
// Выход из системы посредством LogoutServlet
<a href="logout">Logout</a>
</body> </html>
(рис 3.42) Доступные товарыСтраница с фотографиями товаров (сигарет) изображена на Рис. 3.42. Сервлет AddToCartServlet получает в качестве параметра ID товара, из контекста сессии берет удаленный интерфейс ShoppingListBean,а затем выполняет вызов addCommodity (goodId).
Стоит отметить, что ни сервлет, не компонент не проверяют факта существования товара с таким ID. Поэтому пользователь может "обмануть" магазин передав заведомо неправильный good_id в сервлет.
public class AddToCartServlet extends HttpServlet
{
private static final long serialVersionUID = -666156591413576322L;
public AddToCartServlet()
{
super();
}
protected void doPost(HttpServletRequest req, HttpServletResponse res)
{
doGet(req, res);
}
protected void doGet(HttpServletRequest req, HttpServletResponse res)
{
HttpSession session = req.getSession();
ShoppingBasketRemote remote = (ShoppingBasketRemote)
session.getAttribute("basketbean");
String strGoodId = req.getParameter("good id");
int goodId = 0;
try
{
goodId = Integer.parseInt(strGoodId);
}
catch (NumberFormatException e)
{
goodId = 0;
}
if (goodId > 0 remote != null)
{
try
{
remote.addCommodity(goodId);
}
catch (RemoteException e)
{
e.printStackTrace();
}
}
RequestDispatcher reqDispatcher = getServletContext().getRequestDispatcher("/goodlist.jsp");
try
{
reqDispatcher.forward(req, res);
}
catch (java.io.IOException e)
{
e.printStackTrace();
}
catch (javax.servlet.ServletException e)
{
e.printStackTrace();
}
}
}
Теперь рассмотрим страницу, в которой отображается содержимое корзины. На ней при помощи компонентов ShoppingBasketBean и CommoditiesListBean генерируется список из текущих товаров в корзине. Также снизу отображается суммарная стоимость товаров в заказе. Пользователь также может пересчитать количество каждого товара в корзине.
Либо удалить его совсем из корзины, введя "0" в соответствующее текстовое поле. Суммарная стоимость считается прямо на странице (что не очень хорошо, это будет сделано далее непосредственно в компоненте).
<%@ page language="java" contentType="text/html; charset=cp1251"
pageEncoding="cp1251"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%@page import="shoppingBasket.ShoppingBasketRemote"%>
<%@page import="java.util.List"%>
<%@page import="java.util.Iterator"%>
<%@page import="dataObjects.CommodityOrder"%>
<%@page import="shoppingBasket.CommoditiesListRemote"%>
<%@page import="dataObjects.Commodity"%>
<%@page import="java.text.NumberFormat"%>
<%@page import="java.util.Locale"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Shopping Basket</title>
</head>
<body>
<h1>Welcome to the finest internet cigarettes store!</h1>
<h2>This is the current state of the shopping basket</h2>
<a href="goodlist.jsp">Goto commodities list</a><br>
<a href="prevorders.jsp">View previous orders</a><br>
<table border="0" cellpadding="0" cellspacing="0">
<%
ShoppingBasketRemote remote =
(ShoppingBasketRemote) session.getAttribute("basketbean");
List commodities = remote.getCurrentCommoditiesList();
CommoditiesListRemote commRemote =
(CommoditiesListRemote) session.getAttribute("commoditiesbean");
NumberFormat format = NumberFormat.getCurrencyInstance(Locale.US);
double price = 0;
if (commodities.size() != 0)
{
for (Iterator iter = commodities.iterator(); iter.hasNext();)
{ // Getting the commodity order
CommodityOrder order = (CommodityOrder) iter.next();
Commodity commodity = commRemote.getCommodityById(order.getCommodityId());
// Подсчет суммарной стоимости заказа
price += commodity.getPrice() * order.getCommodityQuantity();
%><tr>
<!-Форма для запроса к сервлету, осуществляющему
пересчет товаров в корзине-->
<form action="recalculate" method="post">
<input type="hidden" name="good id"
value="<%=order.getCommodityId()%>">
</input> <td style="padding-left:10px; padding-right:10px;">
<%=commodity.getName()%></td>
<td style="padding-left:10px; padding-right:10px;">
<%=format.format(commodity.getPrice())%></td>
<td style="padding-left:10px; padding-right:10px;">
Amount: <input type="text" name="amount" value="
<%=order.getCommodityQuantity()%>"></input>
</td>
<td><input type="SUBMIT"
value="Recalculate"></input></td>
</form>
</tr>
<%
}
%>
<tr><td>The total price is <%=format.format(price)%>
</td></tr>
<%
} else {
%>
<!-Товаров в корзине нет ->
<tr><td><h3>No commodities in basket!</h3>
</td></tr>
<%
}
%>
</table>
<a href="makeorder">Make order</a><br>
<a href="logout">Logout</a><br>
</body>
</html>
(рис 3.43) Корзина с товарамиСтраница с корзиной изображена на Рис. 3.43.
RecalculateServlet вызывает метод recalculateCommodity(id, amount) с ID товара и новым количеством этого товара, после чего переходит обратно на страницу с корзиной. Опять таки, факт наличия товара с соответствующим ID нигде не проверяется.
public class RecalculateServlet extends HttpServlet
{
private static final long serialVersionUID = 4151274707994513136L;
public RecalculateServlet()
{
super();
}
public void doPost(HttpServletRequest req, HttpServletResponse res)
{
HttpSession session = req.getSession();
ShoppingBasketRemote remote =
(ShoppingBasketRemote) session.getAttribute("basketbean");
String strGoodId = req.getParameter("good id");
String strAmount = req.getParameter("amount");
int goodId = 0;
int amount = -1;
try
{
goodId = Integer.parseInt(strGoodId);
amount = Integer.parseInt(strAmount);
}
catch (NumberFormatException e)
{
goodId = 0;
amount = -1;
}
if (goodId > 0 remote != null amount >= 0)
{
try
{
remote.recalculateCommodity(goodId, amount);
}
catch (RemoteException e)
{
e.printStackTrace();
}
}
RequestDispatcher reqDispatcher = getServletContext().getRequestDispatcher("/basket.jsp");
try
{
reqDispatcher.forward(req, res);
}
catch (java.io.IOException e)
{
e.printStackTrace();
}
catch (javax.servlet.ServletException e)
{
e.printStackTrace();
}
}
}
MakeOrderServlet получаем удаленный интерфейс компонента ShoppingBasketBean и вызывает у него метод void processOrder(). После чего переходит на страницу с, где пользователя благодарят за покупки.
public class MakeOrderServlet extends HttpServlet
{
private static final long serialVersionUID = -171986817882942920L;
public MakeOrderServlet()
{
super();
}
public void doGet(HttpServletRequest req, HttpServletResponse res)
{
HttpSession session = req.getSession();
ShoppingBasketRemote remote = (ShoppingBasketRemote) session.getAttribute("basketbean");
if (remote != null)
{
try
{
remote.processOrder();
}
catch (RemoteException e)
{ e.printStackTrace();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
RequestDispatcher reqDispatcher =
getServletContext().getRequestDispatcher("/thankyou.jsp");
try
{
reqDispatcher.forward(req, res);
}
catch (java.io.IOException e)
{
e.printStackTrace();
}
catch (javax.servlet.ServletException e)
{
e.printStackTrace();
}
}
public void doPost(HttpServletRequest req, HttpServletResponse res)
{
doGet(req, res);
}
}
На странице благодарности за покупки расположении три ссылки:
<%@ page language="java" contentType="text/html; charset=cp1251" pageEncoding="cp1251"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=cp1251"> <title>Cigarette store</title> </head> <body> <h1>Thank you for making purchases in our store!</h1> <h2>From here you can: </h2> <ul> <!- Продолжить делать покупки --> <li>Continue shopping: <a href="goodlist.jsp">Goto shopping</a></li> <!- Просмотреть заказы --> <li>View your orders: <a href="prevorders.jsp">View orders</a></li> <!- Выход из магазина --> <li>Logout: <a href="logout">Logout</a></li> </ul> </body> </html>
(рис 3.44) Спасибо за покупкуСтраница с благодарностью за покупку изображена на Рис. 3.44."/>
Сервлет выхода их магазина убирает из контекста сессии все атрибуты (компоненты EJB и имя пользователя).
public class LogoutServlet extends HttpServlet
{
private static final long serialVersionUID = 4562488849398632419L;
public LogoutServlet()
{
super();
}
protected void doGet(HttpServletRequest req, HttpServletResponse res)
{
doPost(req, res);
}
protected void doPost(HttpServletRequest req, HttpServletResponse res)
{
HttpSession session = req.getSession();
session.removeAttribute("username");
session.removeAttribute("basketbean");
session.removeAttribute("commoditiesbean");
session.removeAttribute("ordersbean");
RequestDispatcher reqDispatcher =
getServletContext().getRequestDispatcher("/index.jsp");
try
{
reqDispatcher.forward(req, res);
}
catch (java.io.IOException e)
{
e.printStackTrace();
}
catch (javax.servlet.ServletException e)
{
e.printStackTrace();
}
}
}
На странице со всеми заказами, сделанными пользователем, используются две компоненты CommoditiesListBean и OrdersListBean.Посредством метода List getOrdersForUser (String username) компонента OrdersListBean сначала создается список из всех заказов пользователя,
после этого для каждого заказа посредством метода List getCommodityOrdersForOrder(int orderId) создается список из объектов CommodityOrder. Далее для каждого объекта CommodityOrder вызывается метод Commodity getCommodityById (int commodityId), и формируется описание товара в заказе. Стоимость заказа формируется способом, аналогичным способу на странице с корзиной.
<%@ page language="java" contentType="text/html; charset=cp1251"
pageEncoding="cp1251"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%@page import="java.util.List"%>
<%@page import="java.util.Iterator"%>
<%@page import="shoppingBasket.OrdersListRemote"%>
<%@page import="dataObjects.Order"%>
<%@page import="dataObjects.CommodityOrder"%>
<%@page import="shoppingBasket.CommoditiesListRemote"%>
<%@page import="dataObjects.Commodity"%>
<%@page import="java.text.NumberFormat"%>
<%@page import="java.util.Locale"%>
<tml> <head>
<meta http-equiv="Content-Type" content="text/html;
charset=ISO-8859-1">
<title>Previous Orders</title>
</head>
<body>
<h1>Welcome to the finest internet cigarettes store!</h1>
<h2>These are your orders</h2>
<a href="goodlist.jsp">Goto commodities list</a><br>
<a href="basket.jsp">Goto Basket</a><br>
<table border="0" cellpadding="0" cellspacing="0">
<%
OrdersListRemote remote = (OrdersListRemote) session.getAttribute("ordersbean");
String username = (String) session.getAttribute("username");
CommoditiesListRemote commRemote =
(CommoditiesListRemote) session.getAttribute("commoditiesbean");
NumberFormat format = NumberFormat.getCurrencyInstance(Locale.US);
// Получаем список заказов для пользователя
List orders = remote.getOrdersForUser(username);
if (orders.size() != 0)
{
for (Iterator iter = orders.iterator(); iter.hasNext(); )
{
// Getting the commodity order
Order order = (Order) iter.next();
// Для каждого заказа получаем список товаров в нем
List commodityOrders = remote.getCommodityOrdersForOrder(order.getId());
double price = 0;
%><tr>
<td>Order done on: <%=order.getDate()%></td>
</tr>
<tr>
<td style="padding-left: 20px;">
<ul>
<%for (Iterator iter2 = commodityOrders.iterator();
iter2.hasNext();)
{ CommodityOrder cOrder = (CommodityOrder) iter2.next();
// Получаем сам товар с описанием
Commodity commodity = commRemote.getCommodityById(cOrder.getCommodityId());
price += commodity.getPrice() * cOrder.getCommodityQuantity();
%>//Выводим информацию о товаре
<li>Commodity: <%=commodity.getName()%>, Price:
<%=format.format(commodity.getPrice()) %>, Quantity:
<%=cOrder.getCommodityQuantity()%> </li>
<%
}
%>
</ul> </td>
</tr> <tr>
<td style="padding-left: 10px;">Total price is:
<%=format.format(price)%>
</td>
</tr> <tr>
<td style="padding-top: 20px;"></td>
</tr>
<%
}
} else {
%>
<tr>
<td>
<h3>No orders!</h3>
</td>
</tr>
<%
}
%>
</table>
<a href="logout">Logout</a>
</body>
</html>
(рис 3.45) Страница с заказами Страница с заказами изображена на Рис. 3.45.
Фильтр в данном веб-приложении один - он осуществляет контроль над несанкционированным доступом к персональным страницам. Проще говоря, если пользователь не ввел свое имя, то этот фильтр на даст ему возможности открыть страницу basket.jsp, prevorder.jsp, thankyou.jsp, goodlist.jsp а также все сервлеты, кроме сервлета входа в магазин LoginServlet.
Делает он это просто проверяя наличие атрибута с именем пользователя в контексте сессии.
public class AuthFilter implements Filter
{
private FilterConfig fc;
public void init(FilterConfig filterConfig) throws ServletException
{
fc = filterConfig;
}
public void destroy()
{
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (req.getSession().getAttribute("username") == null)
{
// Имя пользователя не обнаружено
RequestDispatcher reqDispatcher = fc.getServletContext().
getRequestDispatcher("/index.jsp");
try
{
reqDispatcher.forward(req, res);
}
catch (java.io.IOException e)
{
e.printStackTrace();
}
catch (javax.servlet.ServletException e)
{
e.printStackTrace();
}
}
else
{
// Все в порядке, страница или сервлет доступен
chain.doFilter(req, res);
}
}
}
Файл конфигурации web.xml описывает все сервлеты и фильтры, используемые в веб-приложении, а также прописывает URL -адреса привязки сервлетов и фильтров. Помимо этого задаются названия файлов, открываемых по умолчанию ( index.html, index.jsp и т.д.).
<?xml version="1.0" encoding="UTF-8"?> <web-app id="WebApp ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2 0 01/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app 2 4.xsd"> <display-name> ShoppingBasketWebClient </display-name> <!- Список имен файлов, открывающихся по умолчанию --> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <servlet> <description>The servlet used to login</description> <display-name>LoginServlet</display-name> <servlet-name>LoginServlet</servlet-name> <servlet-class>webclient.servlets.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping> <servlet> <description>The servlet used to add items to cart</description> <display-name>AddToCartServlet</display-name> <servlet-name>AddToCartServlet</servlet-name> <servlet-class>webclient.servlets.AddToCartServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>AddToCartServlet</servlet-name> <url-pattern>/addtocart</url-pattern> </servlet-mapping> <servlet> <description> The servlet used to recalculate items in cart </description> <display-name>RecalculateServlet</display-name> <servlet-name>RecalculateServlet</servlet-name> <servlet-class> webclient.servlets.RecalculateServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>RecalculateServlet</servlet-name> <url-pattern>/recalculate</url-pattern> </servlet-mapping> <servlet> <description>The servlet used to make order</description> <display-name>MakeOrderServlet</display-name> <servlet-name>MakeOrderServlet</servlet-name> <servlet-class>webclient.servlets.MakeOrderServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>MakeOrderServlet</servlet-name> <url-pattern>/makeorder</url-pattern> </servlet-mapping> <servlet> <description>The servlet used to logout</description> <display-name>LogoutServlet</display-name> <servlet-name>LogoutServlet</servlet-name> <servlet-class>webclient.servlets.LogoutServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LogoutServlet</servlet-name> <url-pattern>/logout</url-pattern> </servlet-mapping> <filter> <filter-name>AuthFilter</filter-name> <filter-class>webclient.filter.AuthFilter</filter-class> </filter> <filter-mapping> <filter-name>AuthFilter</filter-name> <servlet-name>AddToCartServlet</servlet-name> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <servlet-name>LogoutServlet</servlet-name> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <servlet-name>MakeOrderServlet</servlet-name> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <servlet-name>RecalculateServlet</servlet-name> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/basket.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/goodlist.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/prevorders.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/thankyou.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/img/</url-pattern> </filter-mapping> </web-app>
Для компонентов EJB следует создать проект EJB,аналогичный проектам из предыдущих примеров. Назовем его ShoppingBasketBean.Для тестовых примеров создаются обычные Java -проекты, к ним еще подключаются библиотеки jbossall-client.jar и jboss-j2ee.jar.
(рис 3.46) Динамический веб-проектДля веб-приложения, следует выбрать тип проекта Web -> Dynamic Web Project (Рис. 3.46). Далее на следующем окне в качестве Target Runtime выбираем JBoss 4.0,в EAR (Enterprise Application Project) пока не добавляем (можно, конечно, было вначале создать EAR -проект -это ничего не меняет).
После этого будет создан проект для динамического веб-приложение - то что нам и нужно.
(рис 3.47) Структура веб-приложенияВ проекте будут созданы два основных каталога - src и WebContent. В src следует помещать все Java -классы, используемые в веб-приложении - сервлеты, фильтры и другие классы. Фильтр поместим в пакет webclient.filter, а сервлеты в пакет webclient. (Рис. 3.47). JSP страницы и
файл конфигурации веб-приложения web.xml помещаюся в каталог WebContent. Помимо этого в этом каталоге создадим каталог img, в котором поместим фотографии пачек сигарет, доступных к продаже в магазине. Страницы JSP помещаем в каталог WebContent, фотографии сигарет в каталог img, файл web.xml в каталог WEB-INF.
Теперь осталось создать EAR -проект - это такой тип проекта, в который можно подключить несколько различных проектов EJB,а также разнообразные клиентские проекты, после этого при развертке этого проекта, он также корректно развернет все проекты, которые к нему подключены (по крайней мере так предполагается). Создаем проект J2EE -> Enterprise Application Project. Опять в качестве Target Runtime выбираем JBoss 4.0.
(рис 3.48) Подключение проектов к EAR-проектуНа последнем этапе следует подключить в EAR -проекту два наших проекта - EJB и веб-проект (Рис. 3.48).
Теперь, когда все проекты готовы, следую подключить EAR -проект к JBoss ( Servers -> JBoss -> Add and Remove Projects ), причем входящие в него проекты подключать не надо, иначе будет ошибка.
Если все было сделано правильно, то при запуске JBoss оба проекта будут корректно развернуты на сервере JBoss, а веб-приложение будет доступно по адресу http : //localhost :8080/ ShoppingBasketWebClient (последняя часть URL соответствует имени проекта). Скриншоты веб-приложения уже приводились при обсуждении страниц JSP и здесь они приводиться не будут.
Последний пример уже был достаточно сложен. Возможность отладки приложения становится все нужнее. К счастью Eclipse- дает возможность как отладки компонентов, развернутых на сервере приложений, так и отладки JSP -страниц и сервлетов. Для этого надо при запуске сервера приложений (в нашем случае JBoss) запустить его не при помощи команды Run, а при помощи команды Debug.
(рис 3.49) Запуск сервера в режиме DebugЗапуск сервера в режиме Debug показан на Рис. 3.49. После этого можно выполнять отладку как JSP -страниц, так и компонентов.
(рис 50) Установка breakpoint'aBreakpoint как для JSP -страниц, так и для Java -классов, устанавливается посредством щелчка правой кнопкой мыши рядом со строчкой кода. В меню можно выбрать операцию с breakpoint - установить, удалить, сделать неактивным. Теперь перед выполнением этой строки кода будет произведена остановка.
Отлаживать лучше в Debug -перспективе - Window -> Open perspective -> Debug. В этой перспективе есть все возможности для отладки - Watch, Breakpoints, вычисление различных выражений и т.д. Для более подробного изучения процесса отладки в Eclipse следует обратиться к документации по нему.
У примера можно выделить несколько недостатков. Перечислим некоторые из них:
Commodity
CommodityListBean.getCommodityByld (int id)) - это крайне неэкономично;Commodity CommodityListBean.getCommodityByld(int id)) - тоже неэкономично;SQL -запросы выполняются непосредственно из кода Эти недостатки будут, по возможности, исправлены в примере из следующего раздела. В нем рассматриваются объектные компоненты, которые позволяют сильно упростить разработку
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.