Кросс-платформенные и многозвенные технологии

Технология Enterprise Java Beans. Часть 1

Разбить на страницы
Показывать лекцию целиком

История и необходимость появления EJB

В данном разделе будут рассмотрены сеансовые (Session),объектные (Entity) и управляемые сообщениями (Message-driven) компоненты (Beans) EJB.Все примеры разработаны в среде Windows XP SP2.Несмотря на это, все используемые компоненты являются кросс-платформенными, так что примеры должны работать так же в Linux, Solaris или Mac OS X.Для демонстрации примеров необходимо следующее ПО:

  • Java 5.0 (http ://java.sun.com/);
  • Сервер приложений (контейнер EJB ) - открытый сервер приложений JBoss (http : //www .jboss. org/);
  • СУБД - Oracle Database 9i.
  • Среда разработки - Eclipse-WTP (Eclipse Web Tools Platform).Это модифицированная версия среды разработки Eclipse, в которую добавлена широкая поддержка веб-приложений и EJB. Скачать ее можно также на сайте Eclipse - http://www. eclipse. org/.
  • Для Eclipse-WTP следует скачать еще плагин XDoclet (http : //xdoclet . sourceforge.net/). Он используется при развертывании объектных компонентов (Entity Beans) EJB в базе данных.
  • Для примеров web -приложений, взаимодействующих через EJB с базой данных будут использоваться веб-сервер Apache (http : //www . apache.org/), и контейнер JSP Tomcat (http://tomcat. apache. org/).
  • Общее описание архитектуры EJB

    Компонент Enterprise Bean

    Серверные компоненты Enterprise Java Beans бывают трех принципиально разных типов:

  • объектные компоненты (Entity Beans; точный перевод с английского -компоненты-сущности);
  • сеансовые компоненты (Session Beans) и
  • компоненты, управляемые сообщениями (Message-driven Beans).
  • Сеансовые и объектные компоненты являются серверными компонентами, основанными на RMI,к которым можно получить доступ при помощи распределенных объектных протоколов. Компоненты, управляемые сообщениями, появившиеся в EJB 2.0 - это асинхронные серверные компоненты, которые отвечают на асинхронные сообщения JMS и других протоколов.

    Хорошее эмпирическое правило при конструировании компонентов EJB состоит в том, что объектные компоненты моделируют прикладные понятия, которые могут быть выражены существительными. Например, объектный компонент может представлять покупателя, часть оборудования, элемент инвентаря или даже место. Другими словами, объектные компоненты моделируют объекты реального мира. Эти объекты являются обычными постоянными записями в какой-либо базе данных.

    Сеансовые компоненты являются расширениями клиентского приложения и отвечают за управление процессами и задачами. Для примера рассмотрим задачу о проектировании системы управления отелем. В качестве объектных компонентов в отеле могут выступать "Комната", "Постоялец", "Член персонала". Сеансовые компоненты отвечают за взаимодействие этих элементов и предназначены для управления различными видами деятельности, такими как заказ мест в гостинице. Сеансовый компонент "Бронирование места" может использовать компоненты "Постоялец" и "Комната", чтобы сделать заказ. Точно так же компоненты, управляемые сообщениями, отвечают за координацию задач, включающих другие сеансовые и объектные компоненты. Главное отличие между компонентами, управляемыми сообщениями, и сеансовыми компонентами состоит в том, как к ним осуществляется доступ. В то время как сеансовый компонент предоставляет удаленный интерфейс, определяющий, какие методы могут быть вызваны, компонент, управляемый сообщениями, этого не делает. Вместо этого он подписывается или прослушивает определенные асинхронные сообщения, обрабатывает их и управляет действиями, которые выполняют другие компоненты в ответ на эти сообщения. Например, компонент "Обработчик заказов", управляемый сообщениями, мог бы принимать асинхронные сообщения, возможно из существующей системы заказов, с помощью которых он бы мог координировать взаимодействие компонентов "Постоялец" и "Комната", для того, чтобы забронировать место в отеле.

    Действия, которые принимают сеансовые компоненты и компоненты, управляемые сообщениями, по своей сути являются кратковременными: вы начали делать заказ, вы сделали часть работы, и на этом их действие заканчивается. Сеансовые компоненты и компоненты, управляемые сообщениями не соответствуют никаким объектам в базе данных. Понятно, что в процессе своей работы они оказывают влияние на базу данных, но через объектные компоненты. По крайней мере, такого подхода придерживаются при разработке больших распределенных приложений. В принципе, ничто не мешает из сеансового компонента сделать подключение к базе данных при помощи JDBC,и произвести необходимые операции с базой данных. Такой способ работы с базой данных через EJB будет рассмотрен далее в примерах.

    Главное различие компонентов в том, что объектные компоненты имеют устойчивое состояние, а сеансовые компоненты и компоненты, управляемые сообщениями, моделируют взаимодействия и устойчивого состояния не имеют.

    Классы и интерфейсы

    Для реализации объектного или сеансового компонента необходимо определить:

  • интерфейсы компонента,
  • класс компонента и
  • первичный ключ (только для объектных компонентов).
  • В данной главе будут сначала рассмотрены примеры сеансовых компонентов, разработанные в соответствии со спецификацией EJB 2.1.Компонент может иметь следующие интерфейсы:

  • Удаленный интерфейс (Remote Interface) определяет прикладные методы компонента, доступные из приложений, внешних по отношению к контейнеру EJB,то есть прикладные методы, которые компонент представляет внешним приложениям. Удаленный интерфейс расширяет (extends) интерфейс javax.ejb.EJBObject.Внешние по отношению к EJB приложения вызывают методы удаленного интерфейса, а получают эти методы они при помощи домашнего интерфейса (Home Interface).
  • Домашний интерфейс (Home Interface) определяет методы жизненного цикла компонента, доступные из приложений, внешних по отношению к контейнеру EJB,то есть методы создания новых компонентов, удаления и поиска существующих компонентов. Домашний интерфейс (Home Interface) расширяет (extends) интерфейс javax.ejb.EJBHome. Для того чтобы создать новый EJB или обратиться к существующему EJB,внешнее приложение должно сначала вызвать соответствующий метод create () его домашнего интерфейса (Home Interface).
  • Локальный интерфейс (Local Interface) определяет прикладные методы EJB, которые могут использоваться другими компонентами, размещенными в том же контейнере EJB,но не внешними по отношению к контейнеру EJB приложениями. Он позволяет компонентам, находящимся в одном контейнере EJB,взаимодействовать без дополнительных затрат, связанных с применением протокола распределенных объектов, что повышает их производительность. Локальный интерфейс расширяет (extends) интерфейс javax.ejb.EJBLocalObject.Внешние по отношению к EJB компоненты, находящиеся в том же самом контейнере, работают с методами локального интерфейса, а получают эти методы они при помощи локального домашнего интерфейса (Local Home Interface)
  • Локальный домашний интерфейс (Local Home Interface) определяет методы жизненного цикла компонента, доступные из компонентов, находящихся в том же контейнер EJB,то есть методы создания новых компонентов, удаления и поиска существующих компонентов. Это позволяет компонентам взаимодействовать без дополнительных затрат, связанных с использованием протокола распределенных объектов, что улучшает их производительность. Домашний интерфейс (Home Interface) расширяет (extends) интерфейс javax.ejb.EJBLocalHome.Для того чтобы создать новый EJB или обратиться к существующему EJB,; компонент, находящийся в том же контейнере EJB,должен сначала вызвать соответствующий метод его домашнего интерфейса (Home Interface).
  • Важным отличием между удаленным и локальным интерфейсом является то, что вызовы методов локального интерфейса не используют RMI и не генерируют RemoteException.Для корректной работы не обязательно предоставлять классу компонента все четыре интерфейса. К примеру, если компонент не используется никаким клиентским приложением (а нужен только другим компонентам, работающим под управлением того же EJB контейнера), то нет необходимости предоставлять удаленный интерфейс. То есть в зависимости от тех задач, которые решает данный компонент, ему может быть предоставлен либо локальные, либо удаленные, либо и те и другие интерфейсы.

  • Для реализации компонентов, помимо интерфейсов, необходимо реализовать также
  • Класс компонента.Классы сеансовых и объектных компонентов фактически реализуют прикладные методы и методы жизненного цикла компонента. Заметим, что класс компонента для сеансовых и объектных компонентов не реализует непосредственно (implements в терминах Java) ни один интерфейс компонента, однако он должен содержать методы, названия и списки параметров которых совпадают с названиями и списками параметров методов, определенных в соответствующих интерфейсах. Также этот класс должен содержать метод (или методы) ejbCreate(), соответствующие некоторым методам в обоих, домашнем (Home Interface) и локальном домашнем (Local Home Interface),интерфейсах. Объектные компоненты должны реализовывать интерфейс javax.ejb.EntityBeanсеансовые компоненты - интерфейс javax.ejb.SessionBean.Компоненты, управляемые сообщениями, не используют ни один из интерфейсов компонентов, потому что к ним никогда не осуществляется доступ посредством вызовов методов из других приложений или компонентов. Вместо этого компоненты, управляемые сообщениями, содержат один метод onMessage (). Этот метод вызывается при прибытии нового сообщения.
  • Первичный ключ.Это простой класс, предоставляющий указатель внутри базы данных. Первичный ключ необходим только объектным компонентам. Он должен реализовывать интерфейс java.io.Serializable.
  • У компонентов, управляемых сообщениями нет никаких компонентных интерфейсов, но, несмотря на это, они могут быть клиентами любых сеансовых или объектных компонентов и взаимодействовать с этими компонентами через их интерфейсы. Объектные и сеансовые компоненты, с которыми взаимодействует компонент, управляемый сообщениями, могут быть совмещенными, в этом случае он работает с их локальными компонентными интерфейсами, или они могут располагаться в разных адресных пространствах или контейнерах EJB - в этом случае используются удаленные интерфейсы. Спецификации EJB 1.1 и старше определяют удаленные (Remote) интерфейсы компонента в терминах протокола Java RMI-IIOP,который обеспечивает совместимость с CORBA.Для совместимости с типами Java RMI-IIOP поставщики EJB должны ограничить определение интерфейсов и параметров типами, соответствующими CORBA IIOP 1.2 (в соответствии со спецификацией CORBA 2.3.1).Локальные интерфейсы в EJB 2.0, EJB 2.1 и EJB 3.0 не являются интерфейсами Java RMI и не обязаны поддерживать IIOP 1.2 и использовать типы совместимые с протоколом Java RMI-IIOPJava RMI-IIOP определены три базовых типа объектов:

  • byte, boolean, char, int, long, short, double, float ;
  • объекты, реализующие интерфейс java .io.Serializable ;
  • удаленные типы Java RMI - классы реализующие интерфейс java.rmi.RemoteException.
  • При попытке передать через удаленный метод объект, не относящийся к одному из трех типов, будет сгенерировано исключение java.rmi.RemoteException. Каждый метод в удаленном интерфейсе (Remote Interface) должен генерировать исключение java.rmi.Remote. Оно генерируется при проблемах с передачей данных в сети или невозможности найти требуемый сервер. Помимо этого разработчики могут использовать любые другие исключения при разработке компонент EJB.

    Протокол Java RMI IIOP также накладывает ограничения на удаленные интерфейсы. Этих ограничений много, но реально используется только два из этих ограничений.

  • Сериализуемые объекты (Serializable) не должны реализовывать (implement) интерфейс java.rmi.Remote.
  • Удаленный интерфейс не может реализовывать два и более интерфейса, у которых есть методы с одинаковыми именами. Удаленный интерфейс может перегружать (overload) свои собственные методы и расширять (extends) удаленный интерфейс с перегруженными (overloaded) методами.
  • Существует много инструментальных средств, которые позволяют сильно упростить работу с EJB.В частности одним из средств, которое также предоставляет платформа Eclipse-WTP,является средство создания связей между объектными компонентами и базой данных. В Eclipse-WTP это делается при помощи пакета XDoclet.Каким образом это делается, будет рассмотрено далее.

    Сеансовые компоненты

    Сеансовые компоненты могут быть с состоянием (Stateful) и без состояния (Stateless).Компоненты с состоянием при использовании клиентом поддерживают состояния диалога (Conversational state).Состояние диалога не заносится в базу данных, оно сохраняется в памяти все время, пока клиент использует сессию. Состояние сеансового компонента может измениться при вызове любого его метода, и это изменение может привести к последующим вызовам методов. Состояние диалога поддерживается только до тех пор, пока клиентское приложение активно использует компонент. Если клиент отключается или освобождает компонент, его состояние теряется навсегда. Сеансовый компонент с состоянием не может использоваться несколькими клиентами. Он выделяется для одного клиента на все время своей жизни.

    Сеансовые компоненты без состояния (Stateless Session Bean) ни поддерживают никакого состояния диалога. Каждый метод полностью независим и пользуется только данными, переданными через его параметры. Сеансовые компоненты без состояния (Stateless Session Bean) имеют более высокую производительность, с точки зрения пропускной способности и потребления ресурсов, чем объектные компоненты и сеансовые компоненты с состоянием (Stateful Session Bean),поскольку для обслуживания большого количества клиентов требуется всего несколько экземпляров сеансового компонента без состояния. При создании сеансового компонента без состояния (Stateless Session Bean) контейнер EJB выполняет следующую последовательность действии:

  • При помощи метода Class.newInstance () создается экземпляр класса компонента.
  • Вызывается метод SessionBean.setSessionContext( SessionContext context). При помощи этого метода экземпляру компонента передается ссылка на объект класса EJBContext, предоставляющий интерфейс для взаимодействия с контейнером EJB (в частности, в этом объекте есть метод lookup(), позволяющий искать другие объекты по путям JNDI).
  • Вызывается метод ejbCreate (). У сеансовых компонентов без состояния (Stateless Session Bean) этот метод не имеет параметров. В этом методе компонент может создать подключения к базам данных и к другим ресурсам.
  • Контейнер помещает созданный компонент в пул готовых компонентов.
  • При запуске контейнер обычно создает несколько экземпляров сеансовых компонентов без состояния (Stateless Session Bean).При необходимости контейнер может либо создавать новые экземпляры, либо удалять ненужные. При удалении компонента из пула готовых компонентов вызывается метод ejbRemove(). В этом методе следует закрыть все открытые подключения к различным ресурсам.

    То, каким образом и когда контейнер EJB должен создавать и удалять экземпляры сеансовых компонентов без состояния, не описывается спецификацией EJB. В каждом из существующих контейнеров EJB этот механизм реализован по-разному.

    Сеансовые компоненты с состоянием создаются тогда, когда клиент запрашивает метод create() у домашнего интерфейса (Home Interface).После этого созданный компонент переходит в состояние готовых методов. В этом состоянии он готов выполнять свои методы, вызываемые клиентом через удаленную ссылку. Компонент в это время может хранить открытые ресурсы (базы данных, TCP/IP сессии и др.), а также хранить посредством своих переменных определенное состояние, которое может изменяться при вызове клиентом различных его методов. Последовательность методов, вызываемых контейнером при создании сеансового компонента с состоянием аналогична последовательности методов при создании сеансового компонента без состояния. Только компонент в конце переходит в состояние готовых методов, а не в пул готовых компонентов.

    При нахождении компонента в состоянии готовых методов, он может либо оставаться в этом состоянии, либо переведен в пассивное состояние (при этом будет вызван метод ejbPassivate () - см. далее), либо быть удаленным. То, при каких условиях компонент будет переведен в пассивное состояние, в каждом из существующих контейнеров определяется по-разному. При активизации компонента (вызывается метод ejbActivate () - см. далее), компонент переводится из пассивного состояние в состояние готовых методом. При этом значение всех внутренних переменных и состояние диалога сохраняется.

    Компонент может быть удален при вызове соответствующего метода remove() домашнего интерфейса, либо контейнером после определенного промежутка времени, в течение которого компонент "бездействовал". Компонент не может быть удален при выполнении транзакции. При удалении у компонента будет вызван метод ejbRemove().

    Пример "Точное время"

    Компонент

    Разработаем сеансовый компонент без состояния (Stateless Session Bean),который в ответ на запрос будет возвращать текущее серверное время. Назовем компонент CorrectTimeBean.Вначале рассмотрим исходный код компонента и консольного клиента, при помощи которого мы будет узнавать точное время сервера, а затем покажем, как можно реализовать данный пример при помощи среды разработки Eclipse-WTP.Все классы и интерфейсы, относящиеся непосредственно к компоненту, размещены в пакете 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 (). Для сеансового компонента без состояния (Stateless Session Bean) этот метод вызывается всего один раз - непосредственно при создании его сервером. Когда клиент при помощи домашнего интерфейса (Home Interface) получает ссылку на объект сеансового компонента без состояния, метод ejbCreate() не вызывается.
  • void ejbActivate() throws EJBException, RemoteException. Этот метод используется для сеансовых компонентов с состоянием (Stateful Session Beans) и объектных компонентов (Entity Beans)сеансовых компонентах без состояния (Stateless Session Beans) не используется.
  • void ejbPassivate() throws EJBException, RemoteException. Этот метод используется для сеансовых компонентов с состоянием и объектных компонентов. В сеансовых компонентах без состояния (Stateless Session Beans) не используется.
  • 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 -дескриптор развертывания компонента (Deployment descriptor).Компоненты бывают различных типов, могут взаимодействовать со службой безопасности, транзакциями, системой имен и другими общими для распределенных объектных систем сервисами. Контейнер EJB управляет компонентами автоматически, но ему необходимо предоставить дополнительную информацию о каждом компоненте. Для этого используются дескрипторы развертывания. Изначально в EJB 1.0 в качестве дескрипторов использовались специальные классы, потом эти классы были заменены XML -файлами. Для EJB 2.1 дескриптор развертывания для CorrectTimeBean выглядит следующим образом:

    <?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> - этот элемент представляет собой идентификатор, который могут использовать некоторые программы, работающие с дескриптором развертывания (Eclipse, JBuilder, IntelliJ Idea).
  • <enterprise-beans> - содержит объявления всех компонентов, описываемых этим дескриптором развертывания.
  • <session> - в этом элементе описывается сеансовый компонент (Session Bean).
  • <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 -совместимую службу каталогов. При помощи 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,запущенного на локальной машине. После установления JNDI -соединения и получения контекста из метода createJBossContext () этот контекст можно использовать для поиска внутреннего объекта CorrectTimeBean.

    Object ref = jndiContext.lookup("CorrectTimeBean");

    В нашем клиентском приложении используется метод

    PortableRemoteObject.narrow(); CorrectTimeHome home = (CorrectTimeHome)

    PortableRemoteObject.narrow(ref, CorrectTimeHome.class);

    Имя, по которому выполняется поиск внутреннего объекта компонента CorrectTimeBean,задается во время развертывания. Оно может совпадать с именем компонента в XML- дескрипторе, либо быть чем-то совершенно другим.

    Получив удаленную ссылку на внутренний объект, мы может использовать ее для создания нового объекта компонента CorrectTimeBean.

    CorrectTimeRemote remote = home.create();

    На самом деле, как уже говорилось раньше, сеансовый компонент без состояния не обязательно создается при вызове create() - у контейнера EJB есть пул уже готовых компонентов без состояния. На вызов create() он просто возвращает один из них. Теперь мы можем вызвать метод obtainCorrectTime (), в результате чего будет вызван через RMI соответствующий метод у компонента из контейнера EJB.

    Date remoteDate = remote.obtainCorrectTime();

    Создание проекта для компонента в Eclipse-WTP

    Все готово, чтобы реализовать код в проекте 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) Создание файла дескриптора развертывания

    Теперь необходимо задать дескриптор развертывания (Deployment descriptor).Для этого в каталоге META-INF каталога с исходными кодами создаем файл ejb-jar.xml - дескриптор развертывания компонента (Рис. 3.11).

    (рис 3.12) Файл дескриптора компонента

    Дескриптор развертывания компонента создан (Рис. 3.12).Помимо этого файла в каталог META-INF следует положить файл MANIFEST.MF - для того, чтобы потом использовать его при создании 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 -скрипта для сборки компонента, клиента, и размещению компонента на контейнере JBossEclipse-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) Компонент развернут

    Если развертывание прошло успешно, то в конце загрузки появится название компонента и его JNDI -имя (Рис. 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).

    Создание проекта при помощи дополнительных средств Eclipse-WTP

    В предыдущем разделе был рассмотрено создание проекта и его развертывание только при помощи средства 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 в каталог ejbModuleXML -дескриптор в файл 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).

    Пример "Конвертор валют"

    Компонент

    В данном примере мы покажем разработку сеансового компонента без состояния (Stateless Session Bean) для конвертации денежных сумм из одной валюте в другую. В качестве валют будут использоваться доллары, евро и йены. Компонент будет переводить сумму в долларах в сумму в евро или йенах.

    Удаленный интерфейс

    В удаленном интерфейсе (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 создаем интерфейсы и классы, вставляем дескриптор развертывания. После этого добавляем проект к контейнеру JBoss.

    (рис 3.26) Теперь их два

    Если все было сделано правильно - то при запуске JBoss появится сообщение об успешном развертывании двух компонентов (компонента из предыдущего примера и текущего компонента) (Рис. 3.26).

    Создание проекта для клиентского приложения

    Теперь напишем небольшое приложение, которое будет работать с компонентом CurrencyConvertorBean.Это будет графическое приложение, в котором пользователь сможет ввести наименования валют, и сумму денег в одной валюте. После этого приложение выведет сумму денег в другой валюте.

    Вначале создадим Java -проект и назовем его CurrencyConvertorBeanClient.Аналогичным образом, как было описано в предыдущем примере, подключим необходимые библиотеки и выберем в качестве зависимого проекта CurrencyConvertorBean.Далее создадим пакет, а в нем - класс клиента.

    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/jca установочного каталога 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;
    	}
    }

    Компоненты

    Для реализации заявленной функциональности будут использоваться три сеансовых компонента EJB: CommoditiesListBean, OrdersListBean и ShoppingBasketBean. CommoditiesListBean и OrdersListBean являются сеансовыми компонентами без состояния, а ShoppingBasketBean - компонентом с состоянием (stateful).

    CommoditiesListBean используется для поиска товара в базе данных по ID товара, а также для получения всех товаров в базе данных.

    OrdersListBean используется для составления списка сделанных заказов по имени пользователя, а также для получения списка товаров, входящих в заказ, описываемый при помощи ID.

    ShoppingBasketBean используется для реализации логики корзины в интернет магазине. При помощи него можно добавлять товары в корзину, пересчитывать количество товаров и делать заказ.

    Удаленный интерфейс CommoditiesListBean

    В удаленном интерфейсе (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;
    }

    Домашний интерфейс класса CommoditiesListBean

    Этот интерфейс аналогичен домашним интерфейсам из предыдущих примеров.

    public interface CommoditiesListHome extends EJBHome
    {
    public CommoditiesListRemote create()   throws RemoteException, CreateException;
    }

    Класс компонента CommoditiesListBean

    В классе компонента заданы два основных метода - 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

    Для того чтобы проверить работоспособность компонента 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.

    Удаленный интерфейс OrdersListBean

    В удаленном интерфейсе определим два метода:

  • 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;
    }

    Домашний интерфейс OrdersListBean

    Этот интерфейс аналогичен домашним интерфейсам из предыдущих примеров.

    public interface OrdersListHome extends EJBHome  
    {
    public OrdersListRemote create()   throws RemoteException,   CreateException;
    }

    Класс компонента OrdersListBean

    Выполнение запроса к базе данных и формирование объектов 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

    Для того чтобы проверить работоспособность компонента 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.

    Удаленный интерфейс ShoppingBasketBean

    Интерфейс этого сеансового компонента с состоянием отражает те возможные опреации, которые пользователь может производить с корзиной. Эти методы перечислены далее:

  • 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;
    }

    Домашний интерфейс OrdersListBean

    Этот интерфейс отличается от домашних интерфейсов предыдущих компонентов. Компонент ShoppingBasketBean создается отдельно для каждого пользователя, поэтому удобно в него передать имя пользователя. Имя пользователя будет использоваться при выполнении запроса в методе processOrder (). В метод create(String username) домашнего интерфейса передается имя пользователя.

    public interface ShoppingBasketHome extends EJBHome  
    {
    public ShoppingBasketRemote create(String username)   throws RemoteException,   CreateException;
    
    }

    Класс компонента ShoppingBasketBean

    Компонент 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

    Тестовый клиент для компонента 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:

  • select * from ORDERS;
  • select * from COMMODITY_ORDER.
  • (рис 3.40) Новый заказ добавлен

    Результаты выполнения запросов показаны на Рис. 3.40. Как видно, в таблице ORDERS появилась информация о новом заказе, а в таблице COMMODITYORDERS - информация о составе заказа.

    Дескрипторы развертывания

    Дескрипторы развертывания определены для трех компонентов в одном файле ejb-jar.xml. Дескрипторы сеансовых компонентов без состояния CommoditiesListBean и OrdersListBean ничем не отличаются от дескрипторов развертывания компонентов из предыдущих примеров. У компонента ShoppingBasketBean в элементе session-type будет стоять вместо Stateless- Stateful.

    <?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.
  • Помимо страниц, необходимы еще сервлеты, которые будут обрабатывать действия пользователя. Сервлеты необходимы для:

  • Добавления товара в корзину, сервлет AddToCartServlet.
  • Пересчета товаров в корзине, сервлет RecalculateServlet.
  • Выполнения заказа, сервлет MakeOrderServlet.
  • Входа в систему, сервлет LoginServlet.
  • Выхода из системы, сервлет LogoutServlet.
  • Помимо сервлетов, будет использоваться фильтр сервлетов. Если пользователь не вошел в магазин, этот фильтр будет перенаправлять его на страницу входа 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 - в нем хранится путь до изображения, которое соответствует товару. При помощи Googletrade; находим фотографии различных пачек сигарет и поместим их в каталог 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.0EAR (Enterprise Application Project) пока не добавляем (можно, конечно, было вначале создать EAR -проект -это ничего не меняет). После этого будет создан проект для динамического веб-приложение - то что нам и нужно.

    (рис 3.47) Структура веб-приложения

    В проекте будут созданы два основных каталога - src и WebContent. В src следует помещать все Java -классы, используемые в веб-приложении - сервлеты, фильтры и другие классы. Фильтр поместим в пакет webclient.filter, а сервлеты в пакет webclient.servlets (Рис. 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-WTP дает возможность как отладки компонентов, развернутых на сервере приложений, так и отладки JSP -страниц и сервлетов. Для этого надо при запуске сервера приложений (в нашем случае JBoss) запустить его не при помощи команды Run, а при помощи команды Debug.

    (рис 3.49) Запуск сервера в режиме Debug

    Запуск сервера в режиме Debug показан на Рис. 3.49. После этого можно выполнять отладку как JSP -страниц, так и компонентов.

    (рис 50) Установка breakpoint'a

    Breakpoint как для JSP -страниц, так и для Java -классов, устанавливается посредством щелчка правой кнопкой мыши рядом со строчкой кода. В меню можно выбрать операцию с breakpoint - установить, удалить, сделать неактивным. Теперь перед выполнением этой строки кода будет произведена остановка.

    Отлаживать лучше в Debug -перспективе - Window -> Open perspective -> Debug. В этой перспективе есть все возможности для отладки - Watch, Breakpoints, вычисление различных выражений и т.д. Для более подробного изучения процесса отладки в Eclipse следует обратиться к документации по нему.

    Недостатки данного примера

    У примера можно выделить несколько недостатков. Перечислим некоторые из них:

  • Отсутствие дизайна веб-приложения - это в курсе по веб-дизайну, либо посетите сайт студии Артемия Лебедева http://www. artlebedev.ru - там все написано;
  • Подсчет стоимости заказа вынесен на страницу JSP - это плохо, так как страница JSP отвечает, по-хорошему, только за представление информации, а бизнес-логика, свойственная компонентам EJB,в ней должна отсутствовать;
  • При добавлении товара в корзину или запроса содержимого заказа не проверяется факт существования товара или заказа в базе данных;
  • При формировании страницы со всеми заказами, на каждый товар идет запрос к базе данных (посредством метода Commodity CommodityListBean.getCommodityByld (int id)) - это крайне неэкономично;
  • При формировании корзины с товарами, на каждый товар опять запрос к базе данных (посредством метода Commodity CommodityListBean.getCommodityByld(int id)) - тоже неэкономично;
  • Нет возможности отследить, что кто-то вошел в систему под одним именем с различных компьютеров;
  • SQL -запросы выполняются непосредственно из кода сеансовых компонентов, что не очень удобно.
  • Эти недостатки будут, по возможности, исправлены в примере из следующего раздела. В нем рассматриваются объектные компоненты, которые позволяют сильно упростить разработку

    Страницы:

    История и необходимость появления EJB

    В данном разделе будут рассмотрены сеансовые (Session),объектные (Entity) и управляемые сообщениями (Message-driven) компоненты (Beans) EJB.Все примеры разработаны в среде Windows XP SP2.Несмотря на это, все используемые компоненты являются кросс-платформенными, так что примеры должны работать так же в Linux, Solaris или Mac OS X.Для демонстрации примеров необходимо следующее ПО:

  • Java 5.0 (http ://java.sun.com/);
  • Сервер приложений (контейнер EJB ) - открытый сервер приложений JBoss (http : //www .jboss. org/);
  • СУБД - Oracle Database 9i.
  • Среда разработки - Eclipse-WTP (Eclipse Web Tools Platform).Это модифицированная версия среды разработки Eclipse, в которую добавлена широкая поддержка веб-приложений и EJB. Скачать ее можно также на сайте Eclipse - http://www. eclipse. org/.
  • Для Eclipse-WTP следует скачать еще плагин XDoclet (http : //xdoclet . sourceforge.net/). Он используется при развертывании объектных компонентов (Entity Beans) EJB в базе данных.
  • Для примеров web -приложений, взаимодействующих через EJB с базой данных будут использоваться веб-сервер Apache (http : //www . apache.org/), и контейнер JSP Tomcat (http://tomcat. apache. org/).
  • Общее описание архитектуры EJB

    Компонент Enterprise Bean

    Серверные компоненты Enterprise Java Beans бывают трех принципиально разных типов:

  • объектные компоненты (Entity Beans; точный перевод с английского -компоненты-сущности);
  • сеансовые компоненты (Session Beans) и
  • компоненты, управляемые сообщениями (Message-driven Beans).
  • Сеансовые и объектные компоненты являются серверными компонентами, основанными на RMI,к которым можно получить доступ при помощи распределенных объектных протоколов. Компоненты, управляемые сообщениями, появившиеся в EJB 2.0 - это асинхронные серверные компоненты, которые отвечают на асинхронные сообщения JMS и других протоколов.

    Хорошее эмпирическое правило при конструировании компонентов EJB состоит в том, что объектные компоненты моделируют прикладные понятия, которые могут быть выражены существительными. Например, объектный компонент может представлять покупателя, часть оборудования, элемент инвентаря или даже место. Другими словами, объектные компоненты моделируют объекты реального мира. Эти объекты являются обычными постоянными записями в какой-либо базе данных.

    Сеансовые компоненты являются расширениями клиентского приложения и отвечают за управление процессами и задачами. Для примера рассмотрим задачу о проектировании системы управления отелем. В качестве объектных компонентов в отеле могут выступать "Комната", "Постоялец", "Член персонала". Сеансовые компоненты отвечают за взаимодействие этих элементов и предназначены для управления различными видами деятельности, такими как заказ мест в гостинице. Сеансовый компонент "Бронирование места" может использовать компоненты "Постоялец" и "Комната", чтобы сделать заказ. Точно так же компоненты, управляемые сообщениями, отвечают за координацию задач, включающих другие сеансовые и объектные компоненты. Главное отличие между компонентами, управляемыми сообщениями, и сеансовыми компонентами состоит в том, как к ним осуществляется доступ. В то время как сеансовый компонент предоставляет удаленный интерфейс, определяющий, какие методы могут быть вызваны, компонент, управляемый сообщениями, этого не делает. Вместо этого он подписывается или прослушивает определенные асинхронные сообщения, обрабатывает их и управляет действиями, которые выполняют другие компоненты в ответ на эти сообщения. Например, компонент "Обработчик заказов", управляемый сообщениями, мог бы принимать асинхронные сообщения, возможно из существующей системы заказов, с помощью которых он бы мог координировать взаимодействие компонентов "Постоялец" и "Комната", для того, чтобы забронировать место в отеле.

    Действия, которые принимают сеансовые компоненты и компоненты, управляемые сообщениями, по своей сути являются кратковременными: вы начали делать заказ, вы сделали часть работы, и на этом их действие заканчивается. Сеансовые компоненты и компоненты, управляемые сообщениями не соответствуют никаким объектам в базе данных. Понятно, что в процессе своей работы они оказывают влияние на базу данных, но через объектные компоненты. По крайней мере, такого подхода придерживаются при разработке больших распределенных приложений. В принципе, ничто не мешает из сеансового компонента сделать подключение к базе данных при помощи JDBC,и произвести необходимые операции с базой данных. Такой способ работы с базой данных через EJB будет рассмотрен далее в примерах.

    Главное различие компонентов в том, что объектные компоненты имеют устойчивое состояние, а сеансовые компоненты и компоненты, управляемые сообщениями, моделируют взаимодействия и устойчивого состояния не имеют.

    Классы и интерфейсы

    Для реализации объектного или сеансового компонента необходимо определить:

  • интерфейсы компонента,
  • класс компонента и
  • первичный ключ (только для объектных компонентов).
  • В данной главе будут сначала рассмотрены примеры сеансовых компонентов, разработанные в соответствии со спецификацией EJB 2.1.Компонент может иметь следующие интерфейсы:

  • Удаленный интерфейс (Remote Interface) определяет прикладные методы компонента, доступные из приложений, внешних по отношению к контейнеру EJB,то есть прикладные методы, которые компонент представляет внешним приложениям. Удаленный интерфейс расширяет (extends) интерфейс javax.ejb.EJBObject.Внешние по отношению к EJB приложения вызывают методы удаленного интерфейса, а получают эти методы они при помощи домашнего интерфейса (Home Interface).
  • Домашний интерфейс (Home Interface) определяет методы жизненного цикла компонента, доступные из приложений, внешних по отношению к контейнеру EJB,то есть методы создания новых компонентов, удаления и поиска существующих компонентов. Домашний интерфейс (Home Interface) расширяет (extends) интерфейс javax.ejb.EJBHome. Для того чтобы создать новый EJB или обратиться к существующему EJB,внешнее приложение должно сначала вызвать соответствующий метод create () его домашнего интерфейса (Home Interface).
  • Локальный интерфейс (Local Interface) определяет прикладные методы EJB, которые могут использоваться другими компонентами, размещенными в том же контейнере EJB,но не внешними по отношению к контейнеру EJB приложениями. Он позволяет компонентам, находящимся в одном контейнере EJB,взаимодействовать без дополнительных затрат, связанных с применением протокола распределенных объектов, что повышает их производительность. Локальный интерфейс расширяет (extends) интерфейс javax.ejb.EJBLocalObject.Внешние по отношению к EJB компоненты, находящиеся в том же самом контейнере, работают с методами локального интерфейса, а получают эти методы они при помощи локального домашнего интерфейса (Local Home Interface)
  • Локальный домашний интерфейс (Local Home Interface) определяет методы жизненного цикла компонента, доступные из компонентов, находящихся в том же контейнер EJB,то есть методы создания новых компонентов, удаления и поиска существующих компонентов. Это позволяет компонентам взаимодействовать без дополнительных затрат, связанных с использованием протокола распределенных объектов, что улучшает их производительность. Домашний интерфейс (Home Interface) расширяет (extends) интерфейс javax.ejb.EJBLocalHome.Для того чтобы создать новый EJB или обратиться к существующему EJB,; компонент, находящийся в том же контейнере EJB,должен сначала вызвать соответствующий метод его домашнего интерфейса (Home Interface).
  • Важным отличием между удаленным и локальным интерфейсом является то, что вызовы методов локального интерфейса не используют RMI и не генерируют RemoteException.Для корректной работы не обязательно предоставлять классу компонента все четыре интерфейса. К примеру, если компонент не используется никаким клиентским приложением (а нужен только другим компонентам, работающим под управлением того же EJB контейнера), то нет необходимости предоставлять удаленный интерфейс. То есть в зависимости от тех задач, которые решает данный компонент, ему может быть предоставлен либо локальные, либо удаленные, либо и те и другие интерфейсы.

  • Для реализации компонентов, помимо интерфейсов, необходимо реализовать также
  • Класс компонента.Классы сеансовых и объектных компонентов фактически реализуют прикладные методы и методы жизненного цикла компонента. Заметим, что класс компонента для сеансовых и объектных компонентов не реализует непосредственно (implements в терминах Java) ни один интерфейс компонента, однако он должен содержать методы, названия и списки параметров которых совпадают с названиями и списками параметров методов, определенных в соответствующих интерфейсах. Также этот класс должен содержать метод (или методы) ejbCreate(), соответствующие некоторым методам в обоих, домашнем (Home Interface) и локальном домашнем (Local Home Interface),интерфейсах. Объектные компоненты должны реализовывать интерфейс javax.ejb.EntityBeanсеансовые компоненты - интерфейс javax.ejb.SessionBean.Компоненты, управляемые сообщениями, не используют ни один из интерфейсов компонентов, потому что к ним никогда не осуществляется доступ посредством вызовов методов из других приложений или компонентов. Вместо этого компоненты, управляемые сообщениями, содержат один метод onMessage (). Этот метод вызывается при прибытии нового сообщения.
  • Первичный ключ.Это простой класс, предоставляющий указатель внутри базы данных. Первичный ключ необходим только объектным компонентам. Он должен реализовывать интерфейс java.io.Serializable.
  • У компонентов, управляемых сообщениями нет никаких компонентных интерфейсов, но, несмотря на это, они могут быть клиентами любых сеансовых или объектных компонентов и взаимодействовать с этими компонентами через их интерфейсы. Объектные и сеансовые компоненты, с которыми взаимодействует компонент, управляемый сообщениями, могут быть совмещенными, в этом случае он работает с их локальными компонентными интерфейсами, или они могут располагаться в разных адресных пространствах или контейнерах EJB - в этом случае используются удаленные интерфейсы. Спецификации EJB 1.1 и старше определяют удаленные (Remote) интерфейсы компонента в терминах протокола Java RMI-IIOP,который обеспечивает совместимость с CORBA.Для совместимости с типами Java RMI-IIOP поставщики EJB должны ограничить определение интерфейсов и параметров типами, соответствующими CORBA IIOP 1.2 (в соответствии со спецификацией CORBA 2.3.1).Локальные интерфейсы в EJB 2.0, EJB 2.1 и EJB 3.0 не являются интерфейсами Java RMI и не обязаны поддерживать IIOP 1.2 и использовать типы совместимые с протоколом Java RMI-IIOPJava RMI-IIOP определены три базовых типа объектов:

  • byte, boolean, char, int, long, short, double, float ;
  • объекты, реализующие интерфейс java .io.Serializable ;
  • удаленные типы Java RMI - классы реализующие интерфейс java.rmi.RemoteException.
  • При попытке передать через удаленный метод объект, не относящийся к одному из трех типов, будет сгенерировано исключение java.rmi.RemoteException. Каждый метод в удаленном интерфейсе (Remote Interface) должен генерировать исключение java.rmi.Remote. Оно генерируется при проблемах с передачей данных в сети или невозможности найти требуемый сервер. Помимо этого разработчики могут использовать любые другие исключения при разработке компонент EJB.

    Протокол Java RMI IIOP также накладывает ограничения на удаленные интерфейсы. Этих ограничений много, но реально используется только два из этих ограничений.

  • Сериализуемые объекты (Serializable) не должны реализовывать (implement) интерфейс java.rmi.Remote.
  • Удаленный интерфейс не может реализовывать два и более интерфейса, у которых есть методы с одинаковыми именами. Удаленный интерфейс может перегружать (overload) свои собственные методы и расширять (extends) удаленный интерфейс с перегруженными (overloaded) методами.
  • Существует много инструментальных средств, которые позволяют сильно упростить работу с EJB.В частности одним из средств, которое также предоставляет платформа Eclipse-WTP,является средство создания связей между объектными компонентами и базой данных. В Eclipse-WTP это делается при помощи пакета XDoclet.Каким образом это делается, будет рассмотрено далее.

    Сеансовые компоненты

    Сеансовые компоненты могут быть с состоянием (Stateful) и без состояния (Stateless).Компоненты с состоянием при использовании клиентом поддерживают состояния диалога (Conversational state).Состояние диалога не заносится в базу данных, оно сохраняется в памяти все время, пока клиент использует сессию. Состояние сеансового компонента может измениться при вызове любого его метода, и это изменение может привести к последующим вызовам методов. Состояние диалога поддерживается только до тех пор, пока клиентское приложение активно использует компонент. Если клиент отключается или освобождает компонент, его состояние теряется навсегда. Сеансовый компонент с состоянием не может использоваться несколькими клиентами. Он выделяется для одного клиента на все время своей жизни.

    Сеансовые компоненты без состояния (Stateless Session Bean) ни поддерживают никакого состояния диалога. Каждый метод полностью независим и пользуется только данными, переданными через его параметры. Сеансовые компоненты без состояния (Stateless Session Bean) имеют более высокую производительность, с точки зрения пропускной способности и потребления ресурсов, чем объектные компоненты и сеансовые компоненты с состоянием (Stateful Session Bean),поскольку для обслуживания большого количества клиентов требуется всего несколько экземпляров сеансового компонента без состояния. При создании сеансового компонента без состояния (Stateless Session Bean) контейнер EJB выполняет следующую последовательность действии:

  • При помощи метода Class.newInstance () создается экземпляр класса компонента.
  • Вызывается метод SessionBean.setSessionContext( SessionContext context). При помощи этого метода экземпляру компонента передается ссылка на объект класса EJBContext, предоставляющий интерфейс для взаимодействия с контейнером EJB (в частности, в этом объекте есть метод lookup(), позволяющий искать другие объекты по путям JNDI).
  • Вызывается метод ejbCreate (). У сеансовых компонентов без состояния (Stateless Session Bean) этот метод не имеет параметров. В этом методе компонент может создать подключения к базам данных и к другим ресурсам.
  • Контейнер помещает созданный компонент в пул готовых компонентов.
  • При запуске контейнер обычно создает несколько экземпляров сеансовых компонентов без состояния (Stateless Session Bean).При необходимости контейнер может либо создавать новые экземпляры, либо удалять ненужные. При удалении компонента из пула готовых компонентов вызывается метод ejbRemove(). В этом методе следует закрыть все открытые подключения к различным ресурсам.

    То, каким образом и когда контейнер EJB должен создавать и удалять экземпляры сеансовых компонентов без состояния, не описывается спецификацией EJB. В каждом из существующих контейнеров EJB этот механизм реализован по-разному.

    Сеансовые компоненты с состоянием создаются тогда, когда клиент запрашивает метод create() у домашнего интерфейса (Home Interface).После этого созданный компонент переходит в состояние готовых методов. В этом состоянии он готов выполнять свои методы, вызываемые клиентом через удаленную ссылку. Компонент в это время может хранить открытые ресурсы (базы данных, TCP/IP сессии и др.), а также хранить посредством своих переменных определенное состояние, которое может изменяться при вызове клиентом различных его методов. Последовательность методов, вызываемых контейнером при создании сеансового компонента с состоянием аналогична последовательности методов при создании сеансового компонента без состояния. Только компонент в конце переходит в состояние готовых методов, а не в пул готовых компонентов.

    При нахождении компонента в состоянии готовых методов, он может либо оставаться в этом состоянии, либо переведен в пассивное состояние (при этом будет вызван метод ejbPassivate () - см. далее), либо быть удаленным. То, при каких условиях компонент будет переведен в пассивное состояние, в каждом из существующих контейнеров определяется по-разному. При активизации компонента (вызывается метод ejbActivate () - см. далее), компонент переводится из пассивного состояние в состояние готовых методом. При этом значение всех внутренних переменных и состояние диалога сохраняется.

    Компонент может быть удален при вызове соответствующего метода remove() домашнего интерфейса, либо контейнером после определенного промежутка времени, в течение которого компонент "бездействовал". Компонент не может быть удален при выполнении транзакции. При удалении у компонента будет вызван метод ejbRemove().

    Пример "Точное время"

    Компонент

    Разработаем сеансовый компонент без состояния (Stateless Session Bean),который в ответ на запрос будет возвращать текущее серверное время. Назовем компонент CorrectTimeBean.Вначале рассмотрим исходный код компонента и консольного клиента, при помощи которого мы будет узнавать точное время сервера, а затем покажем, как можно реализовать данный пример при помощи среды разработки Eclipse-WTP.Все классы и интерфейсы, относящиеся непосредственно к компоненту, размещены в пакете 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 (). Для сеансового компонента без состояния (Stateless Session Bean) этот метод вызывается всего один раз - непосредственно при создании его сервером. Когда клиент при помощи домашнего интерфейса (Home Interface) получает ссылку на объект сеансового компонента без состояния, метод ejbCreate() не вызывается.
  • void ejbActivate() throws EJBException, RemoteException. Этот метод используется для сеансовых компонентов с состоянием (Stateful Session Beans) и объектных компонентов (Entity Beans)сеансовых компонентах без состояния (Stateless Session Beans) не используется.
  • void ejbPassivate() throws EJBException, RemoteException. Этот метод используется для сеансовых компонентов с состоянием и объектных компонентов. В сеансовых компонентах без состояния (Stateless Session Beans) не используется.
  • 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 -дескриптор развертывания компонента (Deployment descriptor).Компоненты бывают различных типов, могут взаимодействовать со службой безопасности, транзакциями, системой имен и другими общими для распределенных объектных систем сервисами. Контейнер EJB управляет компонентами автоматически, но ему необходимо предоставить дополнительную информацию о каждом компоненте. Для этого используются дескрипторы развертывания. Изначально в EJB 1.0 в качестве дескрипторов использовались специальные классы, потом эти классы были заменены XML -файлами. Для EJB 2.1 дескриптор развертывания для CorrectTimeBean выглядит следующим образом:

    <?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> - этот элемент представляет собой идентификатор, который могут использовать некоторые программы, работающие с дескриптором развертывания (Eclipse, JBuilder, IntelliJ Idea).
  • <enterprise-beans> - содержит объявления всех компонентов, описываемых этим дескриптором развертывания.
  • <session> - в этом элементе описывается сеансовый компонент (Session Bean).
  • <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 -совместимую службу каталогов. При помощи 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,запущенного на локальной машине. После установления JNDI -соединения и получения контекста из метода createJBossContext () этот контекст можно использовать для поиска внутреннего объекта CorrectTimeBean.

    Object ref = jndiContext.lookup("CorrectTimeBean");

    В нашем клиентском приложении используется метод

    PortableRemoteObject.narrow(); CorrectTimeHome home = (CorrectTimeHome)

    PortableRemoteObject.narrow(ref, CorrectTimeHome.class);

    Имя, по которому выполняется поиск внутреннего объекта компонента CorrectTimeBean,задается во время развертывания. Оно может совпадать с именем компонента в XML- дескрипторе, либо быть чем-то совершенно другим.

    Получив удаленную ссылку на внутренний объект, мы может использовать ее для создания нового объекта компонента CorrectTimeBean.

    CorrectTimeRemote remote = home.create();

    На самом деле, как уже говорилось раньше, сеансовый компонент без состояния не обязательно создается при вызове create() - у контейнера EJB есть пул уже готовых компонентов без состояния. На вызов create() он просто возвращает один из них. Теперь мы можем вызвать метод obtainCorrectTime (), в результате чего будет вызван через RMI соответствующий метод у компонента из контейнера EJB.

    Date remoteDate = remote.obtainCorrectTime();

    Создание проекта для компонента в Eclipse-WTP

    Все готово, чтобы реализовать код в проекте 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) Создание файла дескриптора развертывания

    Теперь необходимо задать дескриптор развертывания (Deployment descriptor).Для этого в каталоге META-INF каталога с исходными кодами создаем файл ejb-jar.xml - дескриптор развертывания компонента (Рис. 3.11).

    (рис 3.12) Файл дескриптора компонента

    Дескриптор развертывания компонента создан (Рис. 3.12).Помимо этого файла в каталог META-INF следует положить файл MANIFEST.MF - для того, чтобы потом использовать его при создании 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 -скрипта для сборки компонента, клиента, и размещению компонента на контейнере JBossEclipse-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) Компонент развернут

    Если развертывание прошло успешно, то в конце загрузки появится название компонента и его JNDI -имя (Рис. 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).

    Создание проекта при помощи дополнительных средств Eclipse-WTP

    В предыдущем разделе был рассмотрено создание проекта и его развертывание только при помощи средства 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 в каталог ejbModuleXML -дескриптор в файл 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).

    Пример "Конвертор валют"

    Компонент

    В данном примере мы покажем разработку сеансового компонента без состояния (Stateless Session Bean) для конвертации денежных сумм из одной валюте в другую. В качестве валют будут использоваться доллары, евро и йены. Компонент будет переводить сумму в долларах в сумму в евро или йенах.

    Удаленный интерфейс

    В удаленном интерфейсе (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 создаем интерфейсы и классы, вставляем дескриптор развертывания. После этого добавляем проект к контейнеру JBoss.

    (рис 3.26) Теперь их два

    Если все было сделано правильно - то при запуске JBoss появится сообщение об успешном развертывании двух компонентов (компонента из предыдущего примера и текущего компонента) (Рис. 3.26).

    Создание проекта для клиентского приложения

    Теперь напишем небольшое приложение, которое будет работать с компонентом CurrencyConvertorBean.Это будет графическое приложение, в котором пользователь сможет ввести наименования валют, и сумму денег в одной валюте. После этого приложение выведет сумму денег в другой валюте.

    Вначале создадим Java -проект и назовем его CurrencyConvertorBeanClient.Аналогичным образом, как было описано в предыдущем примере, подключим необходимые библиотеки и выберем в качестве зависимого проекта CurrencyConvertorBean.Далее создадим пакет, а в нем - класс клиента.

    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/jca установочного каталога 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;
    	}
    }

    Компоненты

    Для реализации заявленной функциональности будут использоваться три сеансовых компонента EJB: CommoditiesListBean, OrdersListBean и ShoppingBasketBean. CommoditiesListBean и OrdersListBean являются сеансовыми компонентами без состояния, а ShoppingBasketBean - компонентом с состоянием (stateful).

    CommoditiesListBean используется для поиска товара в базе данных по ID товара, а также для получения всех товаров в базе данных.

    OrdersListBean используется для составления списка сделанных заказов по имени пользователя, а также для получения списка товаров, входящих в заказ, описываемый при помощи ID.

    ShoppingBasketBean используется для реализации логики корзины в интернет магазине. При помощи него можно добавлять товары в корзину, пересчитывать количество товаров и делать заказ.

    Удаленный интерфейс CommoditiesListBean

    В удаленном интерфейсе (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;
    }

    Домашний интерфейс класса CommoditiesListBean

    Этот интерфейс аналогичен домашним интерфейсам из предыдущих примеров.

    public interface CommoditiesListHome extends EJBHome
    {
    public CommoditiesListRemote create()   throws RemoteException, CreateException;
    }

    Класс компонента CommoditiesListBean

    В классе компонента заданы два основных метода - 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

    Для того чтобы проверить работоспособность компонента 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.

    Удаленный интерфейс OrdersListBean

    В удаленном интерфейсе определим два метода:

  • 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;
    }

    Домашний интерфейс OrdersListBean

    Этот интерфейс аналогичен домашним интерфейсам из предыдущих примеров.

    public interface OrdersListHome extends EJBHome  
    {
    public OrdersListRemote create()   throws RemoteException,   CreateException;
    }

    Класс компонента OrdersListBean

    Выполнение запроса к базе данных и формирование объектов 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

    Для того чтобы проверить работоспособность компонента 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.

    Удаленный интерфейс ShoppingBasketBean

    Интерфейс этого сеансового компонента с состоянием отражает те возможные опреации, которые пользователь может производить с корзиной. Эти методы перечислены далее:

  • 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;
    }

    Домашний интерфейс OrdersListBean

    Этот интерфейс отличается от домашних интерфейсов предыдущих компонентов. Компонент ShoppingBasketBean создается отдельно для каждого пользователя, поэтому удобно в него передать имя пользователя. Имя пользователя будет использоваться при выполнении запроса в методе processOrder (). В метод create(String username) домашнего интерфейса передается имя пользователя.

    public interface ShoppingBasketHome extends EJBHome  
    {
    public ShoppingBasketRemote create(String username)   throws RemoteException,   CreateException;
    
    }

    Класс компонента ShoppingBasketBean

    Компонент 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

    Тестовый клиент для компонента 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:

  • select * from ORDERS;
  • select * from COMMODITY_ORDER.
  • (рис 3.40) Новый заказ добавлен

    Результаты выполнения запросов показаны на Рис. 3.40. Как видно, в таблице ORDERS появилась информация о новом заказе, а в таблице COMMODITYORDERS - информация о составе заказа.

    Дескрипторы развертывания

    Дескрипторы развертывания определены для трех компонентов в одном файле ejb-jar.xml. Дескрипторы сеансовых компонентов без состояния CommoditiesListBean и OrdersListBean ничем не отличаются от дескрипторов развертывания компонентов из предыдущих примеров. У компонента ShoppingBasketBean в элементе session-type будет стоять вместо Stateless- Stateful.

    <?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.
  • Помимо страниц, необходимы еще сервлеты, которые будут обрабатывать действия пользователя. Сервлеты необходимы для:

  • Добавления товара в корзину, сервлет AddToCartServlet.
  • Пересчета товаров в корзине, сервлет RecalculateServlet.
  • Выполнения заказа, сервлет MakeOrderServlet.
  • Входа в систему, сервлет LoginServlet.
  • Выхода из системы, сервлет LogoutServlet.
  • Помимо сервлетов, будет использоваться фильтр сервлетов. Если пользователь не вошел в магазин, этот фильтр будет перенаправлять его на страницу входа 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 - в нем хранится путь до изображения, которое соответствует товару. При помощи Googletrade; находим фотографии различных пачек сигарет и поместим их в каталог 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.0EAR (Enterprise Application Project) пока не добавляем (можно, конечно, было вначале создать EAR -проект -это ничего не меняет). После этого будет создан проект для динамического веб-приложение - то что нам и нужно.

    (рис 3.47) Структура веб-приложения

    В проекте будут созданы два основных каталога - src и WebContent. В src следует помещать все Java -классы, используемые в веб-приложении - сервлеты, фильтры и другие классы. Фильтр поместим в пакет webclient.filter, а сервлеты в пакет webclient.servlets (Рис. 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-WTP дает возможность как отладки компонентов, развернутых на сервере приложений, так и отладки JSP -страниц и сервлетов. Для этого надо при запуске сервера приложений (в нашем случае JBoss) запустить его не при помощи команды Run, а при помощи команды Debug.

    (рис 3.49) Запуск сервера в режиме Debug

    Запуск сервера в режиме Debug показан на Рис. 3.49. После этого можно выполнять отладку как JSP -страниц, так и компонентов.

    (рис 50) Установка breakpoint'a

    Breakpoint как для JSP -страниц, так и для Java -классов, устанавливается посредством щелчка правой кнопкой мыши рядом со строчкой кода. В меню можно выбрать операцию с breakpoint - установить, удалить, сделать неактивным. Теперь перед выполнением этой строки кода будет произведена остановка.

    Отлаживать лучше в Debug -перспективе - Window -> Open perspective -> Debug. В этой перспективе есть все возможности для отладки - Watch, Breakpoints, вычисление различных выражений и т.д. Для более подробного изучения процесса отладки в Eclipse следует обратиться к документации по нему.

    Недостатки данного примера

    У примера можно выделить несколько недостатков. Перечислим некоторые из них:

  • Отсутствие дизайна веб-приложения - это в курсе по веб-дизайну, либо посетите сайт студии Артемия Лебедева http://www. artlebedev.ru - там все написано;
  • Подсчет стоимости заказа вынесен на страницу JSP - это плохо, так как страница JSP отвечает, по-хорошему, только за представление информации, а бизнес-логика, свойственная компонентам EJB,в ней должна отсутствовать;
  • При добавлении товара в корзину или запроса содержимого заказа не проверяется факт существования товара или заказа в базе данных;
  • При формировании страницы со всеми заказами, на каждый товар идет запрос к базе данных (посредством метода Commodity CommodityListBean.getCommodityByld (int id)) - это крайне неэкономично;
  • При формировании корзины с товарами, на каждый товар опять запрос к базе данных (посредством метода Commodity CommodityListBean.getCommodityByld(int id)) - тоже неэкономично;
  • Нет возможности отследить, что кто-то вошел в систему под одним именем с различных компьютеров;
  • SQL -запросы выполняются непосредственно из кода сеансовых компонентов, что не очень удобно.
  • Эти недостатки будут, по возможности, исправлены в примере из следующего раздела. В нем рассматриваются объектные компоненты, которые позволяют сильно упростить разработку

    Вернуться к учебному плану