Главная » Spring » Сбор информации о клиенте Spring

0

Если прежде вам доводилось заказывать пиццу, вы наверняка зна- комы с этой процедурой. Сначала у вас спрашивают номер теле- фона. Кроме возможности позвонить вам, если разносчик пиццы не

сможет отыскать ваш дом, номер телефона для пиццерии служит дополнительным идентификационным признаком. Если вы являе- тесь постоянным клиентом, в пиццерии могут использовать номер телефона для определения вашего адреса, чтобы узнать, куда до- ставлять заказ.

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

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

Эта последовательность гораздо интереснее последовательности верхнего уровня. Она нелинейна и имеет пару ветвлений в зависи-

Рис. 9.3. Последовательность идентификации клиента получается более закрученной, чем основная последовательность

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

В листинге 9.4 представлено определение последовательности, выполняющей идентификацию клиента.

Листинг 9.4. Реализация идентификации голодного клиента в виде веб- последовательности

<?xml  version="1.0"  encoding="UTF-8"?>

<flow      xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/webflow

http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

<var  name="customer"  class="com.springinaction.pizza.domain.Customer"/>

<view-state  id="welcome">                             <!–  Вывод  приветствия  –>

<transition  on="phoneEntered"  to="lookupCustomer"/>

</view-state>

<action-state id="lookupCustomer">             <!– Поиск информации о клиенте –>

<evaluate   result="customer"   expression= "pizzaFlowActions.lookupCustomer(requestParameters.phoneNumber)" />

<transition  to="registrationForm"  on-exception= "com.springinaction.pizza.service.CustomerNotFoundException"     />

<transition to="customerReady" />

</action-state>

<!– Регистрация нового клиента –>

<view-state id="registrationForm" model="customer">

<on-entry>

<evaluate expression=

"customer.phoneNumber  =  requestParameters.phoneNumber"  />

</on-entry>

<transition on="submit" to="checkDeliveryArea" />

</view-state>

<decision-state  id="checkDeliveryArea">     <!–  Проверка  зоны  обслуживания  –>

<if     test="pizzaFlowActions.checkDeliveryArea(customer.zipCode)" then="addCustomer"

else="deliveryWarning"/>

</decision-state>

<view-state id="deliveryWarning">     <!– Вывод предупреждения –>

<transition on="accept" to="addCustomer" />

</view-state>

<action-state id="addCustomer">         <!– Добавление информации о клиенте –>

<evaluate  expression="pizzaFlowActions.addCustomer(customer)"  />

<transition to="customerReady" />

</action-state>

<end-state  id="cancel"  />

<end-state id="customerReady">

<output name="customer" />

</end-state>

<global-transitions>

<transition  on="cancel"  to="cancel"  />

</global-transitions>

</flow>

В этом определении последовательности присутствуют ранее не использовавшиеся элементы, включая элемент <decision-state>. Кро- ме того, поскольку эта последовательность является подпоследова- тельностью, вызываемой из основной последовательности, предпо- лагается, что ей будет передаваться объект Order.

Как и прежде, рассмотрим определение последовательности, со- стояние за состоянием, начав с состояния welcome.

Запрос номера телефона

Состояние welcome – это очень простое состояние-представление, приветствующее клиента, пришедшего на веб-сайт Spizza, и пред- лагающее ввести номер телефона. Само состояние не содержит ничего интересного. В нем определены два перехода: один ведет к состоянию lookupCustomer, если представление возбудит событие phoneEntered, и второй – глобальный переход cancel, который выпол- няется в ответ на событие cancel.

Наибольший интерес в состоянии welcome представляет само пред- ставление. Оно определено в файле /WEB-INF/flows/pizza/customer/ welcome.jspx, как показано в листинге 9.5.

Листинг 9.5. Представление, приветствующее клиента и предлагающее ввести номер телефона

<html      xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:form="http://www.springframework.org/tags/form">

<jsp:output  omit-xml-declaration="yes"/>

