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