001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/calendar/tags/sakai_2-4-1/mergedlist-util/util/src/java/org/sakaiproject/util/MergedList.java $
003: * $Id: MergedList.java 8050 2006-04-20 17:39:55Z 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.util;
021:
022: import java.util.ArrayList;
023: import java.util.Collections;
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.Map;
028:
029: import org.sakaiproject.entity.api.ResourceProperties;
030: import org.sakaiproject.site.api.Site;
031: import org.sakaiproject.site.cover.SiteService;
032:
033: /**
034: * Contains the list of merged/non-merged channels
035: */
036: public class MergedList extends ArrayList {
037: /**
038: * Used to create a reference. This is unique to each caller, so we
039: * need an interface.
040: */
041: public interface ChannelReferenceMaker {
042:
043: String makeReference(String siteId);
044: }
045:
046: /**
047: * channel entry used to communicate with the Velocity templates when dealing with merged channels.
048: */
049: public interface MergedEntry extends Comparable {
050: /**
051: * Returns the display string for the channel.
052: */
053: public String getDisplayName();
054:
055: /**
056: * Returns the ID of the group. (The ID is used as a key.)
057: */
058: public String getReference();
059:
060: /**
061: * Returns true if this channel is currently being merged.
062: */
063: public boolean isMerged();
064:
065: /**
066: * Marks this channel as being merged or not.
067: */
068: public void setMerged(boolean b);
069:
070: /**
071: * This returns true if this list item should be visible to the user.
072: */
073: public boolean isVisible();
074:
075: /**
076: * Implemented so that we can order by the group full name.
077: */
078: public int compareTo(Object arg0);
079: }
080:
081: /**
082: * This interface is used to describe a generic list entry provider so that
083: * a variety of list entries can be used. This currently serves merged sites
084: * for the schedule and merged channels for announcements.
085: */
086: public interface EntryProvider {
087: /**
088: * Gets an iterator for the channels, calendars, etc.
089: */
090: public Iterator getIterator();
091:
092: /**
093: * See if we can do a "get" on the calendar, channel, etc.
094: */
095: public boolean allowGet(String ref);
096:
097: /**
098: * Generically access the context of the resource provided
099: * by the getIterator() call.
100: * @return The context.
101: */
102: public String getContext(Object obj);
103:
104: /**
105: * Generically access the reference of the resource provided
106: * by the getIterator() call.
107: */
108: public String getReference(Object obj);
109:
110: /**
111: * Generically access the resource's properties.
112: * @return The resource's properties.
113: */
114: public ResourceProperties getProperties(Object obj);
115:
116: public boolean isUserChannel(Object channel);
117:
118: public boolean isSpecialSite(Object channel);
119:
120: public String getSiteUserId(Object channel);
121:
122: public Site getSite(Object channel);
123:
124: }
125:
126: /** This is used to separate group names in the config parameter. */
127: static private final String ID_DELIMITER = "_,_";
128:
129: /**
130: * Implementation of channel entry used for rendering the list of merged channels
131: */
132: private class MergedChannelEntryImpl implements MergedEntry {
133: final private String channelReference;
134: final private String channelFullName;
135: private boolean merged;
136: private boolean visible;
137:
138: public MergedChannelEntryImpl(String channelReference,
139: String channelFullName, boolean merged, boolean visible) {
140: this .channelReference = channelReference;
141: this .channelFullName = channelFullName;
142: this .merged = merged;
143: this .visible = visible;
144: }
145:
146: /* (non-Javadoc)
147: * @see org.chefproject.actions.channelAction.MergedCalenderEntry#getchannelDisplayName()
148: */
149: public String getDisplayName() {
150: return channelFullName;
151: }
152:
153: /* (non-Javadoc)
154: * @see org.chefproject.actions.channelAction.MergedCalenderEntry#getchannelReference()
155: */
156: public String getReference() {
157: return channelReference;
158: }
159:
160: /* (non-Javadoc)
161: * @see org.chefproject.actions.channelAction.MergedCalenderEntry#isMerged()
162: */
163: public boolean isMerged() {
164: return merged;
165: }
166:
167: /* (non-Javadoc)
168: * @see org.chefproject.actions.channelAction.MergedCalenderEntry#setMerged(boolean)
169: */
170: public void setMerged(boolean b) {
171: merged = b;
172: }
173:
174: /* (non-Javadoc)
175: * @see java.lang.Comparable#compareTo(java.lang.Object)
176: */
177: public int compareTo(Object arg0) {
178: MergedChannelEntryImpl compObj = (MergedChannelEntryImpl) arg0;
179:
180: return this .getDisplayName().compareTo(
181: compObj.getDisplayName());
182: }
183:
184: /* (non-Javadoc)
185: * @see org.chefproject.actions.channelAction.MergedCalenderEntry#isVisible()
186: */
187: public boolean isVisible() {
188: return visible;
189: }
190: }
191:
192: /**
193: * Selects and loads channels from a list provided by the entryProvider
194: * parameter. The algorithm for loading channels is a bit complex, and
195: * depends on whether or not the user is currently in their "My Workspace", etc.
196: *
197: * This function formerly filtered through a list of all sites. It still
198: * goes through the motions of filtering, and deciding how to flag the channels
199: * as to whether or not they are merged, hidden, etc. However, it has been
200: * modified to take all of its information from an EntryProvider parameter,
201: * This list is now customized and is no longer "all sites in existence".
202: * When sites are being selected for merging, this list can be quite long.
203: * This function is more often called just to display merged events, so
204: * passing a more restricted list makes for better performance.
205: *
206: * At some point we could condense redundant logic, but this modification
207: * was performed under the time constraints of a release. So, an effort was
208: * made not to tinker with the logic, so much as to reduce the set of data
209: * that the function had to process.
210: *
211: */
212: public void loadChannelsFromDelimitedString(
213: boolean isOnWorkspaceTab, EntryProvider entryProvider,
214: String userId, String[] channelArray, boolean isSuperUser,
215: String currentSiteId) {
216: // Remove any initial list contents.
217: this .clear();
218:
219: // We'll need a map since we want to test for the
220: // presence of channels without searching through a list.
221: Map currentlyMergedchannels = makeChannelMap(channelArray);
222:
223: // Loop through the channels that the EntryProvider gives us.
224: Iterator it = entryProvider.getIterator();
225:
226: while (it.hasNext()) {
227: Object channel = it.next();
228:
229: // Watch out for null channels. Ignore them if they are there.
230: if (channel == null) {
231: continue;
232: }
233:
234: // If true, this channel will be added to the list of
235: // channels that may be merged.
236: boolean addThisChannel = false;
237:
238: // If true, this channel will be marked as "merged".
239: boolean merged = false;
240:
241: // If true, then this channel will be in the list, but will not
242: // be shown to the user.
243: boolean hidden = false;
244:
245: // If true, this is the channel associated with the current
246: // user.
247: boolean this IsTheUsersMyWorkspaceChannel = false;
248:
249: // If true, this is a user channel.
250: boolean this IsUserChannel = entryProvider
251: .isUserChannel(channel);
252:
253: // If true, this is a "special" site.
254: boolean isSpecialSite = entryProvider
255: .isSpecialSite(channel);
256:
257: if (this IsUserChannel
258: && userId.equals(entryProvider
259: .getSiteUserId(channel))) {
260: this IsTheUsersMyWorkspaceChannel = true;
261: }
262:
263: //
264: // Don't put the channels of other users in the merge list.
265: // Go to the next item in the loop.
266: //
267: if (this IsUserChannel && !this IsTheUsersMyWorkspaceChannel) {
268: continue;
269: }
270:
271: // Only add to the list if the user can access this channel.
272: if (entryProvider.allowGet(entryProvider
273: .getReference(channel))) {
274: // Merge *almost* everything the user can access.
275: if (this IsTheUsersMyWorkspaceChannel) {
276: // Don't merge the user's channel in with a
277: // group channel. If we're on the "My Workspace"
278: // tab, then it's okay to merge.
279: if (isOnWorkspaceTab) {
280: merged = true;
281: } else {
282: merged = false;
283: }
284: } else {
285: //
286: // If we're the admin, and we're on our "My Workspace" tab, then only
287: // use our channel (handled above). We'd be overloaded if we could
288: // see everyone's events.
289: //
290: if (isSuperUser && isOnWorkspaceTab) {
291: merged = false;
292: } else {
293: // Set it to merged if the channel was specified in the merged
294: // channel list that we got from the portlet configuration.
295: if (isOnWorkspaceTab) {
296: merged = true;
297: } else {
298: merged = currentlyMergedchannels
299: .containsKey(entryProvider
300: .getReference(channel));
301: }
302: }
303: }
304:
305: addThisChannel = true;
306:
307: // Hide user or "special" sites from the user interface merge list.
308: if (this IsUserChannel || isSpecialSite) {
309: // Hide the user's own channel from them.
310: hidden = true;
311: }
312: }
313:
314: if (addThisChannel) {
315: String siteDisplayName = "";
316:
317: // There is no point in getting the display name for hidden
318: // items.
319: if (!hidden) {
320: String displayNameProperty = entryProvider
321: .getProperties(channel).getProperty(
322: entryProvider
323: .getProperties(channel)
324: .getNamePropDisplayName());
325:
326: // If the channel has a displayName property and use that
327: // instead.
328: if (displayNameProperty != null
329: && displayNameProperty.length() != 0) {
330: siteDisplayName = displayNameProperty;
331: } else {
332: String channelName = "";
333:
334: Site site = entryProvider.getSite(channel);
335:
336: if (site != null) {
337: boolean isCurrentSite = currentSiteId
338: .equals(site.getId());
339:
340: //
341: // Hide and force the current site to be merged.
342: //
343: if (isCurrentSite) {
344: hidden = true;
345: merged = true;
346: } else {
347: // Else just get the name.
348: channelName = site.getTitle();
349: siteDisplayName = channelName + " ("
350: + site.getId() + ") ";
351: }
352: }
353: }
354: }
355:
356: this .add(new MergedChannelEntryImpl(entryProvider
357: .getReference(channel), siteDisplayName,
358: merged, !hidden));
359: }
360: }
361:
362: // MergedchannelEntry implements Comparable, so the sort will work correctly.
363: Collections.sort(this );
364: } // loadFromPortletConfig
365:
366: /**
367: * Forms an array of all channel references to which the user has read access.
368: */
369: public String[] getAllPermittedChannels(
370: ChannelReferenceMaker refMaker) {
371: List finalList = new ArrayList();
372: String[] returnArray = null;
373:
374: List siteList = SiteService
375: .getSites(
376: org.sakaiproject.site.api.SiteService.SelectionType.ACCESS,
377: null,
378: null,
379: null,
380: org.sakaiproject.site.api.SiteService.SortType.TITLE_ASC,
381: null);
382:
383: Iterator it = siteList.iterator();
384:
385: // Add all the references to the list.
386: while (it.hasNext()) {
387: Site site = (Site) it.next();
388: finalList.add(refMaker.makeReference(site.getId()));
389: }
390:
391: // Make the array that we'll return
392: returnArray = new String[finalList.size()];
393:
394: for (int i = 0; i < finalList.size(); i++) {
395: returnArray[i] = (String) finalList.get(i);
396: }
397:
398: return returnArray;
399: }
400:
401: /**
402: * This gets a list of channels from the portlet configuration information.
403: * Channels here can really be a channel or a schedule from a site.
404: */
405: public String[] getChannelReferenceArrayFromDelimitedString(
406: String primarychannelReference,
407: String mergedInitParameterValue) {
408: String mergedChannels = null;
409:
410: // Get a list of the currently merged channels. This is a delimited list.
411: mergedChannels = StringUtil
412: .trimToNull(mergedInitParameterValue);
413:
414: String[] mergedChannelArray = null;
415:
416: // Split the configuration string into an array of channel references.
417: if (mergedChannels != null) {
418: mergedChannelArray = mergedChannels.split(ID_DELIMITER);
419: } else {
420: // If there are no merged channels, default to the primary channel.
421: mergedChannelArray = new String[1];
422: mergedChannelArray[0] = primarychannelReference;
423: }
424:
425: return mergedChannelArray;
426: } // getChannelReferenceArrayFromDelimitedString
427:
428: /**
429: * Create a channel reference map from an array of channel references.
430: */
431: private Map makeChannelMap(String[] mergedChannelArray) {
432: // Make a map of those channels that are currently merged.
433: Map currentlyMergedchannels = new HashMap();
434:
435: if (mergedChannelArray != null) {
436: for (int i = 0; i < mergedChannelArray.length; i++) {
437: currentlyMergedchannels.put(mergedChannelArray[i],
438: Boolean.valueOf(true));
439: }
440: }
441: return currentlyMergedchannels;
442: }
443:
444: /**
445: * Loads data input by the user into this list and then saves the list to
446: * the portlet config information. The initContextForMergeOptions() function
447: * must have previously been called.
448: */
449: public void loadFromRunData(ParameterParser params) {
450: Iterator it = this .iterator();
451:
452: while (it.hasNext()) {
453: MergedEntry entry = (MergedEntry) it.next();
454:
455: // If the group is even mentioned in the parameters, then
456: // it means that the checkbox was selected. Deselected checkboxes
457: // will not be present in the parameter list.
458: if (params.getString(entry.getReference()) != null) {
459: entry.setMerged(true);
460: } else {
461: //
462: // If the entry isn't visible, then we can't "unmerge" it due to
463: // the lack of a checkbox in the user interface.
464: //
465: if (entry.isVisible()) {
466: entry.setMerged(false);
467: }
468: }
469: }
470: }
471:
472: /**
473: * Loads data input by the user into this list and then saves the list to
474: * the portlet config information. The initContextForMergeOptions() function
475: * must have previously been called.
476: */
477: public String getDelimitedChannelReferenceString() {
478: StringBuffer mergedReferences = new StringBuffer("");
479:
480: Iterator it = this .iterator();
481: boolean firstEntry = true;
482:
483: while (it.hasNext()) {
484: MergedEntry entry = (MergedEntry) it.next();
485:
486: if (entry.isMerged()) {
487: // Add a delimiter, if appropriate.
488: if (!firstEntry) {
489: mergedReferences.append(ID_DELIMITER);
490: } else {
491: firstEntry = false;
492: }
493:
494: // Add to our list
495: mergedReferences.append(entry.getReference());
496: }
497: }
498:
499: // Return the delimited list of merged references
500: return mergedReferences.toString();
501: }
502:
503: /**
504: * Returns an array of merged references.
505: */
506: public List getReferenceList() {
507: List references = new ArrayList();
508:
509: Iterator it = this .iterator();
510:
511: while (it.hasNext()) {
512: MergedEntry mergedEntry = (MergedEntry) it.next();
513:
514: // Only add it to the list if it has been merged.
515: if (mergedEntry.isMerged()) {
516: references.add(mergedEntry.getReference());
517: }
518: }
519:
520: return references;
521: }
522: }
|