<jsp:directive.page  contentType="text/html;charset=UTF-8"  />

<head><title>Spizza</title></head>

<body>

<h2>Welcome to Spizza!!!</h2>

<form:form>

<input   type="hidden"   name="_flowExecutionKey" value="${flowExecutionKey}"/>

<input type="text" name="phoneNumber"/><br/>

<input   type="submit"   name="_eventId_phoneEntered" value="Lookup  Customer"  />

</form:form>

</body>

</html>

Эта простая форма предлагает клиенту ввести номер телефона. Но форма имеет два специальных ингредиента, позволяющих ей управлять выполнением последовательности.

Первый – скрытое поле _flowExecutionKey. При входе в состояние- представление выполнение последовательности приостанавливает- ся до того момента, пока пользователь не выполнит какого-либо действия. Представлению передается ключ выполнения последова- тельности, своеобразное «удостоверение» для последовательности. Когда пользователь отправит форму, ключ выполнения последо- вательности будет отправлен вместе с ней, в поле _flowExecutionKey, и последовательность продолжит выполнение с того места, где она была приостановлена.

Обратите также внимание на значение атрибута name кнопки submit. Часть _eventId_ в значении атрибута – это подсказка фреймворку Spring Web Flow, что оставшаяся часть значения является именем события, которое должно быть возбуждено. Когда форма отправ- ляется щелчком на этой кнопке, возбуждается событие phoneEntered, заставляющее выполнить переход к состоянию lookupCustomer.

Поиск информации о клиенте

После того как форма с приветствием будет отправлена на сервер, в ее составе будет отправлен и номер телефона клиента, которым можно воспользоваться, чтобы отыскать информацию о клиенте. Поиск выполняется элементом <evaluate> состояния lookupCustomer. Он извлекает номер телефона из параметров запроса и передает его методу lookupCustomer()  компонента pizzaFlowActions.

В данный момент реализация метода lookupCustomer() не имеет большого значения. Достаточно знать, что он либо возвращает объ- ект Customer, либо возбуждает исключение CustomerNotFoundException.

В первом случае объект Customer сохраняется в переменной customer (определяется атрибутом result) и выполняется переход по умол- чанию к состоянию customerReady. Но если клиент не найден, метод возбудит исключение CustomerNotFoundException, и последовательность перейдет к состоянию registrationForm.

Регистрация нового клиента

В состоянии registrationForm пользователю предлагается указать адрес доставки. Подобно другим состояниям-представлениям, это состояние также отображает JSP-страницу, представленную в лис- тинге 9.6.

Листинг 9.6. Регистрация нового клиента

<html      xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:spring="http://www.springframework.org/tags" xmlns:form="http://www.springframework.org/tags/form">

<jsp:output  omit-xml-declaration="yes"/>

<jsp:directive.page  contentType="text/html;charset=UTF-8"  />

<head><title>Spizza</title></head>

<body>

<h2>Customer Registration</h2>

<form:form commandName="customer">

<input   type="hidden"   name="_flowExecutionKey" value="${flowExecutionKey}"/>

<b>Phone  number:  </b><form:input  path="phoneNumber"/><br/>

<b>Name:  </b><form:input  path="name"/><br/>

<b>Address:  </b><form:input  path="address"/><br/>

<b>City:   </b><form:input   path="city"/><br/>

<b>State: </b><form:input path="state"/><br/>

<b>Zip   Code:   </b><form:input   path="zipCode"/><br/>

<input type="submit" name="_eventId_submit" value="Submit" />

<input  type="submit"  name="_eventId_cancel" value="Cancel"  />

</form:form>

</body>

</html>

Это не первая форма, встретившаяся нам в нашей последователь- ности. Представление welcome тоже отображает форму, более прос- тую, содержащую единственное поле, значение которого совсем не- сложно было извлечь из параметров запроса. Форма регистрации имеет более сложную организацию.

Чтобы избавиться от необходимости извлекать значения полей по отдельности из параметров запроса, имеет смысл связать форму с объ- ектом Customer и переложить всю рутинную работу на фреймворк.

