/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/portal/trunk/portal-service-impl/impl/src/java/org/sakaiproject/portal/service/SiteNeighbourhoodServiceImpl.java $ * $Id: SiteNeighbourhoodServiceImpl.java 120416 2013-02-23 01:14:40Z 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.portal.service; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Vector; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.alias.api.Alias; import org.sakaiproject.alias.api.AliasService; import org.sakaiproject.component.api.ServerConfigurationService; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.exception.IdUnusedException; import org.sakaiproject.exception.PermissionException; import org.sakaiproject.portal.api.SiteNeighbourhoodService; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.SiteService; import org.sakaiproject.tool.api.Session; import org.sakaiproject.user.api.Preferences; import org.sakaiproject.user.api.PreferencesService; import org.sakaiproject.user.api.UserDirectoryService; import org.sakaiproject.user.api.UserNotDefinedException; /** * @author ieb */ public class SiteNeighbourhoodServiceImpl implements SiteNeighbourhoodService { private static final String SITE_ALIAS = "/sitealias/"; private static final Log log = LogFactory.getLog(SiteNeighbourhoodServiceImpl.class); private SiteService siteService; private PreferencesService preferencesService; private UserDirectoryService userDirectoryService; private ServerConfigurationService serverConfigurationService; private AliasService aliasService; /** Should all site aliases have a prefix */ private boolean useAliasPrefix = false; private boolean useSiteAliases = false; public void init() { useSiteAliases = serverConfigurationService.getBoolean("portal.use.site.aliases", false); } public void destroy() { } /* * (non-Javadoc) * * @see org.sakaiproject.portal.api.SiteNeighbourhoodService#getSitesAtNode(javax.servlet.http.HttpServletRequest, * org.sakaiproject.tool.api.Session, boolean) */ public List<Site> getSitesAtNode(HttpServletRequest request, Session session, boolean includeMyWorkspace) { return getAllSites(request, session, includeMyWorkspace); } /** * Get All Sites for the current user. If the user is not logged in we * return the list of publically viewable gateway sites. * * @param includeMyWorkspace * When this is true - include the user's My Workspace as the first * parameter. If false, do not include the MyWorkspace anywhere in * the list. Some uses - such as the portlet styled portal or the rss * styled portal simply want all of the sites with the MyWorkspace * first. Other portals like the basic tabbed portal treats My * Workspace separately from all of the rest of the workspaces. * @see org.sakaiproject.portal.api.PortalSiteHelper#getAllSites(javax.servlet.http.HttpServletRequest, * org.sakaiproject.tool.api.Session, boolean) */ public List<Site> getAllSites(HttpServletRequest req, Session session, boolean includeMyWorkspace) { boolean loggedIn = session.getUserId() != null; List<Site> mySites; // collect the Publically Viewable Sites if (!loggedIn) { mySites = getGatewaySites(); return mySites; } // collect the user's sites - don't care whether long descriptions are loaded mySites = siteService.getUserSites(false); // collect the user's preferences List prefExclude = new ArrayList(); List prefOrder = new ArrayList(); if (session.getUserId() != null) { Preferences prefs = preferencesService.getPreferences(session.getUserId()); ResourceProperties props = prefs.getProperties("sakai:portal:sitenav"); List l = props.getPropertyList("exclude"); if (l != null) { prefExclude = l; } l = props.getPropertyList("order"); if (l != null) { prefOrder = l; } } // remove all in exclude from mySites List<Site> visibleSites = new ArrayList<Site>(); for (Site site: mySites) { if ( ! prefExclude.contains(site.getId())) { visibleSites.add(site); } } mySites = visibleSites; // Prepare to put sites in the right order Vector<Site> ordered = new Vector<Site>(); Set<String> added = new HashSet<String>(); // First, place or remove MyWorkspace as requested Site myWorkspace = getMyWorkspace(session); if (myWorkspace != null) { if (includeMyWorkspace) { ordered.add(myWorkspace); added.add(myWorkspace.getId()); } else { int pos = listIndexOf(myWorkspace.getId(), mySites); if (pos != -1) mySites.remove(pos); } } // re-order mySites to have order first, the rest later for (Iterator i = prefOrder.iterator(); i.hasNext();) { String id = (String) i.next(); // find this site in the mySites list int pos = listIndexOf(id, mySites); if (pos != -1) { Site s = mySites.get(pos); if (!added.contains(s.getId())) { ordered.add(s); added.add(s.getId()); } } } // We only do the child processing if we have less than 200 sites boolean haveChildren = false; int siteCount = mySites.size(); // pick up the rest of the top-level-sites for (int i = 0; i < mySites.size(); i++) { Site s = mySites.get(i); if (added.contains(s.getId())) continue; // Once the user takes over the order, // ignore parent/child sorting put all the sites // at the top String ourParent = null; if ( prefOrder.size() == 0 ) { ResourceProperties rp = s.getProperties(); ourParent = rp.getProperty(SiteService.PROP_PARENT_ID); } // System.out.println("Top Site:"+s.getTitle()+" // parent="+ourParent); if (siteCount > 200 || ourParent == null) { // System.out.println("Added at root"); ordered.add(s); added.add(s.getId()); } else { haveChildren = true; } } // If and only if we have some child nodes, we repeatedly // pull up children nodes to be behind their parents // This is O N**2 - so if we had thousands of sites it // it would be costly - hence we only do it for < 200 sites // and limited depth - that makes it O(N) not O(N**2) boolean addedSites = true; int depth = 0; while (depth < 20 && addedSites && haveChildren) { depth++; addedSites = false; haveChildren = false; for (int i = mySites.size() - 1; i >= 0; i--) { Site s = mySites.get(i); if (added.contains(s.getId())) continue; ResourceProperties rp = s.getProperties(); String ourParent = rp.getProperty(SiteService.PROP_PARENT_ID); if (ourParent == null) continue; haveChildren = true; // System.out.println("Child Site:"+s.getTitle()+ // "parent="+ourParent); // Search the already added pages for a parent // or sibling node boolean found = false; int j = -1; for (j = ordered.size() - 1; j >= 0; j--) { Site ps = ordered.get(j); // See if this site is our parent if (ourParent.equals(ps.getId())) { found = true; break; } // See if this site is our sibling rp = ps.getProperties(); String peerParent = rp.getProperty(SiteService.PROP_PARENT_ID); if (ourParent.equals(peerParent)) { found = true; break; } } // We want to insert *after* the identified node j = j + 1; if (found && j >= 0 && j < ordered.size()) { // System.out.println("Added after parent"); ordered.insertElementAt(s, j); added.add(s.getId()); addedSites = true; // Worth going another level deeper } } } // End while depth // If we still have children drop them at the end if (haveChildren) for (int i = 0; i < mySites.size(); i++) { Site s = mySites.get(i); if (added.contains(s.getId())) continue; // System.out.println("Orphan Site:"+s.getId()+" "+s.getTitle()); ordered.add(s); } // All done mySites = ordered; return mySites; } // Get the sites which are to be displayed for the gateway /** * @return */ private List<Site> getGatewaySites() { List<Site> mySites = new ArrayList<Site>(); String[] gatewaySiteIds = getGatewaySiteList(); if (gatewaySiteIds == null) { return mySites; // An empty list - deal with this higher up in the // food chain } // Loop throught the sites making sure they exist and are visitable for (int i = 0; i < gatewaySiteIds.length; i++) { String siteId = gatewaySiteIds[i]; Site site = null; try { site = getSiteVisit(siteId); } catch (IdUnusedException e) { continue; } catch (PermissionException e) { continue; } if (site != null) { mySites.add(site); } } if (mySites.size() < 1) { log.warn("No suitable gateway sites found, gatewaySiteList preference had " + gatewaySiteIds.length + " sites."); } return mySites; } /** * @see org.sakaiproject.portal.api.PortalSiteHelper#getMyWorkspace(org.sakaiproject.tool.api.Session) */ private Site getMyWorkspace(Session session) { String siteId = siteService.getUserSiteId(session.getUserId()); // Make sure we can visit Site site = null; try { site = getSiteVisit(siteId); } catch (IdUnusedException e) { site = null; } catch (PermissionException e) { site = null; } return site; } /** * Find the site in the list that has this id - return the position. * * @param value * The site id to find. * @param siteList * The list of Site objects. * @return The index position in siteList of the site with site id = value, * or -1 if not found. */ private int listIndexOf(String value, List siteList) { for (int i = 0; i < siteList.size(); i++) { Site site = (Site) siteList.get(i); if (site.getId().equals(value)) { return i; } } return -1; } // Return the list of tabs for the anonymous view (Gateway) // If we have a list of sites, return that - if not simply pull in the // single // Gateway site /** * @return */ private String[] getGatewaySiteList() { String gatewaySiteListPref = serverConfigurationService .getString("gatewaySiteList"); if (gatewaySiteListPref == null || gatewaySiteListPref.trim().length() < 1) { gatewaySiteListPref = serverConfigurationService.getGatewaySiteId(); } if (gatewaySiteListPref == null || gatewaySiteListPref.trim().length() < 1) return null; String[] gatewaySites = gatewaySiteListPref.split(","); if (gatewaySites.length < 1) return null; return gatewaySites; } /** * Do the getSiteVisit, but if not found and the id is a user site, try * translating from user EID to ID. * * @param siteId * The Site Id. * @return The Site. * @throws PermissionException * If not allowed. * @throws IdUnusedException * If not found. */ public Site getSiteVisit(String siteId) throws PermissionException, IdUnusedException { try { return siteService.getSiteVisit(siteId); } catch (IdUnusedException e) { if (siteService.isUserSite(siteId)) { try { String userEid = siteService.getSiteUserId(siteId); String userId = userDirectoryService.getUserId(userEid); String alternateSiteId = siteService.getUserSiteId(userId); return siteService.getSiteVisit(alternateSiteId); } catch (UserNotDefinedException ee) { } } // re-throw if that didn't work throw e; } } /** * @return the preferencesService */ public PreferencesService getPreferencesService() { return preferencesService; } /** * @param preferencesService * the preferencesService to set */ public void setPreferencesService(PreferencesService preferencesService) { this.preferencesService = preferencesService; } /** * @return the serverConfigurationService */ public ServerConfigurationService getServerConfigurationService() { return serverConfigurationService; } /** * @param serverConfigurationService * the serverConfigurationService to set */ public void setServerConfigurationService( ServerConfigurationService serverConfigurationService) { this.serverConfigurationService = serverConfigurationService; } /** * @return the siteService */ public SiteService getSiteService() { return siteService; } /** * @param siteService * the siteService to set */ public void setSiteService(SiteService siteService) { this.siteService = siteService; } /** * @return the userDirectoryService */ public UserDirectoryService getUserDirectoryService() { return userDirectoryService; } /** * @param userDirectoryService * the userDirectoryService to set */ public void setUserDirectoryService(UserDirectoryService userDirectoryService) { this.userDirectoryService = userDirectoryService; } public String lookupSiteAlias(String id, String context) { if (!useSiteAliases) { return null; } List<Alias> aliases = aliasService.getAliases(id); if (aliases.size() > 0) { if (aliases.size() > 1 && log.isInfoEnabled()) { if (log.isDebugEnabled()) { log.debug("More than one alias for "+ id+ " sorting."); } Collections.sort(aliases, new Comparator<Alias>() { public int compare(Alias o1, Alias o2) { return o1.getId().compareTo(o2.getId()); } }); } for (Alias alias : aliases) { String aliasId = alias.getId(); boolean startsWithPrefix = aliasId.startsWith(SITE_ALIAS); if (startsWithPrefix) { if (useAliasPrefix) { return aliasId.substring(SITE_ALIAS.length()); } } else { if (!useAliasPrefix) { return aliasId; } } } } return null; } public String parseSiteAlias(String url) { String id = ((useAliasPrefix)?SITE_ALIAS:"")+url; try { String reference = aliasService.getTarget(id); return reference; } catch (IdUnusedException e) { if (log.isDebugEnabled()) { log.debug("No alias found for "+ id); } } return null; } public void setAliasService(AliasService aliasService) { this.aliasService = aliasService; } public boolean isUseAliasPrefix() { return useAliasPrefix; } public void setUseAliasPrefix(boolean useAliasPrefix) { this.useAliasPrefix = useAliasPrefix; } }