1
2
3
4
5
6
7
8
9
10 package com.sri.emo.wizard.expressoimpl;
11
12 import com.jcorporate.expresso.core.controller.*;
13 import com.jcorporate.expresso.core.dbobj.DBObject;
14 import com.sri.common.controller.AbstractComponentController;
15 import com.sri.emo.EmoSchema;
16 import com.sri.emo.wizard.*;
17 import com.sri.emo.wizard.defaults.SequentialWizard;
18
19 import java.io.Serializable;
20 import java.util.*;
21
22
23 /***
24 * Wizard Controller is basically a thin wrapper over the Wizard domain
25 * model which does the actual thinking. The WizardController may
26 * be overridden in the case of custom onFinish() behavior.
27 *
28 * @author Michael Rimov
29 */
30 public class WizardController extends AbstractComponentController {
31
32 /***
33 *
34 */
35 private static final long serialVersionUID = 1L;
36
37 /***
38 * Start state constant.
39 */
40 public static final String STATE_BEGIN = "Begin";
41
42 /***
43 * Next State constant.
44 */
45 public static final String NEXT_STATE = "Next";
46
47 /***
48 * Finish State.
49 */
50 public static final String FINISH_STATE = "Finish";
51
52 /***
53 * Cancel State.
54 */
55 public static final String CANCEL_STATE = "Cancel";
56
57 /***
58 * Display Page State.
59 */
60 public static final String DISPLAY_STATE = "Display";
61
62 /***
63 * Previous State.
64 */
65 public static final String PREVIOUS_STATE = "Previous";
66
67 /***
68 * State name to allow jumping backwards.
69 */
70 public static final String STATE_JUMP_BACKWARDS = "JumpBackwards";
71
72
73 /***
74 * Key for session state for the Wizard. [More than one wizard
75 * is possible in a session since they are keyed by wizard id.
76 */
77 protected static final String SESSION_STATE_KEY_PREFIX = WizardController.class.getName();
78
79 /***
80 * Key for the current wizard in the request.
81 */
82 public static final String REQUEST_ID = "Wizard";
83
84 /***
85 * Constant for the id of the EMO Wizard that is being executed. Used
86 * in transitions inside the wizard.
87 */
88 public static final String WIZ_PARAMETER_ID = "Emo.WizardId";
89
90 /***
91 * Constant for what data is sent us.
92 */
93 public static final String WIZ_DATA_ID = "Emo.DataParameter";
94
95 /***
96 * Where the wizard is stored in the request context when finish() is
97 * called.
98 */
99 public static final String WIZ_RESULT_ID = "WizResult";
100
101
102 /***
103 * Parameter for Wizard's finish to get the ExpressoRequest object.
104 */
105 public static final String FINISH_REQUEST = "Request";
106
107 /***
108 * Parameter for the Wizard's finish method to get the
109 * ExpressoResponse object.
110 */
111 public static final String FINISH_RESPONSE = "Response";
112
113 /***
114 * Current Page ID Parameter name.
115 */
116 public static final String WIZ_PAGE_PARAMETER = "Emo.CurrentPage";
117
118 /***
119 * Current page number constant.
120 */
121 public static final String CURRENT_PAGE_NUM = "current";
122
123 /***
124 * Total Pages constant.
125 */
126 public static final String TOTAL_PAGES = "total";
127
128
129 /***
130 * Generic wizard controller.
131 */
132 public WizardController() {
133 super(com.sri.emo.EmoSchema.class);
134
135 State s = new State("Begin", "Begin a Wizard");
136 s.addParameter(WIZ_PARAMETER_ID, DBObject.INT_MASK);
137 addState(s);
138
139 s = new State("Next", "Next Page in the Wizard");
140 s.addParameter(WIZ_PARAMETER_ID, DBObject.INT_MASK);
141 addState(s);
142
143 s = new State("Finish", "Finish the Wizard");
144 s.addParameter(WIZ_PARAMETER_ID, DBObject.INT_MASK);
145 addState(s);
146
147 s = new State("Cancel", "Cancel the Wizard");
148 s.addParameter(WIZ_PARAMETER_ID, DBObject.INT_MASK);
149
150 addState(s);
151
152 s = new State("Display", "Display a wizard page");
153 s.addParameter(WIZ_PARAMETER_ID, true, DBObject.INT_MASK);
154 s.addParameter(WIZ_PAGE_PARAMETER, true, DBObject.INT_MASK);
155 addState(s);
156
157 s = new State("Previous", "Previous page in the Wizard");
158 s.addParameter(WIZ_PARAMETER_ID, DBObject.INT_MASK);
159 addState(s);
160
161 s = new State(STATE_JUMP_BACKWARDS, "Jump Backwards In the Wizard");
162 addState(s);
163
164 setSchema(com.sri.emo.EmoSchema.class);
165 setInitialState("Begin");
166 }
167
168 /***
169 * Return the title of this Controller.
170 *
171 * @return java.lang.String The Title of the controller
172 */
173 public String getTitle() {
174 return "WizardController";
175 }
176
177
178
179 /***
180 * Location for transition on cancel instances. Override in custom
181 * controllers to have your own capabilities.
182 *
183 * @return Transition instance.
184 */
185 protected Transition getCancelTransition() {
186 Transition t = new Transition();
187 t.setName("Cancel");
188 t.setLabel("Cancel");
189 t.setControllerObject(WizardController.class);
190 t.setState(DISPLAY_STATE);
191
192 return t;
193 }
194
195 /***
196 * Retrieve where a particular wizard is stored in the session.
197 *
198 * @param wizardId the current id of the wizard.
199 * @param dataContext the datacontext for the wizard.
200 * @return String in the session of the Wizard.
201 */
202 protected String getSessionKey(final String dataContext, final Serializable wizardId) {
203 assert wizardId != null : " wizardId Parameter Cannot be null";
204 assert dataContext != null: " parameter datacontext cannot be null";
205
206 return SESSION_STATE_KEY_PREFIX + "." + dataContext + "." + wizardId.toString();
207 }
208
209 /***
210 * Retrieves the current wizard from session.
211 *
212 * @param request the current controller request.
213 * @return Wizard Instance
214 * @throws ControllerException if unable to query the session.
215 */
216 public Wizard getCurrentWizard(final ExpressoRequest request) throws ControllerException {
217 Integer id = new Integer(request.getParameter(WIZ_PARAMETER_ID));
218
219
220 String sessionKey = getSessionKey(request.getDataContext(), id);
221
222 WizardMementoConverter converter = getMementoConverter();
223
224
225 Serializable toRetrieve = (Serializable) request.getSession().getPersistentAttribute(sessionKey);
226 if (toRetrieve == null) {
227 return null;
228 }
229
230
231 if (converter != null) {
232 try {
233 toRetrieve = converter.fromMemento(toRetrieve);
234 } catch (WizardException ex) {
235 throw new ControllerException("Error converting wizard to be able to store it in session.", ex);
236 }
237 }
238
239 return (Wizard) toRetrieve;
240 }
241
242 /***
243 * Retrieves the current page as set in the displayed page.
244 *
245 * @param request ExpressoRequest the controller request object.
246 * @param currentWizard Wizard the current wizard.
247 * @return WizardPage the current wizard page as defined by the hidden
248 * parameters on the form.
249 * @throws ControllerException upon error.
250 */
251 public WizardPage getCurrentPage(final ExpressoRequest request,
252 final Wizard currentWizard) throws ControllerException {
253 String currentPageString = request.getParameter(WizardController.WIZ_PAGE_PARAMETER);
254 if (currentPageString == null || currentPageString.length() == 0) {
255 return currentWizard.getCurrentPage();
256 } else {
257 Integer i = null;
258 try {
259 i = new Integer(currentPageString);
260 } catch (NumberFormatException ex) {
261 throw new ControllerException("Illegal argument value " + "for parameter '" + WIZ_PAGE_PARAMETER + "'");
262 }
263 return currentWizard.getPageById(i);
264 }
265 }
266
267 /***
268 * Stores a new wizard to session.
269 *
270 * @param request the <code>ExpressoRequest</code> object
271 * @param toStore the wizard to store.
272 * @param wizId the integer id of the wizard.
273 * @throws ControllerException upon session related error.
274 */
275 protected void storeWizInSession(final ExpressoRequest request, final Wizard toStore,
276 final Integer wizId) throws ControllerException {
277
278 String sessionId = getSessionKey(request.getDataContext(), wizId);
279
280 WizardMementoConverter converter = getMementoConverter();
281 Serializable objectToStore = toStore;
282 if (converter != null) {
283 try {
284 objectToStore = converter.toMemento(toStore);
285 } catch (WizardException ex) {
286 throw new ControllerException("Error converting wizard to be able to store it in session.", ex);
287 }
288 }
289 request.getSession().setPersistentAttribute(sessionId, objectToStore);
290 }
291
292
293 /***
294 * <tt>Template Method</tt>. Retrieves/Constructs the WizardMementoConverter
295 * appropriate for the current run.
296 *
297 * @return WizardMementoConverter or null if none is defined.
298 */
299 protected WizardMementoConverter getMementoConverter() {
300 return null;
301 }
302
303 /***
304 * Create a new wizard and store it to session.
305 *
306 * @param request the <code>ExpressoRequest</code> object
307 * @return Wizard instance
308 * @throws WizardException upon wizard error
309 * @throws ControllerException upon expresso controller related
310 * error.
311 */
312 protected Wizard newWizard(final ExpressoRequest request) throws WizardException, ControllerException {
313 Integer id = new Integer(request.getParameter(WIZ_PARAMETER_ID));
314 WizardRepository repository = ((EmoSchema) getSchemaInstance()).getWizardRepository(request.getDataContext());
315
316 Wizard returnValue = repository.find(id);
317 storeWizInSession(request, returnValue, id);
318
319 return returnValue;
320 }
321
322 /***
323 * Runs the begin state.
324 *
325 * @param request the <code>ExpressoRequest</code> object
326 * @param response the <code>ExpressoResponse</code> object.
327 * @throws ControllerException upon error
328 * @throws NonHandleableException upon fatal error
329 */
330 protected void runBeginState(final ExpressoRequest request,
331 final ExpressoResponse response) throws ControllerException, NonHandleableException {
332 try {
333 Wizard wiz = newWizard(request);
334 wiz.begin();
335 request.setParameter(WIZ_PAGE_PARAMETER, wiz.getCurrentPage().getId().toString());
336 transition(DISPLAY_STATE, request, response);
337 } catch (WizardException ex) {
338 throw new ControllerException("Error starting wizard", ex);
339 }
340 }
341
342 /***
343 * Runs the cancel state.
344 *
345 * @param request the <code>ExpressoRequest</code> object
346 * @param response the <code>ExpressoResponse</code> object.
347 * @throws ControllerException upon error
348 * @throws NonHandleableException upon fatal error
349 */
350 protected void runCancelState(final ExpressoRequest request,
351 final ExpressoResponse response) throws ControllerException, NonHandleableException {
352 Wizard wiz = getCurrentWizard(request);
353 if (wiz != null) {
354 request.getSession().removePersistentAttribute(this.getSessionKey(request.getDataContext(),
355 request.getParameter(WIZ_PARAMETER_ID)));
356 }
357 getCancelTransition().executeTransition(request, response);
358 }
359
360 /***
361 * Displays the result of the wizard. This particular implementation
362 * puts the wizard in the request context under the parameter
363 * name of {@link #REQUEST_ID}. and if the wizard metadata is using
364 * expresso links, then it dumps the resulting Transitions into the
365 * ExpressoResponse object. [Although JSTL can certainly
366 * get the links directly from the {@link #REQUEST_ID} object's metadata:
367 * so choose your style.
368 *
369 * @param request the <code>ExpressoRequest</code> object
370 * @param response the <code>ExpressoResponse</code> object.
371 * @throws ControllerException upon error
372 * @throws NonHandleableException upon fatal error
373 */
374 protected void runDisplayState(final ExpressoRequest request,
375 final ExpressoResponse response) throws ControllerException, NonHandleableException {
376
377
378 response.add(getCancelTransition());
379
380 Wizard requestedWizard = getCurrentWizard(request);
381 if (requestedWizard == null) {
382 return;
383 }
384
385
386 request.getSession().setAttribute(REQUEST_ID, requestedWizard);
387
388 PageMetadata pageMetadata = requestedWizard.getCurrentPage().getMetadata();
389
390
391
392
393 Link l = pageMetadata.getNext();
394 if ((l != null) && (l instanceof ExpressoLink)) {
395 response.add(((ExpressoLink) l).getTransition());
396 }
397
398 l = pageMetadata.getFinish();
399 if ((l != null) && (l instanceof ExpressoLink)) {
400 response.add(((ExpressoLink) l).getTransition());
401 }
402
403 l = pageMetadata.getPrevious();
404 if ((l != null) && (l instanceof ExpressoLink)) {
405 Transition previous = ((ExpressoLink) l).getTransition();
406 previous.addParam(WizardController.WIZ_DATA_ID, request.getParameter(WizardController.WIZ_DATA_ID));
407 response.add(previous);
408 }
409
410
411 addWizardInput(response, requestedWizard, pageMetadata);
412
413
414 if (pageMetadata.getViewId() != null) {
415 response.setStyle(pageMetadata.getViewId());
416 }
417
418
419 addPageNumberForSequentialWizards(requestedWizard, response);
420
421
422
423
424 response.add(new Output("morebot protection internal output", "Sorry, an error has occured "
425 + "Expresso Default Page Has Been Displayed. Check struts configurations."));
426 }
427
428
429 /***
430 * Run the finish state.
431 *
432 * @param request the <code>ExpressoRequest</code> object
433 * @param response the <code>ExpressoResponse</code> object.
434 * @throws ControllerException upon error
435 * @throws NonHandleableException upon fatal error
436 */
437 protected void runFinishState(final ExpressoRequest request,
438 final ExpressoResponse response) throws ControllerException, NonHandleableException {
439
440 try {
441 Wizard wiz = getCurrentWizard(request);
442 if (wiz == null) {
443 response.addError("We're sorry, but your session has expired. Please re-run the wizard");
444 Transition cancel = getCancelTransition();
445 cancel.executeTransition(request, response);
446 return;
447
448 }
449
450 String data = request.getParameter(WIZ_DATA_ID);
451
452 if (this.hasNoDataEntryAndShould(data, wiz.getCurrentPage())) {
453 this.handleNoEntryErrorInPage(request, response);
454 return;
455 }
456
457 WizardPage currentPage = wiz.getCurrentPage();
458 Map finishParameters = new HashMap();
459 this.addOnFinishWizardParameters(request, response, finishParameters);
460 Object result = wiz.processFinish(currentPage, data, finishParameters);
461 if (currentPage.getPageErrors() == null || currentPage.getPageErrors().isEmpty()) {
462 request.getSession().setAttribute(WIZ_RESULT_ID, result);
463 releaseWizard(request, wiz);
464 afterFinishState(request, response);
465 } else {
466
467 transferPageErrorsToRequest(currentPage, response);
468 transition(DISPLAY_STATE, request, response);
469 }
470 } catch (WizardException ex) {
471 throw new ControllerException("Error going to previous page", ex);
472 }
473 }
474
475 protected void addOnFinishWizardParameters(final ExpressoRequest request,
476 final ExpressoResponse response,
477 final Map parameterMap) {
478
479 }
480
481 /***
482 * Template method. Once the wizard is released and processing is finished
483 * what do we do?
484 *
485 * @param request ExpressoRequest Expresso Request object
486 * @param response ExpressoResponse ExpressoResponse object
487 * @throws ControllerException upon errors.
488 */
489 protected void afterFinishState(final ExpressoRequest request, final ExpressoResponse response) throws
490 ControllerException {
491 transition(DISPLAY_STATE, request, response);
492 }
493
494 /***
495 * Removes the wizard resources from the session.
496 *
497 * @param request ExpressoRequest the ExpressoRequest object.
498 * @param wiz Wizard the Wizard to release.
499 * @throws ControllerException upon session removal error.
500 * @throws WizardException upon wizard destruction error.
501 */
502 protected void releaseWizard(final ExpressoRequest request, final Wizard wiz) throws ControllerException,
503 WizardException {
504 wiz.destroy();
505
506
507
508
509 request.getSession().removePersistentAttribute(this.getSessionKey(request.getDataContext(),
510 wiz.getId().toString()));
511
512 request.getSession().setAttribute(
513 getSessionKey(request.getDataContext(), wiz.getId().toString()),
514 wiz);
515 }
516
517 /***
518 * Runs the next state. This fucntion checks for input and transitions
519 * to the display state after invoking the wizard's 'next' function if there
520 * were not errors.
521 *
522 * @param request the <code>ExpressoRequest</code> object
523 * @param response the <code>ExpressoResponse</code> object.
524 * @throws ControllerException upon error
525 * @throws NonHandleableException upon fatal error
526 */
527 protected void runNextState(final ExpressoRequest request,
528 final ExpressoResponse response) throws ControllerException, NonHandleableException {
529 try {
530 Wizard wiz = getCurrentWizard(request);
531 if (wiz == null) {
532 response.addError("We're sorry, but your session has expired. Please re-run the wizard");
533 Transition cancel = getCancelTransition();
534 cancel.executeTransition(request, response);
535 return;
536 }
537
538
539 WizardPage currentPage = getCurrentPage(request, wiz);
540
541
542
543
544 Serializable data = extractPostedWizardData(request);
545
546 boolean errorInPage = false;
547 errorInPage = hasNoDataEntryAndShould(data, currentPage);
548
549 if (errorInPage) {
550 handleNoEntryErrorInPage(request, response);
551 return;
552 }
553
554 wiz.next(currentPage, data);
555
556
557 transferPageErrorsToRequest(currentPage, response);
558 transition(DISPLAY_STATE, request, response);
559 } catch (WizardException ex) {
560 throw new ControllerException("Error going to previous page", ex);
561 }
562 }
563
564 private void transferPageErrorsToRequest(WizardPage currentPage, final ExpressoResponse response) throws
565 ControllerException {
566 ErrorCollection ec = currentPage.getPageErrors();
567 if (ec != null && ec.size() > 0) {
568 response.saveErrors(ec);
569
570
571 currentPage.removeErrors();
572 }
573 }
574
575 /***
576 * Template Method, override in your derived classes if desired. No matter
577 * what, it tries to extract WIZ_DATA_ID. Then It appends _1, _2, _3, etc
578 * until it doesn't find any new parameters. If it finds more than one
579 * value it returns a Collection, if not, it will return a string. Derived
580 * classes, of course, may return other collections depending on the
581 * wizard.
582 *
583 * @param request ExpressoRequest
584 * @return Serializable may be null if the data is not found or incomplete.
585 */
586 protected Serializable extractPostedWizardData(final ExpressoRequest request) {
587 String data = request.getParameter(WIZ_DATA_ID);
588
589
590 if (data != null) {
591
592
593 ArrayList returnCollection = null;
594
595
596 int paramCounter = 1;
597
598
599 String extractedData = null;
600
601
602
603
604
605 do {
606 extractedData = request.getParameter(WIZ_DATA_ID + "_" + paramCounter);
607 if (extractedData != null) {
608 if (returnCollection == null) {
609 returnCollection = new ArrayList();
610 returnCollection.add(data);
611 }
612
613 returnCollection.add(extractedData);
614 }
615 paramCounter++;
616 } while (extractedData != null);
617
618
619 if (returnCollection != null) {
620 return returnCollection;
621 } else {
622
623 return data;
624 }
625 }
626 return data;
627 }
628
629 /***
630 * Method that handles no-data entry if pages that should have occurred.
631 *
632 * @param request ExpressoRequest
633 * @param response ExpressoResponse
634 * @throws NonHandleableException upon transition error.
635 * @throws ControllerException upon error in substate (Usually Display)
636 */
637 protected void handleNoEntryErrorInPage(final ExpressoRequest request, final ExpressoResponse response) throws
638 NonHandleableException,
639 ControllerException {
640 ErrorCollection ec = request.getErrorCollection();
641 if (ec == null) {
642 ec = new ErrorCollection();
643 }
644
645 ec.addError("Please enter a value for this step.");
646 response.saveErrors(ec);
647 transition(DISPLAY_STATE, request, response);
648 }
649
650 /***
651 * Function that allows for consistent checking for whether data was
652 * entered. In this case it checks for null values, zero length strings,
653 * or zero size collections.
654 *
655 * @param data String
656 * @param currentPage WizardPage
657 * @return boolean
658 */
659 protected boolean hasNoDataEntryAndShould(final Object data, final WizardPage currentPage) {
660 boolean errorInPage = false;
661
662 if (currentPage.getMetadata().isHasEntry() && currentPage.getMetadata().isEntryRequired()) {
663 if (data == null) {
664 errorInPage = true;
665 } else if ((data instanceof String) && ((String) data).trim().length() == 0) {
666 errorInPage = true;
667 } else if ((data instanceof Collection) && ((Collection) data).size() == 0) {
668 errorInPage = true;
669 }
670 }
671 return errorInPage;
672 }
673
674 /***
675 * Runs the begin state.
676 *
677 * @param request the <code>ExpressoRequest</code> object
678 * @param response the <code>ExpressoResponse</code> object.
679 * @throws ControllerException upon error
680 * @throws NonHandleableException upon fatal error
681 */
682 protected void runPreviousState(final ExpressoRequest request, final ExpressoResponse response) throws
683 ControllerException, WizardException {
684 Wizard wiz = getCurrentWizard(request);
685
686 if (checkSessionExpired(request, response, wiz)) {
687 return;
688 }
689
690 wiz.previous();
691 transition(DISPLAY_STATE, request, response);
692 }
693
694 private boolean checkSessionExpired(ExpressoRequest request, ExpressoResponse response,
695 Wizard wiz) throws NonHandleableException, ControllerException {
696 if (wiz == null) {
697 response.addError("We're sorry, but your session has expired. Please re-run the wizard");
698 Transition cancel = getCancelTransition();
699 cancel.executeTransition(request, response);
700 return true;
701 } else {
702 return false;
703 }
704 }
705
706
707 /***
708 * Runs the state that jumps the wizard backwards to some sort of previous page.
709 *
710 * @param request the <code>ExpressoRequest</code> object
711 * @param response the <code>ExpressoResponse</code> object.
712 * @throws ControllerException upon error
713 */
714 protected void runJumpBackwardsState(final ExpressoRequest request, final ExpressoResponse response) throws
715 ControllerException {
716 Wizard wiz = getCurrentWizard(request);
717
718 if (checkSessionExpired(request, response, wiz)) {
719 return;
720 }
721
722
723 String pageId = request.getParameter(WIZ_PAGE_PARAMETER);
724 if (pageId == null || pageId.length() == 0) {
725 throw new ControllerException(
726 "Parameter " + WIZ_PAGE_PARAMETER + " is required to jump backwards in the wizard.");
727 }
728
729 wiz.backupToPage(pageId);
730 transition(DISPLAY_STATE, request, response);
731 }
732
733
734 /***
735 * Adds the wizard page input. Template method to allow overriding based
736 * on
737 *
738 * @param response ExpressoResponse
739 * @param wiz Wizard
740 * @param wp PageMetadata
741 * @throws ControllerException
742 */
743 protected void addWizardInput(final ExpressoResponse response, final Wizard wiz,
744 final PageMetadata wp) throws ControllerException {
745 if (wp.isHasEntry()) {
746 Input i = new Input();
747 i.setName(WIZ_DATA_ID);
748 i.setLabel(wp.getDirective());
749
750
751
752
753
754
755
756 if (wiz.getCurrentPage().getMenu() != null && wiz.getCurrentPage().getMenu().length > 0) {
757
758 List v = Arrays.asList(wiz.getCurrentPage().getMenu());
759 i.setValidValues(v);
760 String curVal = (String) wiz.getCurrentPage().getData();
761 if (curVal != null && curVal.length() > 0) {
762 i.setDefaultValue(curVal);
763 }
764 }
765
766
767 if (wiz.getCurrentPage().getData() instanceof List) {
768 List inputs = (List) wiz.getCurrentPage().getData();
769 int counter = 0;
770 for (Iterator iterator = inputs.iterator(); iterator.hasNext();) {
771 if (counter == 0) {
772 i.setDefaultValue((String) iterator.next());
773 response.add(i);
774 } else {
775 Input nextInput = new Input();
776 nextInput.setName(WIZ_DATA_ID + "_" + counter);
777 nextInput.setDefaultValue((String) iterator.next());
778 response.add(nextInput);
779 }
780
781 counter++;
782 }
783 } else {
784 if (wiz.getCurrentPage().getData() != null) {
785 i.setDefaultValue(wiz.getCurrentPage().getData().toString());
786 }
787 response.add(i);
788 }
789 }
790 }
791
792 protected void addPageNumberForSequentialWizards(final Wizard wiz, final ExpressoResponse response) throws
793 ControllerException {
794
795
796
797 if (wiz instanceof SequentialWizard) {
798 SequentialWizard sequence = (SequentialWizard) wiz;
799 response.add(new Output(CURRENT_PAGE_NUM, "" + sequence.getCurrentPageIndex()));
800 response.add(new Output(TOTAL_PAGES, "" + sequence.getTotalPages()));
801 }
802 }
803 }