/** * Copyright (c) 2008-2010 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.osedu.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.roster.tool.entityprovider; import java.io.IOException; import java.io.OutputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.sakaiproject.entitybroker.EntityReference; import org.sakaiproject.entitybroker.EntityView; import org.sakaiproject.entitybroker.entityprovider.annotations.EntityCustomAction; import org.sakaiproject.entitybroker.entityprovider.capabilities.ActionsExecutable; import org.sakaiproject.entitybroker.entityprovider.capabilities.AutoRegisterEntityProvider; import org.sakaiproject.entitybroker.entityprovider.capabilities.RequestAware; import org.sakaiproject.entitybroker.entityprovider.extension.RequestGetter; import org.sakaiproject.entitybroker.exception.EntityException; import org.sakaiproject.entitybroker.util.AbstractEntityProvider; import org.sakaiproject.roster.api.RosterEnrollment; import org.sakaiproject.roster.api.RosterFunctions; import org.sakaiproject.roster.api.RosterGroup; import org.sakaiproject.roster.api.RosterMember; import org.sakaiproject.roster.api.RosterMemberComparator; import org.sakaiproject.roster.api.RosterSite; import org.sakaiproject.roster.api.SakaiProxy; import org.sakaiproject.roster.impl.SakaiProxyImpl; /** * <code>RosterPOIEntityProvider</code> allows Roster to export to Excel via * Apache's POI. * * @author d.b.robinson@lancaster.ac.uk */ public class RosterPOIEntityProvider extends AbstractEntityProvider implements AutoRegisterEntityProvider, ActionsExecutable, RequestAware { private static final Log log = LogFactory.getLog(RosterPOIEntityProvider.class); public final static String ENTITY_PREFIX = "roster-export"; public final static String DEFAULT_ID = ":ID:"; // error messages public final static String MSG_INVALID_ID = "Invalid site ID"; public final static String MSG_NO_SESSION = "Must be logged in"; public final static String MSG_NO_SITE_ID = "Must provide a site ID"; public final static String MSG_NO_FILE_CREATED = "Error creating file"; public final static String MSG_NO_EXPORT_PERMISSION = "Current user does not have export permission"; public final static String MSG_UNABLE_TO_RETRIEVE_SITE = "Unable to retrieve the requested site"; // roster views public final static String VIEW_OVERVIEW = "overview"; public final static String VIEW_GROUP_MEMBERSHIP = "group_membership"; public final static String VIEW_ENROLLMENT_STATUS = "status"; // key passed as parameters public final static String KEY_GROUP_ID = "groupId"; public final static String KEY_VIEW_TYPE = "viewType"; public final static String KEY_SORT_FIELD = "sortField"; public final static String KEY_SORT_DIRECTION = "sortDirection"; public final static String KEY_BY_GROUP = "byGroup"; public final static String KEY_ENROLLMENT_SET_ID = "enrollmentSetId"; public final static String KEY_ENROLLMENT_STATUS = "enrollmentStatus"; public final static String KEY_FACET_NAME = "facetName"; public final static String KEY_FACET_USER_ID = "facetUserId"; public final static String KEY_FACET_EMAIL = "facetEmail"; public final static String KEY_FACET_ROLE = "facetRole"; public final static String KEY_FACET_GROUPS = "facetGroups"; public final static String KEY_FACET_STATUS = "facetStatus"; public final static String KEY_FACET_CREDITS = "facetCredits"; // defaults to use if any keys are not specified public final static String DEFAULT_FACET_NAME = "Name"; public final static String DEFAULT_FACET_USER_ID = "User ID"; public final static String DEFAULT_FACET_EMAIL = "Email Address"; public final static String DEFAULT_FACET_ROLE = "Role"; public final static String DEFAULT_FACET_GROUPS = "Groups"; public final static String DEFAULT_FACET_STATUS = "Status"; public final static String DEFAULT_FACET_CREDITS = "Credits"; public final static String DEFAULT_GROUP_ID = "all"; public final static String DEFAULT_ENROLLMENT_STATUS= "All"; public final static String DEFAULT_VIEW_TYPE = VIEW_OVERVIEW; public final static String DEFAULT_SORT_FIELD = RosterMemberComparator.SORT_NAME; public final static int DEFAULT_SORT_DIRECTION = RosterMemberComparator.SORT_ASCENDING; public final static boolean DEFAULT_BY_GROUP = false; // misc public final static String FILE_EXTENSION = ".xls"; public final static String FILENAME_SEPARATOR = "_"; public final static String FILENAME_BYGROUP = "ByGroup"; public final static String FILENAME_UNGROUPED = "Ungrouped"; private SakaiProxy sakaiProxy; private RequestGetter requestGetter; public RosterPOIEntityProvider() { sakaiProxy = SakaiProxyImpl.instance(); } /** * {@inheritDoc} */ public String getEntityPrefix() { return ENTITY_PREFIX; } @EntityCustomAction(action = "export-to-excel", viewKey = EntityView.VIEW_SHOW) public void exportToExcel(OutputStream out, EntityReference reference, Map<String, Object> parameters) { HttpServletResponse response = requestGetter.getResponse(); // user must be logged in String userId = sakaiProxy.getCurrentUserId(); if (null == userId) { throw new EntityException(MSG_NO_SESSION, reference.getReference()); } String siteId = reference.getId(); if (StringUtils.isBlank(reference.getId()) || DEFAULT_ID.equals(reference.getId())) { throw new EntityException(MSG_NO_SITE_ID, reference.getReference()); } try { if (sakaiProxy.hasUserSitePermission(userId, RosterFunctions.ROSTER_FUNCTION_EXPORT, siteId)) { RosterSite site = sakaiProxy.getRosterSite(siteId); if (null == site) { throw new EntityException(MSG_UNABLE_TO_RETRIEVE_SITE, reference.getReference()); } export(response, site, parameters); } else { throw new EntityException(MSG_NO_EXPORT_PERMISSION, reference.getReference()); } } catch (IOException e) { log.error(MSG_NO_FILE_CREATED, e); throw new EntityException(MSG_NO_FILE_CREATED, reference.getReference()); } } private void addResponseHeader(HttpServletResponse response, String filename) { response.addHeader("Content-Encoding", "base64"); response.addHeader("Content-Type", "application/vnd.ms-excel"); response.addHeader("Content-Disposition", "attachment; filename=" + filename); } // TODO split into separate methods for different roster views private String createFilename(RosterSite site, String groupId, String viewType, boolean byGroup, String enrollmentSetId, String enrollmentStatus) { StringBuffer filename = new StringBuffer(); if (VIEW_OVERVIEW.equals(viewType)) { filename.append(site.getTitle()); if (null != groupId && !DEFAULT_GROUP_ID.equals(groupId)) { for (RosterGroup group : site.getSiteGroups()) { if (group.getId().equals(groupId)) { filename.append(FILENAME_SEPARATOR); filename.append(group.getTitle()); break; } } } } else if (VIEW_GROUP_MEMBERSHIP.equals(viewType)) { filename.append(site.getTitle()); filename.append(FILENAME_SEPARATOR); if (true == byGroup) { filename.append(FILENAME_BYGROUP); } else { filename.append(FILENAME_UNGROUPED); } } else if (VIEW_ENROLLMENT_STATUS.equals(viewType)) { filename.append(enrollmentSetId); filename.append(FILENAME_SEPARATOR); filename.append(enrollmentStatus); } Date date = new Date(); // ISO formatted date DateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd"); filename.append(FILENAME_SEPARATOR); filename.append(isoFormat.format(date)); filename = new StringBuffer(filename.toString().replaceAll("\\W", FILENAME_SEPARATOR)); filename.append(FILE_EXTENSION); return filename.toString(); } private void export(HttpServletResponse response, RosterSite site, Map<String, Object> parameters) throws IOException { // TODO one generic method could handle the parameters? String groupId = getGroupIdValue(parameters); String viewType = getViewTypeValue(parameters); boolean byGroup = getByGroupValue(parameters); int sortDirection = getSortDirectionValue(parameters); String sortField = getSortFieldValue(parameters); String enrollmentSetId = getEnrollmentSetIdValue(parameters); String enrollmentStatus = getEnrollmentStatusValue(parameters); String enrollmentSetTitle = null; if (null != enrollmentSetId) { for (RosterEnrollment enrollmentSet : site.getSiteEnrollmentSets()) { if (enrollmentSetId.equals(enrollmentSet.getId())) { enrollmentSetTitle = enrollmentSet.getTitle(); break; } } } addResponseHeader(response, createFilename(site, groupId, viewType, byGroup, enrollmentSetTitle, enrollmentStatus)); List<List<String>> dataInRows = new ArrayList<List<String>>(); createSpreadsheetTitle(dataInRows, site, groupId, viewType, enrollmentSetTitle); List<String> header = createColumnHeader(parameters, viewType, site.getId()); if (VIEW_OVERVIEW.equals(viewType)) { List<RosterMember> rosterMembers = getMembership(site.getId(), groupId, sortDirection, sortField); if (null != rosterMembers) { addOverviewRows(dataInRows, rosterMembers, header, site.getId()); } } else if (VIEW_GROUP_MEMBERSHIP.equals(viewType)) { List<RosterMember> rosterMembers = getMembership(site.getId(), groupId, sortDirection, sortField); if (null != rosterMembers) { if (byGroup) { addGroupMembershipByGroupRows(dataInRows, rosterMembers, site, header); } else { addGroupMembershipUngroupedRows(dataInRows, rosterMembers, header); } } } else if (VIEW_ENROLLMENT_STATUS.equals(viewType)) { List<RosterMember> rosterMembers = getEnrolledMembership(site .getId(), enrollmentSetId, sortDirection, sortField, enrollmentStatus); if (null != rosterMembers) { addEnrollmentStatusRows(dataInRows, rosterMembers, header, enrollmentSetTitle, enrollmentStatus, site.getId()); } } Workbook workBook = new HSSFWorkbook(); Sheet sheet = workBook.createSheet(); for (int i = 0; i < dataInRows.size(); i++) { Row row = sheet.createRow(i); for (int j = 0; j < dataInRows.get(i).size(); j++) { Cell cell = row.createCell(j); cell.setCellValue(dataInRows.get(i).get(j)); } } workBook.write(response.getOutputStream()); response.getOutputStream().close(); } private List<RosterMember> getMembership(String siteId, String groupId, int sortDirection, String sortField) { List<RosterMember> rosterMembers; if (DEFAULT_GROUP_ID.equals(groupId)) { rosterMembers = sakaiProxy.getSiteMembership(siteId, false); } else { rosterMembers = sakaiProxy.getGroupMembership(siteId, groupId); } if (null == rosterMembers) { return null; } Collections.sort(rosterMembers, new RosterMemberComparator(sortField, sortDirection, sakaiProxy.getFirstNameLastName())); return rosterMembers; } private List<RosterMember> getEnrolledMembership(String siteId, String enrollmentSetId, int sortDirection, String sortField, String enrollmentStatus) { List<RosterMember> rosterMembers = sakaiProxy.getEnrollmentMembership( siteId, enrollmentSetId); List<RosterMember> membersByStatus = null; if (DEFAULT_ENROLLMENT_STATUS.equals(enrollmentStatus)) { membersByStatus = rosterMembers; } else { membersByStatus = new ArrayList<RosterMember>(); for (RosterMember rosterMember : rosterMembers) { if (enrollmentStatus.equals(rosterMember.getEnrollmentStatus())) { membersByStatus.add(rosterMember); } } } Collections.sort(membersByStatus, new RosterMemberComparator(sortField, sortDirection, sakaiProxy.getFirstNameLastName())); return membersByStatus; } private void addOverviewRows(List<List<String>> dataInRows, List<RosterMember> rosterMembers, List<String> header, String siteId) { dataInRows.add(header); // blank line dataInRows.add(new ArrayList<String>()); for (RosterMember member : rosterMembers) { List<String> row = new ArrayList<String>(); if (sakaiProxy.getFirstNameLastName()) { row.add(member.getDisplayName()); } else { row.add(member.getSortName()); } if (sakaiProxy.getViewUserDisplayId()) { row.add(member.getDisplayId()); } if (sakaiProxy.getViewEmail(siteId)) { row.add(member.getEmail()); } row.add(member.getRole()); dataInRows.add(row); } } private void addGroupMembershipUngroupedRows(List<List<String>> dataInRows, List<RosterMember> rosterMembers, List<String> header) { dataInRows.add(header); // blank line dataInRows.add(new ArrayList<String>()); for (RosterMember member : rosterMembers) { List<String> row = new ArrayList<String>(); if (sakaiProxy.getFirstNameLastName()) { row.add(member.getDisplayName()); } else { row.add(member.getSortName()); } if (sakaiProxy.getViewUserDisplayId()) { row.add(member.getDisplayId()); } row.add(member.getRole()); row.add(member.getGroupsToString()); dataInRows.add(row); } } private void addGroupMembershipByGroupRows(List<List<String>> dataInRows, List<RosterMember> rosterMembers, RosterSite site, List<String> header) { for (RosterGroup group : site.getSiteGroups()) { List<String> groupTitle = new ArrayList<String>(); groupTitle.add(group.getTitle()); dataInRows.add(groupTitle); // blank line dataInRows.add(new ArrayList<String>()); dataInRows.add(header); // blank line dataInRows.add(new ArrayList<String>()); for (RosterMember member : rosterMembers) { if (null != member.getGroups().get(group.getId())) { List<String> row = new ArrayList<String>(); if (sakaiProxy.getFirstNameLastName()) { row.add(member.getDisplayName()); } else { row.add(member.getSortName()); } if (sakaiProxy.getViewUserDisplayId()) { row.add(member.getDisplayId()); } row.add(member.getRole()); row.add(member.getGroupsToString()); dataInRows.add(row); } } // blank line dataInRows.add(new ArrayList<String>()); } } private void addEnrollmentStatusRows(List<List<String>> dataInRows, List<RosterMember> enrollmentSet, /* RosterSite site, */ List<String> header, String enrollmentSetTitle, String enrollmentStatus, String siteId) { List<String> enrollmentSetTitleRow = new ArrayList<String>(); enrollmentSetTitleRow.add(enrollmentSetTitle); dataInRows.add(enrollmentSetTitleRow); // blank line dataInRows.add(new ArrayList<String>()); List<String> enrollmentStatusRow = new ArrayList<String>(); enrollmentStatusRow.add(enrollmentStatus); dataInRows.add(enrollmentStatusRow); // blank line dataInRows.add(new ArrayList<String>()); dataInRows.add(header); // blank line dataInRows.add(new ArrayList<String>()); for (RosterMember member : enrollmentSet) { List<String> row = new ArrayList<String>(); if (sakaiProxy.getFirstNameLastName()) { row.add(member.getDisplayName()); } else { row.add(member.getSortName()); } if (sakaiProxy.getViewUserDisplayId()) { row.add(member.getDisplayId()); } if (sakaiProxy.getViewEmail(siteId)) { row.add(member.getEmail()); } row.add(member.getEnrollmentStatus()); row.add(member.getCredits()); dataInRows.add(row); } } private String getSortFieldValue(Map<String, Object> parameters) { String sortField; if (null != parameters.get(KEY_SORT_FIELD)) { sortField = parameters.get(KEY_SORT_FIELD).toString(); } else { sortField = DEFAULT_SORT_FIELD; } return sortField; } private String getEnrollmentSetIdValue(Map<String, Object> parameters) { String enrollmentSetId = null; if (null != parameters.get(KEY_ENROLLMENT_SET_ID)) { enrollmentSetId = parameters.get(KEY_ENROLLMENT_SET_ID).toString(); } return enrollmentSetId; } private String getEnrollmentStatusValue(Map<String, Object> parameters) { String enrollmentStatus = null; if (null != parameters.get(KEY_ENROLLMENT_STATUS)) { enrollmentStatus = parameters.get(KEY_ENROLLMENT_STATUS).toString(); } return enrollmentStatus; } private int getSortDirectionValue(Map<String, Object> parameters) { if (null != parameters.get(KEY_SORT_DIRECTION)) { return Integer.parseInt(parameters.get(KEY_SORT_DIRECTION) .toString()); } else { return DEFAULT_SORT_DIRECTION; } } private boolean getByGroupValue(Map<String, Object> parameters) { if (null != parameters.get(KEY_BY_GROUP)) { return Boolean .parseBoolean(parameters.get(KEY_BY_GROUP).toString()); } else { return DEFAULT_BY_GROUP; } } private String getViewTypeValue(Map<String, Object> parameters) { if (null != parameters.get(KEY_VIEW_TYPE)) { return parameters.get(KEY_VIEW_TYPE).toString(); } else { return DEFAULT_VIEW_TYPE; } } private String getGroupIdValue(Map<String, Object> parameters) { if (null != parameters.get(KEY_GROUP_ID)) { return parameters.get(KEY_GROUP_ID).toString(); } return null; } private List<String> createColumnHeader(Map<String, Object> parameters, String viewType, String siteId) { List<String> header = new ArrayList<String>(); header.add(parameters.get(KEY_FACET_NAME) != null ? parameters.get( KEY_FACET_NAME).toString() : DEFAULT_FACET_NAME); if (sakaiProxy.getViewUserDisplayId()) { header.add(parameters.get(KEY_FACET_USER_ID) != null ? parameters.get( KEY_FACET_USER_ID).toString() : DEFAULT_FACET_USER_ID); } if (VIEW_OVERVIEW.equals(viewType)) { if (sakaiProxy.getViewEmail(siteId)) { header.add(parameters.get(KEY_FACET_EMAIL) != null ? parameters .get(KEY_FACET_EMAIL).toString() : DEFAULT_FACET_EMAIL); } header.add(parameters.get(KEY_FACET_ROLE) != null ? parameters.get( KEY_FACET_ROLE).toString() : DEFAULT_FACET_ROLE); } else if (VIEW_GROUP_MEMBERSHIP.equals(viewType)) { header.add(parameters.get(KEY_FACET_ROLE) != null ? parameters.get( KEY_FACET_ROLE).toString() : DEFAULT_FACET_ROLE); header.add(parameters.get(KEY_FACET_GROUPS) != null ? parameters .get(KEY_FACET_GROUPS).toString() : DEFAULT_FACET_GROUPS); } else if (VIEW_ENROLLMENT_STATUS.equals(viewType)) { if (sakaiProxy.getViewEmail(siteId)) { header.add(parameters.get(KEY_FACET_EMAIL) != null ? parameters .get(KEY_FACET_EMAIL).toString() : DEFAULT_FACET_EMAIL); } header.add(parameters.get(KEY_FACET_STATUS) != null ? parameters .get(KEY_FACET_STATUS).toString() : DEFAULT_FACET_STATUS); header.add(parameters.get(KEY_FACET_CREDITS) != null ? parameters .get(KEY_FACET_CREDITS).toString() : DEFAULT_FACET_CREDITS); } return header; } private void createSpreadsheetTitle(List<List<String>> dataInRows, RosterSite site, String groupId, String viewType, String enrollmentSet) { List<String> title = new ArrayList<String>(); title.add(site.getTitle()); dataInRows.add(title); // blank line dataInRows.add(new ArrayList<String>()); // SAK-18513 if (VIEW_OVERVIEW.equals(viewType)) { if (null != groupId && !DEFAULT_GROUP_ID.equals(groupId)) { // TODO look at using maps in RosterSite instead for (RosterGroup group : site.getSiteGroups()) { if (group.getId().equals(groupId)) { List<String> groupTitle = new ArrayList<String>(); groupTitle.add(group.getTitle()); dataInRows.add(groupTitle); // blank line dataInRows.add(new ArrayList<String>()); break; } } } } } /** * {@inheritDoc} */ public void setRequestGetter(RequestGetter requestGetter) { this.requestGetter = requestGetter; } }