Главная » Java, JavaBeans » Пример cart EJB

0

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

Пример cart использует session statefule-Компонент.

Session stateful-Компонент

Для поддержки таких компонентов, Контейнер EJB использует высокопроизводительную архитектуру с использованием кеширования.

Создаются два основных пула объектов -"геас1у"-пул и пассивный пул. Если происходит тайм-аут, то компоненты могут перемещаться из первого во второй. Перемещение компонента в пассивный пул приводит к сохранению его состояния в управляемой Контейнером EJB Java-БД.

Файлы примера cart

Пример cart содержит несколько различных файлов. В этом разделе мы будем обращать внимание либо на те файлы, которые вы создаете сами, либо на те, которые иллюстрируют некоторые особенности session- компонентов. Некоторые из файлов, которые находятся в каталоге cart, сгенерированы автоматически (стабы, скелеты и другие CORBA-файлы) и не рассматриваются здесь.

Главным образом, мы будем изучать следующие файлы:

•          Ноте-интерфейс Компонента, CartHome.java. Этот файл содержит определение Ноте-интерфейса для Компонента CartBean. Смотри раздел "Ноте-интерфейс cart" на странице 6-11.

•          Remote-интерфейс Компонента, Cart.java. Этот файл содержит определение Remote-интерфейса для Компонента CartBean. Смотри раздел "Remote-интерфейс cart" на странице 6-12.

•          Реализация Компонента cart, CartBean.java. Это собственно класс Компонента. Смотри "Компонент CartBean" на странице 6-13.

•          Класс данных, Item.java. Этот файл используется Компонентом CartBean.

•          Он содержит методы для получения стоимости и названия покупки, которую нужно поместить в карту. Смотри "Класс Item" на странице 6-18.

•          Файл Дескриптора Поставки, Cart.xml. Спецификация EJB 1.1 требует, чтобы Дескриптор Поставки имел XML-формат. Смотри раздел "XML- файл Дескриптора Поставки" на странице 6-20. (Согласно спецификации EJB 1.1, должен присутствовать файл, который создает Дескриптор Поставки для CartBean в формате EJB 1.0, GenerateDescriptors.java – смотри раздел "Генерация Дескриптора Поставки" на странице В-1).

•          Файлы исключений. В этих файлах содержатся определения специфических для приложения исключений, возбуждаемых в коде Компонента CartBean. Определены три класса таких исключений, каждый из которых находится в своем собственном файле: ItemNotFoundException, PurchaseProblemException и CardExpiredException. См. раздел "Исключения" на странице 6-19.

•          Клиентская программа, CartClient.java. Это просто клиентское приложение.

•          Маке-файл для компиляции и построения Компонентов и клиентского приложения.

Ноте-интерфейс Компонета cart

Для Компонента EJB всегда определены два интерфейса: remote- интерфейс и home-интерфейс. Так, в нашем примере для session- Компонента, который называется CartBean, определены два public EJB- интерфеса: remote-интерфейс, который называется Cart, и home- интерфейс, который называется CartHome.

CartHome является home-интерфейсом для session stateful-Компонента CartBean. Как и все другие home-интерфейсы, он наследует интерфейс EJBHome. Хотя home-интерфейс предназначен для выполнения двух видов операций, создания экземпляра Компонента и поиска экземпляра Компонента, home-интерфейс session-Компонета может только создавать новые объекты. Для таких Компонентов не нужны средства поиска, поскольку session-Компонент является временным – по определению, экземпляр такого Компонента прекращает свое существование после завершения сеанса связи с клиентом. Только home-интерфейсы Entity- Компонентов объявляют операции поиска, поскольку такие Компоненты используются несколькими клиентами одновременно и существуют так долго, как сопоставленная с ними информация в базе данных. Пример Кода 6.4 содержит код для интерфейса CartHome.

Пример кода 6.4 Интерфейс CartHome

// CartHome java

public interface CartHome extends javax ejb EJBHome {

Cart create(String cardHolderName, String creditCardNumber, java.util.Date expirationDate) throws java.rmi.RemoteException, javax.ejb.CreateException;

}