Проверка зоны обслуживания

Когда клиент введет адрес, необходимо убедиться, что он живет в пределах досягаемости. Если пиццерия Spizza окажется не в со- стоянии выполнить доставку, необходимо предупредить клиента и предложить ему прийти за заказом самому.

Принятие решения реализуется состоянием-решением. Состояние checkDeliveryArea имеет элемент <if>, который передает почтовый ин- декс клиента методу checkDeliveryArea() компонента pizzaFlowActions. Этот метод возвращает логическое значение: true, если клиент на- ходится в пределах досягаемости, и false – в противном случае.

Если клиент находится в пределах досягаемости, последователь- ность выполняет переход к состоянию addCustomer. Иначе – к со- стоянию deliveryWarning. Представление, которое стоит за состояни- ем deliveryWarning, определено в файле /WEB-INF/flows/pizza/customer/ deliveryWarning.jspx и приводится в листинге 9.7.

Листинг 9.7. Предупреждение о невозможности доставки пиццы по указанному адресу

<html xmlns:jsp="http://java.sun.com/JSP/Page">

<jsp:output  omit-xml-declaration="yes"/>

<jsp:directive.page  contentType="text/html;charset=UTF-8"  />

<head><title>Spizza</title></head>

<body>

<h2>Delivery Unavailable</h2>

<p>The address is outside of our delivery area. You  may still place the order, but you will need to pick it up yourself.</p>

<![CDATA[

<a     href="${flowExecutionUrl}&_eventId=accept">

Continue, I’ll pick up the order</a> |

<a   href="${flowExecutionUrl}&_eventId=cancel">Never   mind</a>

]]>

</body>

</html>

Ключевыми элементами страницы deliveryWarning.jspx, имеющими отношение к последовательности, являются две ссылки, позволяю- щие клиенту продолжить оформление заказа или отказаться от него. С помощью той же переменной flowExecurtionUrl, что использовалась в состоянии welcome, эти ссылки возбуждают в последовательности событие accept или cancel. При получении события accept последо- вательность выполнит переход к состоянию addCustomer. Иначе будет выполнен глобальный переход cancel, и подпоследовательность по- падет в состояние cancel.

О конечных состояниях будет говориться чуть ниже, а пока об- судим состояние addCustomer.

Сохранение информации о клиенте

К моменту, когда будет достигнуто состояние addCustomer, клиент уже введет свой адрес. Этот адрес необходимо сохранить на будущее (может быть, в базе данных). Состояние addCustomer имеет элемент

<evaluate>, вызывающий метод addCustomer()  компонента pizzaFlow- Actions и передающий ему переменную customer последовательности.

После вычисления выражения выполняется переход по умолча- нию к конечному состоянию с идентификатором customerReady.

Завершение выполнения последовательности

Обычно конечные состояния не содержат ничего интересного. Но в данной последовательности не одно, а целых два конечных со-

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

Когда последовательность идентификации клиента завершит вы- полнение обычным образом, она достигнет конечного состояния с идентификатором customerReady. Когда управление будет передано вызвавшей ее основной последовательности, она примет событие customerReady, в результате которого будет выполнен переход к со- стоянию buildOrder.

Обратите внимание, что конечное состояние customerReady включа- ет элемент <output>. В последовательностях этот элемент играет роль инструкции return. Он возвращает вызывающей последовательности данные из подпоследовательности. В данном случае элемент <output> возвращает переменную customer, которую состояние identifyCustomer в главной последовательности сохраняет в заказе.

С другой стороны, если в какой-то момент в последовательности идентификации клиента будет возбуждено событие cancel, она со- вершит выход через конечное состояние с идентификатором cancel. Это приведет к возбуждению события cancel в главной последова- тельности и к переходу (через глобальный переход) к конечному ее состоянию.

Источник:   Уоллс К., Spring в действии. – М.: ДМК Пресс, 2013. – 752 с.: ил.

По теме:

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