/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/site-manage/trunk/site-manage-tool/tool/src/java/org/sakaiproject/site/tool/SiteBrowserAction.java $ * $Id: SiteBrowserAction.java 121426 2013-03-19 01:00:07Z matthew@longsight.com $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009 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.site.tool; import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.sakaiproject.announcement.cover.AnnouncementService; import org.sakaiproject.authz.cover.SecurityService; import org.sakaiproject.cheftool.Context; import org.sakaiproject.cheftool.JetspeedRunData; import org.sakaiproject.cheftool.PagedResourceActionII; import org.sakaiproject.cheftool.RunData; import org.sakaiproject.cheftool.VelocityPortlet; import org.sakaiproject.cheftool.api.Menu; import org.sakaiproject.cheftool.menu.MenuImpl; import org.sakaiproject.component.cover.ComponentManager; import org.sakaiproject.component.cover.ServerConfigurationService; import org.sakaiproject.content.api.ContentHostingService; import org.sakaiproject.content.api.FilePickerHelper; import org.sakaiproject.content.cover.ContentTypeImageService; import org.sakaiproject.coursemanagement.api.AcademicSession; import org.sakaiproject.entity.api.Reference; import org.sakaiproject.entity.cover.EntityManager; import org.sakaiproject.event.api.SessionState; import org.sakaiproject.exception.IdUnusedException; import org.sakaiproject.exception.PermissionException; import org.sakaiproject.javax.PagingPosition; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.SiteService.SelectionType; import org.sakaiproject.site.cover.SiteService; import org.sakaiproject.site.util.SiteTextEditUtil; import org.sakaiproject.sitemanage.api.SiteHelper; import org.sakaiproject.tool.api.Tool; import org.sakaiproject.tool.api.ToolException; import org.sakaiproject.tool.api.ToolSession; import org.sakaiproject.tool.cover.SessionManager; import org.sakaiproject.tool.cover.ToolManager; import org.sakaiproject.util.ResourceLoader; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.velocity.tools.generic.SortTool; /** * <p> * SiteBrowserAction is the Sakai site browser, showing a searchable list of the defined sites, and details including public resources of each when selected. * </p> */ public class SiteBrowserAction extends PagedResourceActionII implements SiteHelper { private static final String INTER_SIZE = "inter_size"; private org.sakaiproject.coursemanagement.api.CourseManagementService cms = (org.sakaiproject.coursemanagement.api.CourseManagementService) ComponentManager .get(org.sakaiproject.coursemanagement.api.CourseManagementService.class); private ContentHostingService contentHostingService; private static Log log = LogFactory.getLog(SiteBrowserAction.class); private static ResourceLoader rb = new ResourceLoader("sitebrowser"); private static final String PREFIX = "sitebrowser."; private static final String MODE = PREFIX+ "mode"; private final static String SITE_TYPE_ANY = "Any"; private final static String SITE_TERM_ANY = "Any"; private final static String STATE_TERM_SELECTION = PREFIX+ "termSelection"; private final static String STATE_SEARCH_SITE_TYPE = PREFIX+ "siteType"; private final static String STATE_SEARCH_LIST = PREFIX+ "searchList"; private final static String STATE_PROP_SEARCH_MAP = PREFIX+ "propertyCriteriaMap"; private final static String SIMPLE_SEARCH_VIEW = "simpleSearch"; private final static String LIST_VIEW = "list"; // for the site with extra search criteria private final static String SEARCH_TERM_SITE_TYPE = "termSearchSiteType"; private final static String SEARCH_TERM_PROP = "termProp"; private static final String NO_SHOW_SEARCH_TYPE = "noshow_search_sitetype"; /** for navigating between sites in site list */ private static final String STATE_SITES = PREFIX+ "state_sites"; private static final String STATE_PREV_SITE = PREFIX+ "state_prev_site"; private static final String STATE_NEXT_SITE = PREFIX+ "state_next_site"; private static final String STATE_HELPER_DONE = PREFIX+ "helperDone"; private final static String SORT_KEY_SESSION = "worksitesetup.sort.key.session"; private final static String SORT_ORDER_SESSION = "worksitesetup.sort.order.session"; public SiteBrowserAction() { contentHostingService = (ContentHostingService) ComponentManager.get(ContentHostingService.class.getName()); } /** * {@inheritDoc} */ protected List readResourcesPage(SessionState state, int first, int last) { // search? String search = StringUtils.trimToNull((String) state.getAttribute(STATE_SEARCH)); // See what sort of search. org.sakaiproject.site.api.SiteService.SelectionType type = (org.sakaiproject.site.api.SiteService.SelectionType)state.getAttribute(SiteHelper.SITE_PICKER_PERMISSION); List sites = SiteService.getSites(type, state .getAttribute(STATE_SEARCH_SITE_TYPE), search, (HashMap) state.getAttribute(STATE_PROP_SEARCH_MAP), org.sakaiproject.site.api.SiteService.SortType.TITLE_ASC, new PagingPosition(first, last)); return sites; } /** * {@inheritDoc} */ protected int sizeResources(SessionState state) { String search = StringUtils.trimToNull((String) state.getAttribute(STATE_SEARCH)); org.sakaiproject.site.api.SiteService.SelectionType type = (org.sakaiproject.site.api.SiteService.SelectionType)state.getAttribute(SiteHelper.SITE_PICKER_PERMISSION); return SiteService.countSites(type, state .getAttribute(STATE_SEARCH_SITE_TYPE), search, (HashMap) state.getAttribute(STATE_PROP_SEARCH_MAP)); } /** * Populate the state object, if needed. */ protected void initState(SessionState state, VelocityPortlet portlet, JetspeedRunData rundata) { super.initState(state, portlet, rundata); state.setAttribute(STATE_PAGESIZE, Integer.valueOf(DEFAULT_PAGE_SIZE)); // if site type which requires term search exists // get all term-search related data from configuration, String termSearchSiteType = ServerConfigurationService.getString("sitebrowser.termsearch.type"); if (termSearchSiteType != null) { state.setAttribute(SEARCH_TERM_SITE_TYPE, termSearchSiteType); String termSearchProperty = ServerConfigurationService.getString("sitebrowser.termsearch.property"); state.setAttribute(SEARCH_TERM_PROP, termSearchProperty); } String[] noSearchSiteTypes = ServerConfigurationService.getStrings("sitesearch.noshow.sitetype"); if (noSearchSiteTypes != null) { state.setAttribute(NO_SHOW_SEARCH_TYPE, noSearchSiteTypes); } // Make sure we have a permission to be looking for. if (!(state.getAttribute(SiteHelper.SITE_PICKER_PERMISSION) instanceof org.sakaiproject.site.api.SiteService.SelectionType)) { // The default is pubview. state.setAttribute(SiteHelper.SITE_PICKER_PERMISSION, org.sakaiproject.site.api.SiteService.SelectionType.PUBVIEW); } // setup the observer to notify our main panel /* * if (state.getAttribute(STATE_OBSERVER) == null) { // the delivery location for this tool String deliveryId = clientWindowId(state, portlet.getID()); // the html element to update on delivery String elementId = * mainPanelUpdateId(portlet.getID()); // the event resource reference pattern to watch for String pattern = SiteService.siteReference(""); state.setAttribute(STATE_OBSERVER, new EventObservingCourier(deliveryId, elementId, pattern)); } // make * sure the observer is in sync with state updateObservationOfChannel(state, portlet.getID()); */ } // initState /** * Setup our observer to be watching for change events for our channel. * * @param peid * The portlet id. */ private void updateObservationOfChannel(SessionState state, String peid) { /* * EventObservingCourier observer = (EventObservingCourier) state.getAttribute(STATE_OBSERVER); // the delivery location for this tool String deliveryId = clientWindowId(state, peid); observer.setDeliveryId(deliveryId); */ } // updateObservationOfChannel /** * build the context */ public String buildMainPanelContext(VelocityPortlet portlet, Context context, RunData rundata, SessionState state) { context.put("tlang", rb); String template = null; // check mode and dispatch String mode = (String) state.getAttribute(MODE); if ((mode == null) || mode.equals(SIMPLE_SEARCH_VIEW)) { template = buildSimpleSearchContext(state, context); } else if (mode.equals(LIST_VIEW)) { template = buildListContext(state, context); } else if ("visit".equals(mode)) { template = buildVisitContext(state, context); } else { Log.warn("chef", "SiteBrowserAction: mode: " + mode); template = buildListContext(state, context); } return (String) getContext(rundata).get("template") + template; } // buildMainPanelContext /** * Build the context for the main list mode. */ private String buildListContext(SessionState state, Context context) { // put the service in the context (used for allow update calls on each site) context.put("service", SiteService.getInstance()); context.put("helperMode", Boolean.valueOf(state.getAttribute(Tool.HELPER_DONE_URL) != null)); context.put("termProp", (String) state.getAttribute(SEARCH_TERM_PROP)); context.put("searchText", (String) state.getAttribute(STATE_SEARCH)); context.put("siteType", (String) state.getAttribute(STATE_SEARCH_SITE_TYPE)); context.put("termSelection", (String) state.getAttribute(STATE_TERM_SELECTION)); context.put("siteBrowserTextEdit", new SiteTextEditUtil()); // String newPageSize = state.getAttribute(STATE_PAGESIZE).toString(); Integer newPageSize = (Integer) state.getAttribute(INTER_SIZE); if (newPageSize != null) { context.put("pagesize", newPageSize); state.setAttribute(STATE_PAGESIZE, newPageSize); } else { state.setAttribute(STATE_PAGESIZE, Integer.valueOf(DEFAULT_PAGE_SIZE)); context.put("pagesize", Integer.valueOf(DEFAULT_PAGE_SIZE)); } // prepare the paging of realms List sites = prepPage(state); state.setAttribute(STATE_SITES, sites); context.put("sites", sites); if (state.getAttribute(STATE_NUM_MESSAGES) != null) context.put("allMsgNumber", state.getAttribute(STATE_NUM_MESSAGES).toString()); // find the position of the message that is the top first on the page if ((state.getAttribute(STATE_TOP_PAGE_MESSAGE) != null) && (state.getAttribute(STATE_PAGESIZE) != null)) { int topMsgPos = ((Integer) state.getAttribute(STATE_TOP_PAGE_MESSAGE)).intValue() + 1; context.put("topMsgPos", Integer.toString(topMsgPos)); int btmMsgPos = topMsgPos + ((Integer) state.getAttribute(STATE_PAGESIZE)).intValue() - 1; if (state.getAttribute(STATE_NUM_MESSAGES) != null) { int allMsgNumber = ((Integer) state.getAttribute(STATE_NUM_MESSAGES)).intValue(); if (btmMsgPos > allMsgNumber) btmMsgPos = allMsgNumber; } context.put("btmMsgPos", Integer.toString(btmMsgPos)); } // build the menu Menu bar = new MenuImpl(); // add the search commands // bar.add( new MenuField(FORM_SEARCH, "toolbar", "doSearch", (String) state.getAttribute(STATE_SEARCH))); // bar.add( new MenuEntry("Search", null, true, MenuItem.CHECKED_NA, "doSearch", "toolbar")); // add the refresh commands // %%% we want manual only addRefreshMenus(bar, state); if (bar.size() > 0) { context.put(Menu.CONTEXT_MENU, bar); } boolean goPPButton = state.getAttribute(STATE_PREV_PAGE_EXISTS) != null; context.put("goPPButton", Boolean.toString(goPPButton)); boolean goNPButton = state.getAttribute(STATE_NEXT_PAGE_EXISTS) != null; context.put("goNPButton", Boolean.toString(goNPButton)); // } // inform the observing courier that we just updated the page... // if there are pending requests to do so they can be cleared // justDelivered(state); if (cms != null) { Map<String, String> smap =new HashMap<String, String>(); Collection<AcademicSession> sessions = cms.getAcademicSessions(); for (AcademicSession s: sessions) { smap.put(s.getEid(),s.getTitle()); } context.put("termsmap", smap ); } return "_list"; } // buildListContext /** * Build the context for the simple search mode. */ private String buildSimpleSearchContext(SessionState state, Context context) { List newTypes = new Vector(); if (state.getAttribute(NO_SHOW_SEARCH_TYPE) != null) { // SAK-19287 String[] noTypes = (String[]) state.getAttribute(NO_SHOW_SEARCH_TYPE); List<String> oldTypes = SiteService.getSiteTypes(); for (int i = 0; i < noTypes.length; i++) { if (noTypes[i] != null && noTypes[i].length() > 0) { String noType = noTypes[i].trim(); if (oldTypes.contains(noType)) { oldTypes.remove(noType); } } } newTypes.addAll(oldTypes); } else { newTypes = SiteService.getSiteTypes(); } // remove the "myworkspace" type for (Iterator i = newTypes.iterator(); i.hasNext();) { String t = (String) i.next(); if ("myworkspace".equalsIgnoreCase(t)) { i.remove(); } } context.put("siteTypes", newTypes); String termSearchSiteType = (String) state.getAttribute(SEARCH_TERM_SITE_TYPE); if (termSearchSiteType != null) { context.put("termSearchSiteType", termSearchSiteType); if (cms != null) { context.put("terms", sortAcademicSessions( cms.getAcademicSessions())); } } return "_simpleSearch"; } // buildSimpleSearchContext /** * Helper method for sortCmObject * by order from sakai properties if specified or * by default of eid, title * using velocity SortTool * * @param sessions * @return */ private Collection sortAcademicSessions(Collection<AcademicSession> sessions) { // Get the keys from sakai.properties String[] keys = ServerConfigurationService.getStrings(SORT_KEY_SESSION); String[] orders = ServerConfigurationService.getStrings(SORT_ORDER_SESSION); return sortCmObject(sessions, keys, orders); } // sortCourseOffering /** * Custom sort CM collections using properties provided object has getter & setter for * properties in keys and orders * defaults to eid & title if none specified * * @param collection a collection to be sorted * @param keys properties to sort on * @param orders properties on how to sort (asc, dsc) * @return Collection the sorted collection */ private Collection sortCmObject(Collection collection, String[] keys, String[] orders) { if (collection != null && !collection.isEmpty()) { // Add them to a list for the SortTool (they must have the form // "<key:order>" in this implementation) List propsList = new ArrayList(); if (keys == null || orders == null || keys.length == 0 || orders.length == 0) { // No keys are specified, so use the default sort order propsList.add("eid"); propsList.add("title"); } else { // Populate propsList for (int i = 0; i < Math.min(keys.length, orders.length); i++) { String key = keys[i]; String order = orders[i]; propsList.add(key + ":" + order); } } // Sort the collection and return SortTool sort = new SortTool(); return sort.sort(collection, propsList); } return Collections.emptyList(); } // sortCmObject /** * Build the context for the visit site mode. */ private String buildVisitContext(SessionState state, Context context) { List sites = (List) state.getAttribute(STATE_SITES); String siteId = (String) state.getAttribute("siteId"); try { Site site = SiteService.getSite(siteId); if (sites != null) { int pos = -1; for (int index = 0; index < sites.size() && pos == -1; index++) { if (((Site) sites.get(index)).getId().equals(siteId)) { pos = index; } } // has any previous site in the list? if (pos > 0) { state.setAttribute(STATE_PREV_SITE, sites.get(pos - 1)); } else { state.removeAttribute(STATE_PREV_SITE); } // has any next site in the list? if (pos < sites.size() - 1) { state.setAttribute(STATE_NEXT_SITE, sites.get(pos + 1)); } else { state.removeAttribute(STATE_NEXT_SITE); } } if (state.getAttribute(STATE_PREV_SITE) != null) { context.put("prevSite", state.getAttribute(STATE_PREV_SITE)); } if (state.getAttribute(STATE_NEXT_SITE) != null) { context.put("nextSite", state.getAttribute(STATE_NEXT_SITE)); } context.put("site", site); // get the public announcements String anncRef = AnnouncementService.channelReference(site.getId(), SiteService.MAIN_CONTAINER); List announcements = null; try { announcements = AnnouncementService.getMessages(anncRef, null, 0, true, false, true); } catch (PermissionException e) { announcements = new Vector(); } context.put("announcements", announcements); // get the public syllabus try { Object syllabusService = ComponentManager.get("org.sakaiproject.api.app.syllabus.SyllabusService"); Class syllabusServiceClass = syllabusService.getClass(); Class[] paramsClasses = new Class[1]; paramsClasses[0] = java.lang.String.class; Method getMessages = syllabusServiceClass.getMethod("getMessages", paramsClasses); String paramSiteId = site.getId(); List syllabusList = (ArrayList) getMessages.invoke(syllabusService, new Object[] { paramSiteId }); context.put("syllabus", syllabusList); } catch (Exception reflectionEx) { Log.error("Reflection exceptions in SiteBrowserAction for getting public syllabus" + reflectionEx, ""); reflectionEx.printStackTrace(); } // get the public resources List resources = contentHostingService.getAllResources(contentHostingService.getSiteCollection(site.getId())); context.put("resources", resources); // the height for the info frame context.put("height", "300px"); // the url for info String url = site.getInfoUrl(); if (url != null) { url = url.trim(); url = convertReferenceUrl(url); context.put("infoUrl", url); } context.put("contentTypeImageService", ContentTypeImageService.getInstance()); } catch (IdUnusedException err) { } return "_visit"; } // buildVisitContext public void doNavigate_to_site(RunData data, Context context) { SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); String siteId = StringUtils.trimToNull(data.getParameters().getString("newSiteId")); if (siteId != null) { state.setAttribute("siteId", siteId); } else { doBack(data, context); } } // doNavigate_to_site /** * Handle a request to visit a site. */ public void doVisit(RunData data, Context context) { SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); String id = data.getParameters().getString("id"); // get the site try { Site site = SiteService.getSite(id); state.setAttribute("siteId", id); state.setAttribute(MODE, "visit"); // disable auto-updates while in view mode // ((EventObservingCourier) state.getAttribute(STATE_OBSERVER)).disable(); } catch (IdUnusedException e) { Log.warn("chef", "SiteBrowserAction.doEdit: site not found: " + id); addAlert(state, rb.getFormattedMessage("notfound", new Object[]{id})); state.removeAttribute(MODE); // make sure auto-updates are enabled // enableObserver(state); } } // doVisit /** * Handle a request to return to the list. */ public void doBack(RunData data, Context context) { SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); state.removeAttribute("siteId"); state.setAttribute(MODE, LIST_VIEW); } // doBack /** * Handle a request to go to Simple Search Mode. */ public void doShow_simple_search(RunData data, Context context) { SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); state.setAttribute(MODE, SIMPLE_SEARCH_VIEW); } // doShow_simple_search /** * Handle a request to go to Advanced Search Mode. */ /* * public void doShowadvsearch(RunData data, Context context) { SessionState state = ((JetspeedRunData)data).getPortletSessionState(((JetspeedRunData)data).getJs_peid()); state.setAttribute("mode", ADV_SEARCH_VIEW); } // doShowadvsearch */ /** * Handle a request to search. */ public void doSearch(RunData data, Context context) { super.doSearch(data, context); SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); String mode = (String) state.getAttribute(MODE); state.setAttribute("searchMode", mode); state.removeAttribute(STATE_PROP_SEARCH_MAP); state.removeAttribute(STATE_TERM_SELECTION); // read the search form field into the state object String siteType = StringUtils.trimToNull(data.getParameters().getString("siteType")); if (siteType != null) { if (siteType.equalsIgnoreCase("Any")) state.setAttribute(STATE_SEARCH_SITE_TYPE, null); else { state.setAttribute(STATE_SEARCH_SITE_TYPE, siteType); String termSearchSiteType = (String) state.getAttribute(SEARCH_TERM_SITE_TYPE); if (termSearchSiteType != null) { if (siteType.equals(termSearchSiteType)) { // search parameter - term; term.eid from UI String term = StringUtils.trimToNull(data.getParameters().getString("selectTerm")); if (term != null) { state.setAttribute(STATE_TERM_SELECTION, term); // property criteria map Map pMap = null; if (!SITE_TERM_ANY.equals(term)) { pMap = new HashMap(); pMap.put((String) state.getAttribute(SEARCH_TERM_PROP), term); state.setAttribute(STATE_PROP_SEARCH_MAP, pMap); } } } } } } else { state.setAttribute(STATE_SEARCH_SITE_TYPE, null); } state.setAttribute(MODE, LIST_VIEW); state.setAttribute(STATE_PAGESIZE, Integer.valueOf(DEFAULT_PAGE_SIZE)); state.removeAttribute(INTER_SIZE); } // doSearch public void doCancel(RunData data, Context context) { SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); state.setAttribute(SiteHelper.SITE_PICKER_CANCELLED, Boolean.TRUE); state.setAttribute(MODE, STATE_HELPER_DONE); } public void doSelect(RunData data, Context context) { SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); String siteId = data.getParameters().getString("siteId"); if (siteId != null && siteId.length() > 0) { state.setAttribute(SiteHelper.SITE_PICKER_SITE_ID, siteId); state.setAttribute(MODE, STATE_HELPER_DONE); } else { addAlert(state, rb.getString("list.not.selected")); } } /** * Return the url unchanged, unless it's a reference, then return the reference url */ private String convertReferenceUrl(String url) { // make a reference Reference ref = EntityManager.newReference(url); // if it didn't recognize this, return it unchanged if (!ref.isKnownType()) return url; // return the reference's url return ref.getUrl(); } // convertReferenceUrl protected void toolModeDispatch(String methodBase, String methodExt, HttpServletRequest req, HttpServletResponse res) throws ToolException { ToolSession toolSession = SessionManager.getCurrentToolSession(); SessionState state = getState(req); if (STATE_HELPER_DONE.equals(toolSession.getAttribute(MODE))) { String url = (String) SessionManager.getCurrentToolSession().getAttribute(Tool.HELPER_DONE_URL); SessionManager.getCurrentToolSession().removeAttribute(Tool.HELPER_DONE_URL); // TODO: Implement cleanup. cleanup(state); if (log.isDebugEnabled()) { log.debug("Sending redirect to: "+ url); } try { res.sendRedirect(url); } catch (IOException e) { log.warn("Problem sending redirect to: "+ url, e); } return; } else if(sendToHelper(req, res, req.getPathInfo())) { return; } else { super.toolModeDispatch(methodBase, methodExt, req, res); } } private void cleanup(SessionState state) { List<String> names = (List<String>)state.getAttributeNames(); for (String name : names) { if (name.startsWith(PREFIX)) { log.debug("Removed attribute: "+ name); state.removeAttribute(name); } } } } // SiteBrowserAction