В нашем примере это очень простой интерфейс – он объявляет только один метод create (). Так как это stateful-Компонент, то могут присутствовать несколько методов create (), и они могут иметь список аргументов. В нашем случае, метод create () получает три аргумента: cardHolderName, creditCardNumber и expirationDate. Клиент вызывает метод create () для создания новой карты покупок, и Контейнер создает ее специально для этого пользователя. Клиент может работать с картой своих покупок с перерывами (кроме того, сервер может быть перезапущен в результате сбоя), но session-Компонент остается доступным для этого клиента до тех пор, пока пользователь не закончит работу и не удалит Компонент. Inprise Application Server (IAS) помещает состояние Компонента в базу данных. По умолчанию, для этой цели используется база данных JDataStore, хотя Вы можете использовать любую другую базу данных. Так как Компоненты помещаются в базу данных, они могут считаться хранимыми (persistent).

Инфо При работе с Inprise Application Server, в отличие от других продуктов EJB, stateful session-Компоненты сохраняются даже в случае сбоев сервера.

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

Метод create () должен соответствовать правилам, объявленным в спецификации EJB: он может возбуждать стандартное исключение RMI, java.rmi.RemoteException, а также исключение EJB javax.ejb.CreateException. Сигнатура метода create() должна соответствовать методу ejbCreate() в классе Компонента, то есть иметь одинаковое число и типы аргументов. Метод create () возвращает ссылку на remote-интерфейс Cart; это происходит потому, что интерфейс CartHome работает как фабрика Компонентов (типом возвращаемого значения метода ejbCreate() является void).

Remote-интерфейс Cart

Для компонента CartBean определен Remote-интерфейс Cart, который наследует интерфейс EJBObject, который является базовым интерфейсом для всех Remote-интерфейсов. Он позволяет Вам:

•          Получать информацию о Компоненте. Вы можете проверить, является ли данный Компонент идентичным другому. Вы также можете получить значение главного ключа (primary key) для Entity- Компонента, но это не относиться к session-Компонентам.

•          Получать объектную ссылку и/или идентификатор (handle) session- Компонента. Вы можете также получить ссылку на home-интерфейс Компонента. Вы можете сохранить значение идентификатора в базе данных и восстановить его оттуда позднее, а затем использовать как ссылку на экземпляр Компонента.

•          Удалять экземпляры Компонента. Интерфейс EJBObject объявляет метод remove () для удаления экземпляра Компонента.

В дополнение к методам, унаследованным от интерфейса EJBObject, remote-интерфейс Cart содержит пять бизнес-методов. Реализация этих бизнес-методов содержится в классе Компонента CartBean; объявление их в интерфейсе Cart просто делает их доступными для клиента. Клиент может вызывать только те методы, которые объявлены в remote-интерфейсе. Вот эти методы:

•          addltem() – позволяет добавить покупку в карту покупок.

•          remove It em () – удаляет покупку из карты покупок.

•          getTotalePrice () – устанавливает цену всех покупок и возвращает общую стоимость.

•          getContents () – возвращает список всех покупок для просмотра и печати.

• purchase () – выполняет попытку оформления покупки. Код интерфейса Cart показан в Примере Кода 6.5. Пример Кода 6.5 Remote-интерфейс Cart

// Cart java

public interface Cart extends javax.ejb.EJBObject {

void addltem(Item item) throws java.rmi.RemoteException; void removeltem(Item item)

throws ItemNotFoundException, java.rmi.RemoteException; float getTotalPrice() throws java.rmi.RemoteException; java.util.Enumeration getContents() throws

j ava.rmi.RemoteException; void purchase ()

throws PurchaseProblemException, java.rmi.RemoteException;

}

Remote-интерфейс Cart следует всем правилам, определенным в спецификации EJB 1.1:

1       Интерфейс должен наследовать интерфейс javax.ejb.EJBObject.

2       Каждый метод remote-интерфейса должен соответствовать методу, реализованному в классе Компонента. Сигнатуры метода класса и метода Remote-интерфейса должны совпадать, что означает:

•                 они имеют одинаковые имена.

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

•          список исключений метода класса должен быть подмножеством списка исключений метода remote-интерфейса.

