/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/site/trunk/mergedlist-util/util/src/java/org/sakaiproject/util/MergedList.java $
* $Id: MergedList.java 120414 2013-02-23 01:14:36Z botimer@umich.edu $
***********************************************************************************
*
* Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 The Sakai Foundation
*
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.opensource.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
**********************************************************************************/
package org.sakaiproject.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.sakaiproject.entity.api.ResourceProperties;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.cover.SiteService;
/**
* Contains the list of merged/non-merged channels
*/
public class MergedList extends ArrayList
{
/**
* Used to create a reference. This is unique to each caller, so we
* need an interface.
*/
public interface ChannelReferenceMaker
{
String makeReference(String siteId);
}
/**
* channel entry used to communicate with the Velocity templates when dealing with merged channels.
*/
public interface MergedEntry extends Comparable
{
/**
* Returns the display string for the channel.
*/
public String getDisplayName();
/**
* Returns the ID of the group. (The ID is used as a key.)
*/
public String getReference();
/**
* Returns true if this channel is currently being merged.
*/
public boolean isMerged();
/**
* Marks this channel as being merged or not.
*/
public void setMerged(boolean b);
/**
* This returns true if this list item should be visible to the user.
*/
public boolean isVisible();
/**
* Implemented so that we can order by the group full name.
*/
public int compareTo(Object arg0);
}
/**
* This interface is used to describe a generic list entry provider so that
* a variety of list entries can be used. This currently serves merged sites
* for the schedule and merged channels for announcements.
*/
public interface EntryProvider
{
/**
* Gets an iterator for the channels, calendars, etc.
*/
public Iterator getIterator();
/**
* See if we can do a "get" on the calendar, channel, etc.
*/
public boolean allowGet(String ref);
/**
* Generically access the context of the resource provided
* by the getIterator() call.
* @return The context.
*/
public String getContext(Object obj);
/**
* Generically access the reference of the resource provided
* by the getIterator() call.
*/
public String getReference(Object obj);
/**
* Generically access the resource's properties.
* @return The resource's properties.
*/
public ResourceProperties getProperties(Object obj);
public boolean isUserChannel(Object channel);
public boolean isSpecialSite(Object channel);
public String getSiteUserId(Object channel);
public Site getSite(Object channel);
}
/** This is used to separate group names in the config parameter. */
static private final String ID_DELIMITER = "_,_";
/**
* Implementation of channel entry used for rendering the list of merged channels
*/
private class MergedChannelEntryImpl implements MergedEntry
{
final private String channelReference;
final private String channelFullName;
private boolean merged;
private boolean visible;
public MergedChannelEntryImpl(
String channelReference,
String channelFullName,
boolean merged,
boolean visible)
{
this.channelReference = channelReference;
this.channelFullName = channelFullName;
this.merged = merged;
this.visible = visible;
}
/* (non-Javadoc)
* @see org.chefproject.actions.channelAction.MergedCalenderEntry#getchannelDisplayName()
*/
public String getDisplayName()
{
return channelFullName;
}
/* (non-Javadoc)
* @see org.chefproject.actions.channelAction.MergedCalenderEntry#getchannelReference()
*/
public String getReference()
{
return channelReference;
}
/* (non-Javadoc)
* @see org.chefproject.actions.channelAction.MergedCalenderEntry#isMerged()
*/
public boolean isMerged()
{
return merged;
}
/* (non-Javadoc)
* @see org.chefproject.actions.channelAction.MergedCalenderEntry#setMerged(boolean)
*/
public void setMerged(boolean b)
{
merged = b;
}
/* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
public int compareTo(Object arg0)
{
MergedChannelEntryImpl compObj = (MergedChannelEntryImpl) arg0;
return this.getDisplayName().compareTo(compObj.getDisplayName());
}
/* (non-Javadoc)
* @see org.chefproject.actions.channelAction.MergedCalenderEntry#isVisible()
*/
public boolean isVisible()
{
return visible;
}
}
/**
* loadChannelsFromDelimitedString
*
* Selects and loads channels from a list provided by the entryProvider
* parameter. The algorithm for loading channels is a bit complex, and
* depends on whether or not the user is currently in their "My Workspace", etc.
*
* This function formerly filtered through a list of all sites. It still
* goes through the motions of filtering, and deciding how to flag the channels
* as to whether or not they are merged, hidden, etc. However, it has been
* modified to take all of its information from an EntryProvider parameter,
* This list is now customized and is no longer "all sites in existence".
* When sites are being selected for merging, this list can be quite long.
* This function is more often called just to display merged events, so
* passing a more restricted list makes for better performance.
*
* At some point we could condense redundant logic, but this modification
* was performed under the time constraints of a release. So, an effort was
* made not to tinker with the logic, so much as to reduce the set of data
* that the function had to process.
*
* @param isOnWorkspaceTab - true if this is the user's my workspace
* @param entryProvider - provides available channels for load/merge
* @param userId - current userId
* @param channelArray - array of selected channels for load/merge
* @param isSuperUser - if true, then don't merge all available channels
* @param currentSiteId - current worksite
*/
public void loadChannelsFromDelimitedString(
boolean isOnWorkspaceTab,
EntryProvider entryProvider, String userId, String[] channelArray,
boolean isSuperUser, String currentSiteId)
{
loadChannelsFromDelimitedString( isOnWorkspaceTab, true, entryProvider,
userId, channelArray, isSuperUser, currentSiteId );
}
/**
* loadChannelsFromDelimitedString
*
* (see description on above method)
*
* @param isOnWorkspaceTab - true if this is the user's my workspace
* @param mergeAllOnWorkspaceTab - if true, merge all channels in channelArray
* @param entryProvider - provides available channels for load/merge
* @param userId - current userId
* @param channelArray - array of selected channels for load/merge
* @param isSuperUser - if true, then don't merge all available channels
* @param currentSiteId - current worksite
*/
public void loadChannelsFromDelimitedString(
boolean isOnWorkspaceTab,
boolean mergeAllOnWorkspaceTab,
EntryProvider entryProvider, String userId, String[] channelArray,
boolean isSuperUser, String currentSiteId)
{
// Remove any initial list contents.
this.clear();
// We'll need a map since we want to test for the
// presence of channels without searching through a list.
Map currentlyMergedchannels = makeChannelMap(channelArray);
// Loop through the channels that the EntryProvider gives us.
Iterator it = entryProvider.getIterator();
while (it.hasNext())
{
Object channel = it.next();
// Watch out for null channels. Ignore them if they are there.
if ( channel == null )
{
continue;
}
// If true, this channel will be added to the list of
// channels that may be merged.
boolean addThisChannel = false;
// If true, this channel will be marked as "merged".
boolean merged = false;
// If true, then this channel will be in the list, but will not
// be shown to the user.
boolean hidden = false;
// If true, this is a user channel.
boolean thisIsUserChannel = entryProvider.isUserChannel(channel);
// If true, this is a "special" site.
boolean isSpecialSite = entryProvider.isSpecialSite(channel);
// If true, this is the channel associated with the current
// user.
boolean thisIsTheUsersMyWorkspaceChannel = false;
if ( thisIsUserChannel
&& userId.equals(
entryProvider.getSiteUserId(channel)) )
{
thisIsTheUsersMyWorkspaceChannel = true;
}
//
// Don't put the channels of other users in the merge list.
// Go to the next item in the loop.
//
if (thisIsUserChannel && !thisIsTheUsersMyWorkspaceChannel)
{
continue;
}
// Only add to the list if the user can access this channel.
if (entryProvider.allowGet(entryProvider.getReference(channel)))
{
// Merge *almost* everything the user can access.
if (thisIsTheUsersMyWorkspaceChannel)
{
// Don't merge the user's channel in with a
// group channel. If we're on the "My Workspace"
// tab, then it's okay to merge.
if (isOnWorkspaceTab)
{
merged = true;
}
else
{
merged = false;
}
}
else
{
//
// If we're the admin, and we're on our "My Workspace" tab, then only
// use our channel (handled above). We'd be overloaded if we could
// see everyone's events.
//
if (isSuperUser && isOnWorkspaceTab)
{
merged = false;
}
else
{
// merge all sites if onWorkspaceTab and mergeAll is enabled
if (isOnWorkspaceTab && mergeAllOnWorkspaceTab)
{
merged = true;
}
// Set it to merged if the channel was specified in the merged
// channel list that we got from the portlet configuration.
else
{
merged =
currentlyMergedchannels.containsKey(
entryProvider.getReference(channel));
}
}
}
addThisChannel = true;
// Hide user or "special" sites from the user interface merge list.
if (thisIsUserChannel || isSpecialSite)
{
// Hide the user's own channel from them.
hidden = true;
}
}
if (addThisChannel)
{
String siteDisplayName = "";
// There is no point in getting the display name for hidden items
if (!hidden)
{
String displayNameProperty = entryProvider.getProperties(
channel).getProperty(
entryProvider.getProperties(channel)
.getNamePropDisplayName());
// If the channel has a displayName property and use that
// instead.
if (displayNameProperty != null
&& displayNameProperty.length() != 0)
{
siteDisplayName = displayNameProperty;
}
else
{
String channelName = "";
Site site = entryProvider.getSite(channel);
if (site != null)
{
boolean isCurrentSite = currentSiteId.equals(site.getId());
//
// Hide and force the current site to be merged.
//
if (isCurrentSite)
{
hidden = true;
merged = true;
}
else
{
// Else just get the name.
channelName = site.getTitle();
siteDisplayName = channelName + " ("
+ site.getId() + ") ";
}
}
}
}
this.add(
new MergedChannelEntryImpl(
entryProvider.getReference(channel),
siteDisplayName,
merged,
!hidden));
}
}
// MergedchannelEntry implements Comparable, so the sort will work correctly.
Collections.sort(this);
} // loadFromPortletConfig
/**
* Forms an array of all channel references to which the user has read access.
*/
public String[] getAllPermittedChannels(ChannelReferenceMaker refMaker)
{
List finalList = new ArrayList();
String [] returnArray = null;
// Get all accessible sites for the current user, not requiring descriptions
List siteList = SiteService.getUserSites(false);
Iterator it = siteList.iterator();
// Add all the references to the list.
while ( it.hasNext() )
{
Site site = (Site) it.next();
finalList.add(refMaker.makeReference(site.getId()));
}
// Make the array that we'll return
returnArray = new String[finalList.size()];
for ( int i=0; i < finalList.size(); i++ )
{
returnArray[i] = (String) finalList.get(i);
}
return returnArray;
}
/**
* This gets a list of channels from the portlet configuration information.
* Channels here can really be a channel or a schedule from a site.
*/
public String[] getChannelReferenceArrayFromDelimitedString(
String primarychannelReference,
String mergedInitParameterValue)
{
String mergedChannels = null;
// Get a list of the currently merged channels. This is a delimited list.
mergedChannels =
StringUtil.trimToNull(
mergedInitParameterValue);
String[] mergedChannelArray = null;
// Split the configuration string into an array of channel references.
if (mergedChannels != null)
{
mergedChannelArray = mergedChannels.split(ID_DELIMITER);
}
else
{
// If there are no merged channels, default to the primary channel.
mergedChannelArray = new String[1];
mergedChannelArray[0] = primarychannelReference;
}
return mergedChannelArray;
} // getChannelReferenceArrayFromDelimitedString
/**
* Create a channel reference map from an array of channel references.
*/
private Map makeChannelMap(String[] mergedChannelArray)
{
// Make a map of those channels that are currently merged.
Map currentlyMergedchannels = new HashMap();
if (mergedChannelArray != null)
{
for (int i = 0; i < mergedChannelArray.length; i++)
{
currentlyMergedchannels.put(
mergedChannelArray[i],
Boolean.valueOf(true));
}
}
return currentlyMergedchannels;
}
/**
* Loads data input by the user into this list and then saves the list to
* the portlet config information. The initContextForMergeOptions() function
* must have previously been called.
*/
public void loadFromRunData(ParameterParser params)
{
Iterator it = this.iterator();
while (it.hasNext())
{
MergedEntry entry = (MergedEntry) it.next();
// If the group is even mentioned in the parameters, then
// it means that the checkbox was selected. Deselected checkboxes
// will not be present in the parameter list.
if (params.getString(entry.getReference())
!= null)
{
entry.setMerged(true);
}
else
{
//
// If the entry isn't visible, then we can't "unmerge" it due to
// the lack of a checkbox in the user interface.
//
if (entry.isVisible())
{
entry.setMerged(false);
}
}
}
}
/**
* Loads data input by the user into this list and then saves the list to
* the portlet config information. The initContextForMergeOptions() function
* must have previously been called.
*/
public String getDelimitedChannelReferenceString()
{
StringBuilder mergedReferences = new StringBuilder("");
Iterator it = this.iterator();
boolean firstEntry = true;
while (it.hasNext())
{
MergedEntry entry = (MergedEntry) it.next();
if (entry.isMerged())
{
// Add a delimiter, if appropriate.
if ( !firstEntry )
{
mergedReferences.append(ID_DELIMITER);
}
else
{
firstEntry = false;
}
// Add to our list
mergedReferences.append(entry.getReference());
}
}
// Return the delimited list of merged references
return mergedReferences.toString();
}
/**
* Returns an array of merged references.
*/
public List getReferenceList()
{
List references = new ArrayList();
Iterator it = this.iterator();
while (it.hasNext())
{
MergedEntry mergedEntry = (MergedEntry) it.next();
// Only add it to the list if it has been merged.
if (mergedEntry.isMerged())
{
references.add(mergedEntry.getReference());
}
}
return references;
}
}