/**********************************************************************************
* $URL: https://source.sakaiproject.org/contrib/ufp/usermembership/trunk/tool/src/java/org/sakaiproject/umem/tool/ui/SiteListBean.java $
* $Id: SiteListBean.java 4381 2007-03-21 11:25:54Z nuno@ufp.pt $
***********************************************************************************
*
* 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.umem.tool.ui;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.Collator;
import java.text.ParseException;
import java.text.RuleBasedCollator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.authz.api.Role;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.db.api.SqlService;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.site.api.Group;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.site.api.SiteService.SelectionType;
import org.sakaiproject.site.api.SiteService.SortType;
import org.sakaiproject.tool.api.Session;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.tool.api.ToolManager;
import org.sakaiproject.umem.api.Authz;
import org.sakaiproject.user.api.UserNotDefinedException;
import org.sakaiproject.user.cover.UserDirectoryService;
import org.sakaiproject.util.ResourceLoader;
/**
* @author <a href="mailto:nuno@ufp.pt">Nuno Fernandes</a>
*/
public class SiteListBean {
private static final long serialVersionUID = 2L;
private static final String SORT_SITE_NAME = "siteName";
private static final String SORT_GROUPS_TYPE = "groups";
private static final String SORT_SITE_TYPE = "siteType";
private static final String SORT_SITE_RID = "roleId";
private static final String SORT_SITE_PV = "published";
private static final String SORT_USER_STATUS = "userStatus";
private static final String SORT_SITE_TERM = "siteTerm";
/** Our log (commons). */
private static Log LOG = LogFactory.getLog(SiteListBean.class);
/** Resource bundle */
private transient ResourceLoader msgs = new ResourceLoader("org.sakaiproject.umem.tool.bundle.Messages");
/** Controller fields */
private List userSitesRows;
/** Getter vars */
private boolean refreshQuery = false;
private boolean allowed = false;
private String thisUserId = null;
private String userId = null;
private boolean sitesSortAscending = true;
private String sitesSortColumn = SORT_SITE_NAME;
/** Resource properties */
private final static String PROP_SITE_TERM = "term";
/** Sakai APIs */
private SessionManager M_session = (SessionManager) ComponentManager.get(SessionManager.class.getName());
private SqlService M_sql = (SqlService) ComponentManager.get(SqlService.class.getName());
private SiteService M_site = (SiteService) ComponentManager.get(SiteService.class.getName());
private ToolManager M_tm = (ToolManager) ComponentManager.get(ToolManager.class.getName());
private Authz authz = (Authz) ComponentManager.get(Authz.class.getName());
private ServerConfigurationService M_scf = (ServerConfigurationService) ComponentManager.get(ServerConfigurationService.class.getName());
/** Private vars */
private RuleBasedCollator collator;
private long timeSpentInGroups = 0;
private String portalURL = M_scf.getPortalUrl();
private String message = "";
// ######################################################################################
// UserSitesRow CLASS
// ######################################################################################
public class UserSitesRow implements Serializable {
private static final long serialVersionUID = 1L;
private Site site;
private String siteId;
private String siteTitle;
private String siteType;
private String siteURL;
private String groups;
private String roleName;
private String pubView;
private String userStatus;
private String siteTerm;
{
try{
collator= new RuleBasedCollator(((RuleBasedCollator)Collator.getInstance()).getRules().replaceAll("<'\u005f'", "<' '<'\u005f'"));
}catch(ParseException e){
collator = (RuleBasedCollator)Collator.getInstance();
}
}
public UserSitesRow() {
}
public UserSitesRow(String siteId, String siteTitle, String siteType, String groups, String roleName, String pubView, String userStatus, String term) {
this.siteId = siteId;
this.siteTitle = siteTitle;
this.siteType = siteType;
this.groups = groups;
this.roleName = roleName;
this.pubView = pubView;
this.userStatus = userStatus;
this.siteTerm = term;
}
public UserSitesRow(Site site, String groups, String roleName) {
this.siteId = site.getId();
this.siteTitle = site.getTitle();
this.siteType = site.getType();
this.groups = groups;
this.roleName = roleName;
this.pubView = site.isPublished() ? msgs.getString("status_published") : msgs.getString("status_unpublished");
this.userStatus = site.getMember(userId).isActive() ? msgs.getString("site_user_status_active") : msgs.getString("site_user_status_inactive");
this.siteTerm = site.getProperties().getProperty(PROP_SITE_TERM);
}
public String getSiteId() {
return siteId;
}
public String getSiteTitle() {
return siteTitle;
}
public String getSiteType() {
return siteType;
}
public String getSiteURL() {
StringBuilder siteUrl = new StringBuilder();
siteUrl.append(portalURL);
siteUrl.append("/site/");
siteUrl.append(siteId);
return siteUrl.toString();
}
public String getGroups() {
return groups;
}
public String getRoleName() {
return roleName;
}
public String getPubView() {
return pubView;
}
public String getUserStatus(){
return this.userStatus;
}
public String getSiteTerm() {
return siteTerm;
}
}
public static final Comparator getUserSitesRowComparator(final String fieldName, final boolean sortAscending, final Collator collator) {
return new Comparator() {
public int compare(Object o1, Object o2) {
if(o1 instanceof UserSitesRow && o2 instanceof UserSitesRow){
UserSitesRow r1 = (UserSitesRow) o1;
UserSitesRow r2 = (UserSitesRow) o2;
try{
if(fieldName.equals(SORT_SITE_NAME)){
String s1 = r1.getSiteTitle();
String s2 = r2.getSiteTitle();
int res = collator.compare(s1!=null? s1.toLowerCase():"", s2!=null? s2.toLowerCase():"");
if(sortAscending) return res;
else return -res;
}else if(fieldName.equals(SORT_SITE_TYPE)){
String s1 = r1.getSiteType();
String s2 = r2.getSiteType();
int res = collator.compare(s1!=null? s1.toLowerCase():"", s2!=null? s2.toLowerCase():"");
if(sortAscending) return res;
else return -res;
}else if(fieldName.equals(SORT_SITE_RID)){
String s1 = r1.getRoleName();
String s2 = r2.getRoleName();
int res = collator.compare(s1!=null? s1.toLowerCase():"", s2!=null? s2.toLowerCase():"");
if(sortAscending) return res;
else return -res;
}else if(fieldName.equals(SORT_SITE_PV)){
String s1 = r1.getPubView();
String s2 = r2.getPubView();
int res = collator.compare(s1!=null? s1.toLowerCase():"", s2!=null? s2.toLowerCase():"");
if(sortAscending) return res;
else return -res;
}else if(fieldName.equals(SORT_USER_STATUS)){
String s1 = r1.getUserStatus();
String s2 = r2.getUserStatus();
int res = collator.compare(s1!=null? s1.toLowerCase():"", s2!=null? s2.toLowerCase():"");
if(sortAscending) return res;
else return -res;
}else if(fieldName.equals(SORT_SITE_TERM)){
String s1 = r1.getSiteTerm();
String s2 = r2.getSiteTerm();
int res = collator.compare(s1!=null? s1.toLowerCase():"", s2!=null? s2.toLowerCase():"");
if(sortAscending) return res;
else return -res;
}
}catch(Exception e){
LOG.warn("Error occurred while sorting by: "+fieldName, e);
}
}
return 0;
}
};
}
// ######################################################################################
// Main methods
// ######################################################################################
public String getInitValues() {
if(isAllowed()){
if(userId == null){
String param = (String) FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("userId");
if(param != null){
userId = param;
}
}
if(refreshQuery){
LOG.debug("Refreshing query...");
try{
doSearch();
}catch(SQLException e){
LOG.warn("Failed to perform search on usermembership", e);
}
refreshQuery = false;
}
if(userSitesRows != null && userSitesRows.size() > 0) Collections.sort(userSitesRows, getUserSitesRowComparator(sitesSortColumn, sitesSortAscending, collator));
}
return "";
}
/**
* Uses complex SQL for site membership, user role and group membership.<br>
* For a 12 site users it takes < 1 secs!
* @throws SQLException
*/
private void doSearch() throws SQLException {
userSitesRows = new ArrayList();
Connection c = null;
PreparedStatement pst = null;
ResultSet rs = null;
try{
c = M_sql.borrowConnection();
String sql = "select ss.SITE_ID, ss.TITLE, ss.TYPE, ss.PUBLISHED, srr.ROLE_NAME, srrg.ACTIVE, "+
" (select VALUE from SAKAI_SITE_PROPERTY ssp where ss.SITE_ID = ssp.SITE_ID and ssp.NAME = 'term') TERM " +
"from SAKAI_SITE ss, SAKAI_REALM sr, SAKAI_REALM_RL_GR srrg, SAKAI_REALM_ROLE srr " +
"where sr.REALM_ID = CONCAT('/site/',ss.SITE_ID) " +
"and sr.REALM_KEY = srrg.REALM_KEY " +
"and srrg.ROLE_KEY = srr.ROLE_KEY " +
"and srrg.USER_ID = ? " +
"and ss.IS_USER = 0 " +
"and ss.IS_SPECIAL = 0 " +
"ORDER BY ss.TITLE";
pst = c.prepareStatement(sql);
pst.setString(1, userId);
rs = pst.executeQuery();
while (rs.next()){
String id = rs.getString("SITE_ID");
String t = rs.getString("TITLE");
String tp = rs.getString("TYPE");
String pv = rs.getString("PUBLISHED");
if("1".equals(pv)) {
pv = msgs.getString("status_published");
}else{
pv = msgs.getString("status_unpublished");
}
String rn = rs.getString("ROLE_NAME");
String grps = getGroups(userId, id);
String active = rs.getString("ACTIVE").trim().equals("1")? msgs.getString("site_user_status_active") : msgs.getString("site_user_status_inactive");
String term = rs.getString("TERM");
if(term == null)
term = "";
userSitesRows.add(new UserSitesRow(id, t, tp, grps, rn, pv, active, term));
}
}catch(SQLException e){
LOG.warn("SQL error occurred while retrieving user memberships for user: " + userId, e);
LOG.warn("UserMembership will use alternative methods for retrieving user memberships.");
doSearch3();
}finally{
try{
if(rs != null)
rs.close();
}finally{
try{
if(pst != null)
pst.close();
}finally{
if(c != null)
M_sql.returnConnection(c);
}
}
}
}
/**
* Uses ONLY Sakai API for site membership, user role and group membership.
* @throws SQLException
*/
private void doSearch2() throws SQLException {
long start = (new Date()).getTime();
userSitesRows = new ArrayList();
thisUserId = M_session.getCurrentSessionUserId();
setSakaiSessionUser(userId);
LOG.debug("Switched CurrentSessionUserId: " + M_session.getCurrentSessionUserId());
List siteList = org.sakaiproject.site.cover.SiteService.getSites(SelectionType.ACCESS, null, null, null, SortType.TITLE_ASC, null);
setSakaiSessionUser(thisUserId);
Iterator i = siteList.iterator();
while (i.hasNext()){
Site s = (Site) i.next();
UserSitesRow row = new UserSitesRow(s, getGroups(userId, s.getId()), getActiveUserRoleInSite(userId, s));
userSitesRows.add(row);
}
long end = (new Date()).getTime();
LOG.debug("doSearch2() took total of "+((end - start)/1000)+" sec.");
}
/**
* Uses single simple SQL for site membership, uses API for user role and
* group membership.<br>
* For a 12 site users it takes ~30secs!
* @throws SQLException
* @deprecated
*/
private void doSearch3() throws SQLException {
userSitesRows = new ArrayList();
timeSpentInGroups = 0;
Connection c = null;
PreparedStatement pst = null;
ResultSet rs = null;
try{
c = M_sql.borrowConnection();
String sql = "select distinct(SAKAI_SITE_USER.SITE_ID) from SAKAI_SITE_USER,SAKAI_SITE where SAKAI_SITE.SITE_ID=SAKAI_SITE_USER.SITE_ID and IS_USER=0 and IS_SPECIAL=0 and USER_ID=?";
pst = c.prepareStatement(sql);
pst.setString(1, userId);
rs = pst.executeQuery();
while (rs.next()){
String id = rs.getString("SITE_ID");
try{
Site site = M_site.getSite(id);
UserSitesRow row = new UserSitesRow(site, getGroups(userId, site), getActiveUserRoleInSite(userId, site));
userSitesRows.add(row);
}catch(IdUnusedException e){
LOG.warn("Unable to retrieve site for site id: " + id, e);
}
}
}catch(SQLException e){
LOG.warn("SQL error occurred while retrieving user memberships for user: " + userId, e);
LOG.warn("UserMembership will use alternative methods for retrieving user memberships (ONLY Published sites will be listed).");
doSearch2();
}finally{
try{
if(rs != null)
rs.close();
}finally{
try{
if(pst != null)
pst.close();
}finally{
if(c != null)
M_sql.returnConnection(c);
}
}
}
LOG.debug("Group ops took " + (timeSpentInGroups / 1000) + " secs");
}
/**
* Uses Sakai API for getting group membership (very very slow).
* @param userId The user ID.
* @param site The Site object
* @return A String with group list.
*/
public String getGroups(String userId, Site site) {
long start = (new Date()).getTime();
StringBuilder groups = new StringBuilder();
Iterator ig = site.getGroupsWithMember(userId).iterator();
while (ig.hasNext()){
Group g = (Group) ig.next();
if(groups.length() != 0) groups.append(", ");
groups.append(g.getTitle());
}
long end = (new Date()).getTime();
timeSpentInGroups += (end - start);
LOG.debug("getGroups("+userId+", "+site.getTitle()+") took "+((end - start)/1000)+" sec.");
return groups.toString();
}
public String getGroups(String userId, String siteId) throws SQLException {
long start = (new Date()).getTime();
StringBuilder groups = new StringBuilder();
String siteReference = M_site.siteReference(siteId);
Connection c = null;
PreparedStatement pst = null;
ResultSet rs = null;
try{
c = M_sql.borrowConnection();
String sql = "select SS.GROUP_ID, SS.TITLE TITLE, SS.DESCRIPTION "
+ "from SAKAI_SITE_GROUP SS, SAKAI_REALM R, SAKAI_REALM_RL_GR RRG "
+ "where R.REALM_ID = concat(concat('"+siteReference+"','/group/'), SS.GROUP_ID) "
+ "and R.REALM_KEY = RRG.REALM_KEY "
+ "and RRG.USER_ID = ? "
+ "and SS.SITE_ID = ? "
+ "ORDER BY TITLE";
pst = c.prepareStatement(sql);
pst.setString(1, userId);
pst.setString(2, siteId);
rs = pst.executeQuery();
while (rs.next()){
String t = rs.getString("TITLE");
if(groups.length() != 0) groups.append(", ");
groups.append(t);
}
}catch(SQLException e){
LOG.error("SQL error occurred while retrieving group memberships for user: " + userId, e);
}finally{
try{
if(rs != null)
rs.close();
}finally{
try{
if(pst != null)
pst.close();
}finally{
if(c != null)
M_sql.returnConnection(c);
}
}
}
long end = (new Date()).getTime();
timeSpentInGroups += (end - start);
LOG.debug("getGroups("+userId+", "+siteId+") took "+((end - start)/1000)+" sec.");
return groups.toString();
}
/**
* Uses Sakai API for getting user role in site.
* @param userId The user ID.
* @param site The Site object.
* @return The user role in site as String.
*/
protected String getActiveUserRoleInSite(String userId, Site site) {
Role r = site.getUserRole(userId);
return (r != null) ? r.getId() : "";
}
private synchronized void setSakaiSessionUser(String id) {
Session sakaiSession = M_session.getCurrentSession();
sakaiSession.setUserId(id);
sakaiSession.setUserEid(id);
}
// ######################################################################################
// ActionListener methods
// ######################################################################################
public String processActionUserId() {
try{
ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
Map paramMap = context.getRequestParameterMap();
userId = (String) paramMap.get("userId");
refreshQuery = true;
return "sitelist";
}catch(Exception e){
LOG.error("Error getting userId var.");
return "userlist";
}
}
public String processActionBack() {
return "userlist";
}
// ######################################################################################
// Generic get/set methods
// ######################################################################################
public boolean isAllowed() {
allowed = authz.isUserAbleToViewUmem(M_tm.getCurrentPlacement().getContext());
if(!allowed){
FacesContext fc = FacesContext.getCurrentInstance();
message = msgs.getString("unauthorized");
fc.addMessage("allowed", new FacesMessage(FacesMessage.SEVERITY_FATAL, message, null));
allowed = false;
}
return allowed;
}
public List getUserSitesRows() {
if(userSitesRows != null && userSitesRows.size() > 0) Collections.sort(userSitesRows, getUserSitesRowComparator(sitesSortColumn, sitesSortAscending, collator));
return userSitesRows;
}
public void setUserSitesRows(List userRows) {
this.userSitesRows = userRows;
}
public boolean isEmptySiteList() {
return (userSitesRows == null || userSitesRows.size() <= 0);
}
public boolean isRenderTable() {
return !isEmptySiteList();
}
public String getUserDisplayId() {
String displayId = null;
try{
displayId = UserDirectoryService.getUser(userId).getDisplayId();
}catch(UserNotDefinedException e){
displayId = userId;
}
return displayId;
}
public void setUserId(String id) {
this.userId = id;
}
public boolean isSitesSortAscending() {
return this.sitesSortAscending;
}
public void setSitesSortAscending(boolean sitesSortAscending) {
this.sitesSortAscending = sitesSortAscending;
}
public String getSitesSortColumn() {
return this.sitesSortColumn;
}
public void setSitesSortColumn(String sitesSortColumn) {
this.sitesSortColumn = sitesSortColumn;
}
// ######################################################################################
// CSV export
// ######################################################################################
public void exportAsCsv(ActionEvent event) {
Export.writeAsCsv(buildDataTable(userSitesRows), getFileNamePrefix());
}
public void exportAsXls(ActionEvent event) {
Export.writeAsXls(buildDataTable(userSitesRows), getFileNamePrefix());
}
private String getFileNamePrefix() {
return "Membership_for_"+getUserDisplayId();
}
/**
* Build a generic tabular representation of the user site membership data export.
*
* @param userSites The content of the table
* @return
* A table of data suitable to be exported
*/
private List<List<Object>> buildDataTable(List<UserSitesRow> userSites) {
List<List<Object>> table = new LinkedList<List<Object>>();
List<Object> header = new ArrayList<Object>();
header.add(msgs.getString("site_name"));
header.add(msgs.getString("site_id"));
header.add(msgs.getString("groups"));
header.add(msgs.getString("site_type"));
header.add(msgs.getString("site_term"));
header.add(msgs.getString("role_name"));
header.add(msgs.getString("status"));
header.add(msgs.getString("site_user_status"));
table.add(header);
for (UserSitesRow userSiteRow : userSites) {
List<Object> currentRow = new ArrayList<Object>();
currentRow.add(userSiteRow.getSiteTitle());
currentRow.add(userSiteRow.getSiteId());
currentRow.add(userSiteRow.getGroups());
currentRow.add(userSiteRow.getSiteType());
currentRow.add(userSiteRow.getSiteTerm());
currentRow.add(userSiteRow.getRoleName());
currentRow.add(userSiteRow.getPubView());
currentRow.add(userSiteRow.getUserStatus());
table.add(currentRow);
}
return table;
}
}