3       Все методы должны возбуждать исключения java.rmi.RemoteException. Кроме того, они могут возбуждать другие, зависящие от приложения, исключения. Обратите внимание, как метод removeltem () возбуждает исключение ItemNotFoundException в добавление к

j ava.rmi.RemoteException.

4        Типы всех аргументов и возвращаемых значений должны быть корректными типами данных Java RMI-IIOP.

Компонент CartBean

В этом разделе рассмотрены детали реализации Компонента CartBean. Общая структура и основные части этого класса показаны в примере Кода 6.3 на странице 6-8. Здесь мы рассмотрим класс более подробно.

Компонент EJB должен реализовать либо интерфейс javax.ejb.SessionBean, если он является session-Компонентом, или javax.ejb.EntityBean, если он является Entity-Компонентом. Поскольку наш объект CartBean является session-Компонентом, он реализует

интерфейс javax.ejb.SessionBean, как показано на примере:

public class CartBean implements javax.ejb.SessionBean { Класс session-Компонента должен быть объявлен как public. Он не может быть объявлен ни как final, ни как abstract. Класс не определяет метод finalize ().

Session-Компонент является обычным Компонентом языка Java, который может иметь свои собственные переменные (поля). CartBean имеет четыре такие переменные, и все они объявлены как private- переменные (См. пример Кода 6.6). Клиент не может обратиться к ним непосредственно; вместо этого чтение или изменение значения этих переменных выполняется посредством бизнес-методов Компонента и его метода create().

Пример Кода 6.6 Поля Компонента CartBean

private Vector items = new Vector(); private String cardHolderName; private String creditCardNumber; private Date expirationDate;

Переменная items содержит список покупок, владельцем которых является объект cart. Он представляет собой тип "вектор" – другими словами, набор данных. Три остальные переменные – cardHolderName, _creditCardNumber и _expirationDate – хранят значения аргументов метода create () home-интерфейса, а также метода ejbCreate() класса CartBean.

Обязательные методы

Session-Компонент должен реализовать четыре метода, которые объявлены в интерфейсе javax.ejb.SessionBean. Эти методы вызываются Контейнером EJB в определенные моменты цикла жизни экземпляра Компонента. Как минимум, разработчик Компонента должен обеспечить тривиальную (пустую) реализацию этих методов. Если необходимо, разработчик Компонента может добавить необходимые фрагменты кода в эти методы. Компонент CartBean не содержит никакого собственного кода. Методы интерфейса SessionBean показаны в Примере Кода 6.7.

Пример Кода 6.7 Методы, унаследованные от интерфейса SessionBean

public void setSessionContext(javax.ejb.SessionContext sessionContext) {} public void ejbActivate() {} public void ejbPassivate() {} public void ejbRemove() {}

Контейнер вызывает метод setSessionContext () для того, чтобы сопоставить экземпляр Компонента с контекстом сессии. Компонент может сохранить ссылку на контекст сессии как часть своего состояния, но не обязан делать это. В нашем примере Компонент не сохраняет свой контекст сессии. Компонент может использовать контекст сессии для получения информации о самом себе, например, о параметрах среды или своем home-интерфейсе.

Контейнер вызывает метод ejbPassivate () объекта, когда он собирается перевести Компонент в пассивное состояние. Контейнер обеспечивает запись текущего состояния Компонента в хранилище перед выполнением деактивизации; он восстанавливает состояние при последующей активизации объекта. Поскольку Контейнер вызывает этот метод непосредственно перед выполнением активизации экземпляра, разработчик Компонента может добавить код в этот метод для выполнения некоторых действий, например, кеширования. В том же стиле Контейнер вызывает метод ejbActivate () непосредственно перед тем, как переводит объект из пассивного состояния в активное. Частью процесса активизации является восстановление сохраненного состояния Компонента. Если требуется, разработчик Компонента может добавить тот или иной код к методу ejbActivate (). Класс CartBean обеспечивает тривиальную реализацию обоих этих методов.

Session-Компонент не требует наличия конструктора. Вместо этого, Компонент реализует как минимум один метод ejbCreate (), который исполняет роль конструктора при создании нового экземпляра Компонента. Stateful-Компонент может содержать реализацию нескольких методов ejbCreate (); они отличаются друг от друга списком своих аргументов. В соответствие с требованием спецификации EJB, аргументы каждого метода ejbCreate () класса Компонента должны совпадать с аргументами метода create () home-интерфейса. Таким образом, home-интерфейс обязан иметь отдельный метод create () с тем же самым числом и типом аргументов для каждого метода ejbCreate (). Кроме того, не забывайте, что метод ejbCreate () всегда возвращает void, в то время как метод create home-интерфейса возвращает ссылку на remote-интерфейс. Наш класс CartBean содержит один метод ejbCreate () с тремя аргументами, как показано в Примере Кода 6.8:

Пример Кода 6.8 Метод ejbCreate() класса CartBean

public void ejbCreate(String cardHolderName, String creditCardNumber, Date expirationDate) throws CreateException { cardHolderName = cardHolderName; creditCardNumber = creditCardNumber; expirationDate = expirationDate;

}

Необходимо также объявить метод ejbRemove(). Контейнер вызывает этот метод непосредственно перед удалением экземпляра Компонента. Разработчик Компонента может добавить тот или иной фрагмент кода, который будет выполнен перед удалением, хотя это и не является обязательным. Класс CartBean реализует этот метод тривиальным образом.

Бизнес-методы

Класс session-Компонента обязан реализовать те бизнес-методы, которые объявлены в remote-интерфейсе. Существует несколько правил, которым должна следовать реализация бизнес-методов:

•          имена методов не должны начинаться с "ejb" для того, чтобы избежать возможного конфликта имен с именами, зарезервированными архитектурой EJB.

•      метод должен быть объявлен как public.

•      метод не может быть объявлен как final или static.

•          как аргументы, так и тип результата должны быть корректными типами RMI-IIOP.

•          список исключений может содержать исключения javax.ejb.EJBException, а также произвольные исключения.

Для нашего примера cart, в remote-интерфейсе Cart объявлено пять методов. Реализация этих пяти бизнес-методов находится в классе CartBean. Сигнатуры (имя метода, число аргументов, тип аргументов и тип результата) методов класса и remote-интерфейса должны совпадать.

Класс CartBean реализует следующие методы: addltem() , removeltem () , getTotalPrice () , getContents () и purchase (). (Обратите внимание, что исключение java.rmi.RMIException признано устаревшим в спецификации EJB 1.1).

Для того, чтобы помочь вам отслеживать последовательность выполнения операторов программы, каждый бизнес-метод содержит строку кода, которая выводит на экран имя метода и описание того, что он делает.

Метод addltem() добавляет новые данные в набор данных, которые содержат список покупок карты:

Пример Кода 6.9 Метод addltem() класса CartBean

public void addltem(ltem item) {

System.out.println("\taddltem(" + item getTitle() + "): " + this); items.addElement(item) ;

}

Метод removeltem() несколько более сложен. Программа выполняет цикл перебора элементов в списке для поиска того элемента, который нужно удалить, анализируя вид и название покупки. Кроме того, этот метод проверяет, действительно ли вы удаляете ту покупку, которую хотите удалить. Если покупка не найдена, происходит возбуждение исключения ItemNotFoundException.

Пример Кода 6.10 Метод removeltem()ioiacca CartBean

public void removeltem(Item item) throws ItemNotFoundException { System.out.println("\tremoveItem(" + item getTitle() + "): " + this);

Enumeration elements = items.elements(); while(elements.hasMoreElements()) {

Item current = (Item) elements.nextElement(); 11 items are equal if they have the same class and title if(item.getClass() equals(current.getClass()) && item.getTitle() equals(current.getTitle ())) {

items.removeElement(current); return;

}

}

throw new ItemNotFoundException

("The item " + item getTitle() + " is not in your cart");

}

Метод getTotalPrice () сначала устанавливает общую стоимость равной нулю, а затем выполняет перебор списка покупок, добавляя цену каждой покупки к общей цене. Он возвращает общую цену, округленную с точностью до цента.

Пример Кода 6.11 Метод getTotalPrice((класса CartBean

public float getTotalPrice() {

System. out.printIn("\tgetTotalPrice(): " + this); float totalPrice = Of;

Enumeration elements = items.elements(); while(elements.hasMoreElements()) {

Item current = (Item) elements.nextElement(); totalPrice += current.getPrice();

}

// round to the nearest lower penny return (long) (totalPrice * 100) / lOOf;

}

Все типы данных, передаваемых между клиентом и сервером, должны соответствовать требованиям Java-сериализации. Другими словами, они должны реализовывать интерфейс java.io.Serializable. В нашем примере, программа должна вернуть клиенту список покупок. Если бы не было ограничений сериализации, вы могли бы использовать метод

items .elements () для возвращения содержимого вектора покупок. Тем не менее, метод items .elements () возвращает объект типа Java Enumeration, который не соответствует этим ограничениям. Для того, чтобы решить эту проблему, программа реализует класс, который называется com.inprise.ejb.util.VectorEnumeration. Этот класс получает вектор и возвращает обычное перечисление (enumeration), которое содержит данные этого вектора, и для которого может быть выполнена сериализация Java. Такой вектор может быть передан как от клиента, так и клиенту. Метод getContents (), показанный в Примере Кода 6.12, выполняет преобразование типа между Java Enumeration и классом VectorEnumeration.

Пример Кода 6.12 Метод getContents((класса CartBean

public java.util.Enumeration getContents() {

System.out.println("\tgetContents(): " + this);

return new com.inprise.ejb.util.VectorEnumeration( items);

}

Метод purchase () выполняет следующие действия:

1                получает текущее время.

2                сравнивает текущее время с датой окончания действия карты. Если срок действия карты закончился, возбуждается исключение

CardExpiredException.

3                метод завершает процесс покупки, включая перевод денег с карты на счет компании и инициализацию процесса доставки покупок (ни одно из этих действий на самом деле не реализовано). Если на любом из этих этапов происходит ошибка, процесс покупки не завершается и метод возбуждает исключение PurchaseProblemException.

Пример Кода 6.13 Метод purchase() класса CartBean

public void purchased

throws PurchaseProblemException { System.out.println("\tpurchase(): " + this); // make sure the credit card has not expired Date today = Calendar.getlnstance().getTime (); if(_expirationDate.before(today)) {

throw new CardExpiredException("Expiration date: " + expirationDate);

}

// complete purchasing process

// throw PurchaseProblemException if error occurs }

}

В классе CartBean определен метод toStringO для распечатки "CartBean" и имени владельца карты.

public String toStringO {

return "CartBean[name=" + cardHolderName + "]";

}

Класс Item

Класс Item является public-классом. Он реализует интерфейс java.io.Serializable. Такие данные могут передаваться в качестве аргументов при удаленных вызовах.

Класс Item имеет два поля (title и price), два метода для чтения данных – getTitle() и getPrice() – и конструктор. Пример Кода 6.4 показывает код класса Item:

Пример Кода 6.14 Класс Item

// Item java

public class Item implements java.io.Serializable {

private String title; private float price;

public Item(String title, float price) { _title = title; price = price;

}

public String getTitle() { return _title;

}

public float getPrice() { return price;

}

}

Исключения

В Примере cart определены три исключения. Все они являются наследниками стандартного класса Java.Exception. Все они показаны в Примере Кода 6.15.

Пример Кода 6.15 Исключения для примера cart

// ItemNotFoundException java

public class ItemNotFoundException extends Exception { public ItemNotFoundException(String message) { super(message);

}

}

// PurchaseProblemException java

public class PurchaseProblemException extends Exception { public PurchaseProblemException(String message) { super(message);

}

}

// CardExpiredException java

public class CardExpiredException extends PurchaseProblemException { public CardExpiredException(String message) { super(message);

}

}

Метод removeltem () интерфейса cart возбуждает исключения

ItemNotFoundException. Исключение PurchaseProblemException

возбуждается методом purchase (). На базе класса PurchaseProblemException создан производный класс CardExpiredException.

Инфо Исключения Java и производные от них исключения не

поддерживались до реализации консорциумом OMG спецификацией передачи по значению (objects-by-value).

XML-файл Дескриптора Поставки

<remote>Cart</remote> <ejb-class>CartBean</ejb-class> <session-type>Stateful</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> <assembly-descriptor>

<container-transaction> <method>

<ejb-name>cart</ejb-name> <method-name>[1]</method-name> </method>

<trans-attribute>NotSupported</trans-attribute> </container-transaction> <container-transaction> <method>

<ejb-name>cart</ejb-name> <method-name>purchase</method-name> </method>

<trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>

CartClient.java

Рис. 6.3 Функция main() программы CartClient

}

1         Функция main () начинается с использования контекста JNDI, необходимого для поиска объектов. Нужно создать первоначальный контекст – контекст службы имен Java. Это стандартный код JNDI.

2         Выполняется поиск объекта, реализующего home-интерфейс CartHome (этот объект называется cart). Поиск объекта по его JNDI-имени подразумевает обращение клиента к сервису CosNaming. Этот сервис возвращает клиенту объектную ссылку. В нашем примере, он возвращает объектную ссылку CORBA. Программа должна вызвать операцию PortableRemoteObject .narrow () для преобразования полученной ссылки к типу CartHome с одновременным присвоением этого значения переменной с именем home. Такое преобразование является типичным для распределенных приложений. Этот вызов использует CORBA ПОР для выполнения следующих действий:

•            установки связи с сервером.

•            выполнения поиска на уровне службы CosNaming.

•            получения объектной ссылки CORBA.

•            возврата объектной ссылки клиенту.

3           Программа объявляет ссылку на удаленный объект cart, устанавливает значение трех параметров – имя пользователя, номер кредитной карты и срок действия кредитной карты – и создает новый удаленный объект cart.

4           Программа создает два объекта типа Покупка – книга и компакт- диск – и добавляет эти Покупки в карту покупок, используя вызов метода addltem().

5           Затем программа выводит список всех покупок. Для выполнения этого вызывается функция summarize (). Функция summarize () извлекает элементы или данные из карты, используя метод getContents (), который возвращает java Enumeration. Затем используются методы интерфейса Enumeration для чтения каждого элемента перечисления, и извлекаются название и цена каждой покупки. Пример Кода 6.17 показывает реализацию функции summarize():

Пример Кода 6.17 функция summarize() программы CartClient

static void summarize(Cart cart) throws Exception {

System out printin("======= Cart Summary ========");

Enumeration elements = cart.getContents();

while(elements.hasMoreElements()) {

Item current = (Item) elements.nextElement();

System.out.printin

("Price: $" + current.getPrice() + "\t" + current.getClass().getName() + " title: " + current.getTitle ());

}

System.out.printin("Total: $" + cart.getTotalPrice ());

System.out.printin("=============================");

}

6           Затем программа вызывает метод removeItem() для удаления покупки из списка покупок. Она добавляет другую покупку и опять суммирует результат.

7           Потом программа делает попытку оформить покупку. Так как эта операция не будет выполнена – она не реализована на сервере – программа возбуждает исключение PurchaseProblemException.

8 Здесь пользователь завершает сеанс работы, и программа удаляет объект cart.

Инфо Нет необходимости в явном удалении объекта cart. Здесь это сделано для ясности и для иллюстрации хорошего стиля программирования. Session-Компонент существует только для того клиента, который его создал; когда клиент завершает сеанс связи, Контейнер автоматически удаляет экземпляр Компонента. Контейнер также может удалить экземпляр Компонента по тайм-ауту, хотя это не происходит немедленно.

В программе CartClient также используется код, который создает на базе обобщенного класса Item (смотри "класс Item" на странице 6-18) два новых типа данных: "книга" и "компакт-диск". Book и CompactDisk являются конкретными классами, используемыми в нашем примере.

Пример Кода 6.18 Классы Book и CompactDisk

// CartClient java import java.util.*; class Book extends Item {

Book(String title, float price) { super(title, price);

}

}

class CompactDisc extends Item {

CompactDisc(String title, float price) { super(title, price);

}

}

Источник: Руководство программиста Enterprise JavaBeans

По теме:

  • Комментарии