001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/velocity/tags/sakai_2-4-1/tool/src/java/org/sakaiproject/cheftool/PagedResourceAction.java $
003: * $Id: PagedResourceAction.java 7946 2006-04-19 03:13:07Z ggolden@umich.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2004, 2005, 2006 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.cheftool;
021:
022: import java.util.List;
023: import java.util.Vector;
024:
025: import org.sakaiproject.cheftool.api.Menu;
026: import org.sakaiproject.cheftool.api.MenuItem;
027: import org.sakaiproject.cheftool.menu.MenuDivider;
028: import org.sakaiproject.cheftool.menu.MenuEntry;
029: import org.sakaiproject.cheftool.menu.MenuField;
030: import org.sakaiproject.courier.api.ObservingCourier;
031: import org.sakaiproject.entity.api.Entity;
032: import org.sakaiproject.event.api.SessionState;
033: import org.sakaiproject.util.ResourceLoader;
034: import org.sakaiproject.util.StringUtil;
035:
036: /**
037: * <p>
038: * PagedResourceAction is a base class that handles paged display of lists of Resourecs.
039: * </p>
040: */
041: public abstract class PagedResourceAction extends
042: VelocityPortletPaneledAction {
043:
044: private static ResourceLoader rb = new ResourceLoader(
045: "velocity-tool");
046:
047: /** The default number of messages per page. */
048: protected static final int DEFAULT_PAGE_SIZE = 10;
049:
050: /** portlet configuration parameter names. */
051: protected static final String PARAM_PAGESIZE = "pagesize";
052:
053: /** state attribute names. */
054: protected static final String STATE_VIEW_ID = "view-id";
055:
056: protected static final String STATE_TOP_PAGE_MESSAGE = "msg-top";
057:
058: protected static final String STATE_PAGESIZE = "page-size";
059:
060: protected static final String STATE_NUM_MESSAGES = "num-messages";
061:
062: protected static final String STATE_NEXT_PAGE_EXISTS = "msg-next-page";
063:
064: protected static final String STATE_PREV_PAGE_EXISTS = "msg-prev-page";
065:
066: protected static final String STATE_GO_NEXT_PAGE = "msg-go-next-page";
067:
068: protected static final String STATE_GO_PREV_PAGE = "msg-go-prev-page";
069:
070: protected static final String STATE_GO_NEXT = "msg-go-next";
071:
072: protected static final String STATE_GO_PREV = "msg-go-prev";
073:
074: protected static final String STATE_NEXT_EXISTS = "msg-next";
075:
076: protected static final String STATE_PREV_EXISTS = "msg-prev";
077:
078: protected static final String STATE_GO_FIRST_PAGE = "msg-go-first-page";
079:
080: protected static final String STATE_GO_LAST_PAGE = "msg-go-last-page";
081:
082: protected static final String STATE_SEARCH = "search";
083:
084: protected static final String STATE_MANUAL_REFRESH = "manual";
085:
086: /** Form fields. */
087: protected static final String FORM_SEARCH = "search";
088:
089: /**
090: * Implement this to return alist of all the resources that there are to page. Sort them as appropriate, and apply search criteria.
091: */
092: protected abstract List readAllResources(SessionState state);
093:
094: /**
095: * Populate the state object, if needed, concerning paging
096: */
097: protected void initState(SessionState state,
098: VelocityPortlet portlet, JetspeedRunData rundata) {
099: super .initState(state, portlet, rundata);
100:
101: if (state.getAttribute(STATE_PAGESIZE) == null) {
102: state.setAttribute(STATE_PAGESIZE, new Integer(
103: DEFAULT_PAGE_SIZE));
104: PortletConfig config = portlet.getPortletConfig();
105:
106: try {
107: Integer size = new Integer(config
108: .getInitParameter(PARAM_PAGESIZE));
109: if (size.intValue() <= 0) {
110: size = new Integer(DEFAULT_PAGE_SIZE);
111: if (Log.getLogger("chef").isDebugEnabled())
112: Log
113: .debug(
114: "chef",
115: this
116: + ".initState: size parameter invalid: "
117: + config
118: .getInitParameter(PARAM_PAGESIZE));
119: }
120: state.setAttribute(STATE_PAGESIZE, size);
121: } catch (Exception any) {
122: if (Log.getLogger("chef").isDebugEnabled())
123: Log.debug("chef", this
124: + ".initState: size parameter invalid: "
125: + any.toString());
126: state.setAttribute(STATE_PAGESIZE, new Integer(
127: DEFAULT_PAGE_SIZE));
128: }
129: }
130:
131: } // initState
132:
133: /**
134: * Add the menus for a view mode for paging.
135: */
136: protected void addViewPagingMenus(Menu bar, SessionState state) {
137: bar.add(new MenuEntry(rb.getString("viepag.prev"), (state
138: .getAttribute(STATE_PREV_EXISTS) != null),
139: "doView_prev"));
140: bar.add(new MenuEntry(rb.getString("viepag.next"), (state
141: .getAttribute(STATE_NEXT_EXISTS) != null),
142: "doView_next"));
143:
144: } // addViewPagingMenus
145:
146: /**
147: * Add the menus for a list mode for paging.
148: */
149: protected void addListPagingMenus(Menu bar, SessionState state) {
150: bar.add(new MenuEntry("First Page", (state
151: .getAttribute(STATE_PREV_PAGE_EXISTS) != null),
152: "doList_first"));
153: bar.add(new MenuEntry("Previous Page", (state
154: .getAttribute(STATE_PREV_PAGE_EXISTS) != null),
155: "doList_prev"));
156: bar.add(new MenuEntry("Next Page", (state
157: .getAttribute(STATE_NEXT_PAGE_EXISTS) != null),
158: "doList_next"));
159: bar.add(new MenuEntry("Last Page", (state
160: .getAttribute(STATE_NEXT_PAGE_EXISTS) != null),
161: "doList_last"));
162:
163: } // addListPagingMenus
164:
165: /**
166: * Add the menus for search.
167: */
168: protected void addSearchMenus(Menu bar, SessionState state) {
169: bar.add(new MenuDivider());
170: bar.add(new MenuField(FORM_SEARCH, "toolbar", "doSearch",
171: (String) state.getAttribute(STATE_SEARCH)));
172: bar.add(new MenuEntry(rb.getString("sea.sea"), null, true,
173: MenuItem.CHECKED_NA, "doSearch", "toolbar"));
174: if (state.getAttribute(STATE_SEARCH) != null) {
175: bar.add(new MenuEntry(rb.getString("sea.cleasea"),
176: "doSearch_clear"));
177: }
178:
179: } // addSearchMenus
180:
181: /**
182: * Add the menus for manual / auto - refresh.
183: */
184: protected void addRefreshMenus(Menu bar, SessionState state) {
185: // only offer if there's an observer
186: ObservingCourier observer = (ObservingCourier) state
187: .getAttribute(STATE_OBSERVER);
188: if (observer == null)
189: return;
190:
191: bar.add(new MenuDivider());
192: bar.add(new MenuEntry(
193: (observer.getEnabled() ? rb.getString("ref.manref")
194: : rb.getString("ref.autoref")), "doAuto"));
195: if (!observer.getEnabled()) {
196: bar.add(new MenuEntry(rb.getString("ref.refresh"),
197: "doRefresh"));
198: }
199:
200: } // addRefreshMenus
201:
202: /**
203: * Prepare the current page of messages to display.
204: *
205: * @return List of MailArchiveMessage to display on this page.
206: */
207: protected List prepPage(SessionState state) {
208: List rv = new Vector();
209:
210: // access the page size
211: int pageSize = ((Integer) state.getAttribute(STATE_PAGESIZE))
212: .intValue();
213:
214: // cleanup prior prep
215: state.removeAttribute(STATE_NUM_MESSAGES);
216:
217: // are we going next or prev, first or last page?
218: boolean goNextPage = state.getAttribute(STATE_GO_NEXT_PAGE) != null;
219: boolean goPrevPage = state.getAttribute(STATE_GO_PREV_PAGE) != null;
220: boolean goFirstPage = state.getAttribute(STATE_GO_FIRST_PAGE) != null;
221: boolean goLastPage = state.getAttribute(STATE_GO_LAST_PAGE) != null;
222: state.removeAttribute(STATE_GO_NEXT_PAGE);
223: state.removeAttribute(STATE_GO_PREV_PAGE);
224: state.removeAttribute(STATE_GO_FIRST_PAGE);
225: state.removeAttribute(STATE_GO_LAST_PAGE);
226:
227: // are we going next or prev message?
228: boolean goNext = state.getAttribute(STATE_GO_NEXT) != null;
229: boolean goPrev = state.getAttribute(STATE_GO_PREV) != null;
230: state.removeAttribute(STATE_GO_NEXT);
231: state.removeAttribute(STATE_GO_PREV);
232:
233: // read all channel messages
234: List allMessages = readAllResources(state);
235:
236: if (allMessages == null) {
237: return rv;
238: }
239:
240: // if we have no prev page and do have a top message, then we will stay "pined" to the top
241: boolean pinToTop = ((state.getAttribute(STATE_TOP_PAGE_MESSAGE) != null)
242: && (state.getAttribute(STATE_PREV_PAGE_EXISTS) == null)
243: && !goNextPage
244: && !goPrevPage
245: && !goNext
246: && !goPrev
247: && !goFirstPage && !goLastPage);
248:
249: // if we have no next page and do have a top message, then we will stay "pined" to the bottom
250: boolean pinToBottom = ((state
251: .getAttribute(STATE_TOP_PAGE_MESSAGE) != null)
252: && (state.getAttribute(STATE_NEXT_PAGE_EXISTS) == null)
253: && !goNextPage
254: && !goPrevPage
255: && !goNext
256: && !goPrev
257: && !goFirstPage && !goLastPage);
258:
259: // how many messages, total
260: int numMessages = allMessages.size();
261:
262: if (numMessages == 0) {
263: return rv;
264: }
265:
266: // save the number of messges
267: state
268: .setAttribute(STATE_NUM_MESSAGES, new Integer(
269: numMessages));
270:
271: // find the position of the message that is the top first on the page
272: int posStart = 0;
273: String messageIdAtTheTopOfThePage = (String) state
274: .getAttribute(STATE_TOP_PAGE_MESSAGE);
275: if (messageIdAtTheTopOfThePage != null) {
276: // find the next page
277: posStart = findResourceInList(allMessages,
278: messageIdAtTheTopOfThePage);
279:
280: // if missing, start at the top
281: if (posStart == -1) {
282: posStart = 0;
283: }
284: }
285:
286: // if going to the next page, adjust
287: if (goNextPage) {
288: posStart += pageSize;
289: }
290:
291: // if going to the prev page, adjust
292: else if (goPrevPage) {
293: posStart -= pageSize;
294: if (posStart < 0)
295: posStart = 0;
296: }
297:
298: // if going to the first page, adjust
299: else if (goFirstPage) {
300: posStart = 0;
301: }
302:
303: // if going to the last page, adjust
304: else if (goLastPage) {
305: posStart = numMessages - pageSize;
306: if (posStart < 0)
307: posStart = 0;
308: }
309:
310: // pinning
311: if (pinToTop) {
312: posStart = 0;
313: } else if (pinToBottom) {
314: posStart = numMessages - pageSize;
315: if (posStart < 0)
316: posStart = 0;
317: }
318:
319: // get the last page fully displayed
320: if (posStart + pageSize > numMessages) {
321: posStart = numMessages - pageSize;
322: if (posStart < 0)
323: posStart = 0;
324: }
325:
326: // compute the end to a page size, adjusted for the number of messages available
327: int posEnd = posStart + (pageSize - 1);
328: if (posEnd >= numMessages)
329: posEnd = numMessages - 1;
330: int numMessagesOnThisPage = (posEnd - posStart) + 1;
331:
332: // select the messages on this page
333: for (int i = posStart; i <= posEnd; i++) {
334: rv.add(allMessages.get(i));
335: }
336:
337: // save which message is at the top of the page
338: Entity messageAtTheTopOfThePage = (Entity) allMessages
339: .get(posStart);
340: state.setAttribute(STATE_TOP_PAGE_MESSAGE,
341: messageAtTheTopOfThePage.getId());
342:
343: // which message starts the next page (if any)
344: int next = posStart + pageSize;
345: if (next < numMessages) {
346: state.setAttribute(STATE_NEXT_PAGE_EXISTS, "");
347: } else {
348: state.removeAttribute(STATE_NEXT_PAGE_EXISTS);
349: }
350:
351: // which message ends the prior page (if any)
352: int prev = posStart - 1;
353: if (prev >= 0) {
354: state.setAttribute(STATE_PREV_PAGE_EXISTS, "");
355: } else {
356: state.removeAttribute(STATE_PREV_PAGE_EXISTS);
357: }
358:
359: if (state.getAttribute(STATE_VIEW_ID) != null) {
360: int viewPos = findResourceInList(allMessages,
361: (String) state.getAttribute(STATE_VIEW_ID));
362:
363: // are we moving to the next message
364: if (goNext) {
365: // advance
366: viewPos++;
367: if (viewPos >= numMessages)
368: viewPos = numMessages - 1;
369: }
370:
371: // are we moving to the prev message
372: if (goPrev) {
373: // retreat
374: viewPos--;
375: if (viewPos < 0)
376: viewPos = 0;
377: }
378:
379: // update the view message
380: state.setAttribute(STATE_VIEW_ID, ((Entity) allMessages
381: .get(viewPos)).getId());
382:
383: // if the view message is no longer on the current page, adjust the page
384: // Note: next time through this will get processed
385: if (viewPos < posStart) {
386: state.setAttribute(STATE_GO_PREV_PAGE, "");
387: } else if (viewPos > posEnd) {
388: state.setAttribute(STATE_GO_NEXT_PAGE, "");
389: }
390:
391: if (viewPos > 0) {
392: state.setAttribute(STATE_PREV_EXISTS, "");
393: } else {
394: state.removeAttribute(STATE_PREV_EXISTS);
395: }
396:
397: if (viewPos < numMessages - 1) {
398: state.setAttribute(STATE_NEXT_EXISTS, "");
399: } else {
400: state.removeAttribute(STATE_NEXT_EXISTS);
401: }
402: }
403:
404: return rv;
405:
406: } // prepPage
407:
408: /**
409: * Handle a next-message (view) request.
410: */
411: public void doView_next(RunData runData, Context context) {
412: // access the portlet element id to find our state
413: String peid = ((JetspeedRunData) runData).getJs_peid();
414: SessionState state = ((JetspeedRunData) runData)
415: .getPortletSessionState(peid);
416:
417: // set the flag to go to the next message on the next view
418: state.setAttribute(STATE_GO_NEXT, "");
419:
420: } // doView_next
421:
422: /**
423: * Handle a first-message page (list) request.
424: */
425: public void doList_first(RunData runData, Context context) {
426: // access the portlet element id to find our state
427: String peid = ((JetspeedRunData) runData).getJs_peid();
428: SessionState state = ((JetspeedRunData) runData)
429: .getPortletSessionState(peid);
430:
431: // set the flag to go to the next message on the next view
432: state.setAttribute(STATE_GO_FIRST_PAGE, "");
433:
434: } // doList_first
435:
436: /**
437: * Handle a last-message page (list) request.
438: */
439: public void doList_last(RunData runData, Context context) {
440: // access the portlet element id to find our state
441: String peid = ((JetspeedRunData) runData).getJs_peid();
442: SessionState state = ((JetspeedRunData) runData)
443: .getPortletSessionState(peid);
444:
445: // set the flag to go to the next message on the next view
446: state.setAttribute(STATE_GO_LAST_PAGE, "");
447:
448: } // doList_last
449:
450: /**
451: * Handle a next-page (list) request.
452: */
453: public void doList_next(RunData runData, Context context) {
454: // access the portlet element id to find our state
455: String peid = ((JetspeedRunData) runData).getJs_peid();
456: SessionState state = ((JetspeedRunData) runData)
457: .getPortletSessionState(peid);
458:
459: // set the flag to go to the next page on the next list
460: state.setAttribute(STATE_GO_NEXT_PAGE, "");
461:
462: // %%% ?? doList(runData, context);
463:
464: } // doList_next
465:
466: /**
467: * Handle a prev-message (view) request.
468: */
469: public void doView_prev(RunData runData, Context context) {
470: // access the portlet element id to find our state
471: String peid = ((JetspeedRunData) runData).getJs_peid();
472: SessionState state = ((JetspeedRunData) runData)
473: .getPortletSessionState(peid);
474:
475: // set the flag to go to the prev message on the next view
476: state.setAttribute(STATE_GO_PREV, "");
477:
478: } // doView_prev
479:
480: /**
481: * Handle a prev-page (list) request.
482: */
483: public void doList_prev(RunData runData, Context context) {
484: // access the portlet element id to find our state
485: String peid = ((JetspeedRunData) runData).getJs_peid();
486: SessionState state = ((JetspeedRunData) runData)
487: .getPortletSessionState(peid);
488:
489: // set the flag to go to the prev page on the next list
490: state.setAttribute(STATE_GO_PREV_PAGE, "");
491:
492: } // doList_prev
493:
494: /**
495: * Handle a Search request.
496: */
497: public void doSearch(RunData runData, Context context) {
498: // access the portlet element id to find our state
499: String peid = ((JetspeedRunData) runData).getJs_peid();
500: SessionState state = ((JetspeedRunData) runData)
501: .getPortletSessionState(peid);
502:
503: // read the search form field into the state object
504: String search = StringUtil.trimToNull(runData.getParameters()
505: .getString(FORM_SEARCH));
506:
507: // set the flag to go to the prev page on the next list
508: if (search == null) {
509: state.removeAttribute(STATE_SEARCH);
510: } else {
511: state.setAttribute(STATE_SEARCH, search);
512: }
513:
514: // start paging again from the top of the list
515: resetPaging(state);
516:
517: // if we are searching, turn off auto refresh
518: if (search != null) {
519: ObservingCourier observer = (ObservingCourier) state
520: .getAttribute(STATE_OBSERVER);
521: if (observer != null) {
522: observer.disable();
523: }
524: }
525:
526: // else turn it back on
527: else {
528: enableObserver(state);
529: }
530:
531: } // doSearch
532:
533: /**
534: * Handle a Search Clear request.
535: */
536: public void doSearch_clear(RunData runData, Context context) {
537: // access the portlet element id to find our state
538: String peid = ((JetspeedRunData) runData).getJs_peid();
539: SessionState state = ((JetspeedRunData) runData)
540: .getPortletSessionState(peid);
541:
542: // clear the search
543: state.removeAttribute(STATE_SEARCH);
544:
545: // start paging again from the top of the list
546: resetPaging(state);
547:
548: // turn on auto refresh
549: enableObserver(state);
550:
551: } // doSearch_clear
552:
553: /**
554: * Reset to the first page
555: */
556: protected void resetPaging(SessionState state) {
557: // we are changing the sort, so start from the first page again
558: state.removeAttribute(STATE_TOP_PAGE_MESSAGE);
559:
560: } // resetPaging
561:
562: /**
563: * Find the resource with this id in the list.
564: *
565: * @param messages
566: * The list of messages.
567: * @param id
568: * The message id.
569: * @return The index position in the list of the message with this id, or -1 if not found.
570: */
571: protected int findResourceInList(List resources, String id) {
572: for (int i = 0; i < resources.size(); i++) {
573: // if this is the one, return this index
574: if (((Entity) (resources.get(i))).getId().equals(id))
575: return i;
576: }
577:
578: // not found
579: return -1;
580:
581: } // findResourceInList
582:
583: /**
584: * Toggle auto-update
585: */
586: public void doAuto(RunData data, Context context) {
587: // access the portlet element id to find our state
588: String peid = ((JetspeedRunData) data).getJs_peid();
589: SessionState state = ((JetspeedRunData) data)
590: .getPortletSessionState(peid);
591:
592: // get the observer
593: ObservingCourier observer = (ObservingCourier) state
594: .getAttribute(STATE_OBSERVER);
595: if (observer != null) {
596: boolean enabled = observer.getEnabled();
597: if (enabled) {
598: observer.disable();
599: state.setAttribute(STATE_MANUAL_REFRESH, "manual");
600: } else {
601: observer.enable();
602: state.removeAttribute(STATE_MANUAL_REFRESH);
603: }
604: }
605:
606: } // doAuto
607:
608: /**
609: * The action for when the user want's an update
610: */
611: public void doRefresh(RunData data, Context context) {
612: // access the portlet element id to find our state
613: String peid = ((JetspeedRunData) data).getJs_peid();
614: SessionState state = ((JetspeedRunData) data)
615: .getPortletSessionState(peid);
616:
617: } // doRefresh
618:
619: /**
620: * Enable the observer, unless we are in search mode, where we want it disabled.
621: */
622: public void enableObserver(SessionState state) {
623: // get the observer
624: ObservingCourier observer = (ObservingCourier) state
625: .getAttribute(STATE_OBSERVER);
626: if (observer != null) {
627: // we leave it disabled if we are searching, or if the user has last selected to be manual
628: if ((state.getAttribute(STATE_SEARCH) != null)
629: || (state.getAttribute(STATE_MANUAL_REFRESH) != null)) {
630: observer.disable();
631: } else {
632: observer.enable();
633: }
634: }
635:
636: } // enableObserver
637:
638: } // PagedResourceAction
|