Java tutorial
/** * Licensed to Apereo under one or more contributor license * agreements. See the NOTICE file distributed with this work * for additional information regarding copyright ownership. * Apereo licenses this file to you under the Apache 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 the following location: * * http://www.apache.org/licenses/LICENSE-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.jasig.portlet.announcements.mvc.portlet.display; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import javax.portlet.EventRequest; import javax.portlet.EventResponse; import javax.portlet.PortletException; import javax.portlet.PortletPreferences; import javax.portlet.PortletRequest; import javax.portlet.RenderRequest; import javax.portlet.ResourceRequest; import javax.portlet.ResourceResponse; import javax.xml.namespace.QName; import com.fasterxml.jackson.databind.ObjectMapper; import net.sf.ehcache.Cache; import net.sf.ehcache.Element; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jasig.portlet.announcements.UnauthorizedException; import org.jasig.portlet.announcements.model.Announcement; import org.jasig.portlet.announcements.model.AnnouncementSortStrategy; import org.jasig.portlet.announcements.model.Topic; import org.jasig.portlet.announcements.model.TopicSubscription; import org.jasig.portlet.announcements.model.UserRoles; import org.jasig.portlet.announcements.mvc.IViewNameSelector; import org.jasig.portlet.announcements.service.IAnnouncementService; import org.jasig.portlet.announcements.service.ITopicSubscriptionService; import org.jasig.portlet.announcements.service.UserIdService; import org.jasig.portlet.announcements.service.UserPermissionChecker; import org.jasig.portlet.announcements.service.UserPermissionCheckerFactory; import org.jasig.portlet.notice.NotificationCategory; import org.jasig.portlet.notice.NotificationEntry; import org.jasig.portlet.notice.NotificationQuery; import org.jasig.portlet.notice.NotificationResponse; import org.jasig.portlet.notice.NotificationResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.ehcache.EhCacheCacheManager; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.portlet.bind.annotation.EventMapping; import org.springframework.web.portlet.bind.annotation.RenderMapping; import org.springframework.web.portlet.bind.annotation.ResourceMapping; @Controller @RequestMapping("VIEW") public class AnnouncementsViewController { public static final String ACTION_DISPLAY_FULL_ANNOUNCEMENT = "displayFullAnnouncement"; public static final String PREFERENCE_DISPLAY_STARTDATE = "AnnouncementsViewController.displayPublishDate"; public static final String PREFERENCE_DISABLE_EDIT = "AnnouncementsViewController.PREFERENCE_DISABLE_EDIT"; public static final String PREFERENCE_PAGE_SIZE = "AnnouncementsViewController.PAGE_SIZE"; public static final String PREFERENCE_SORT_STRATEGY = "AnnouncementsViewController.AnnouncementSortStrategy"; public static final String PREFERENCE_USE_SCROLLING_DISPLAY = "AnnouncementsViewController.useScrollingDisplay"; public static final String PREFERENCE_SCROLLING_DISPLAY_HEIGHT_PIXELS = "AnnouncementsViewController.scrollingDisplayHeightPixels"; public static final String PREFERENCE_HIDE_ABSTRACT = "AnnouncementsViewController.hideAbstract"; public static final String PREFERENCE_SYNDICATE_TOPICS_AS_NOTIFICATIONS = "AnnouncementsViewController.syndicateTopicsAsNotifications"; public static final String PREFERENCE_SYNDICATE_TOPICS_ANNOUNCEMENTS_DISPLAY_FNAME = "AnnouncementsViewController.syndicateTopicsAnnouncementsDisplayFName"; public static final String DEFAULT_SORT_STRATEGY = "START_DISPLAY_DATE_ASCENDING"; public static final String NOTIFICATION_NAMESPACE = "https://source.jasig.org/schemas/portlet/notification"; public static final String NOTIFICATION_QUERY_LOCAL_NAME = "NotificationQuery"; public static final QName NOTIFICATION_QUERY_QNAME = new QName(NOTIFICATION_NAMESPACE, NOTIFICATION_QUERY_LOCAL_NAME); public static final String NOTIFICATION_QUERY_QNAME_STRING = "{" + NOTIFICATION_NAMESPACE + "}" + NOTIFICATION_QUERY_LOCAL_NAME; public static final String NOTIFICATION_RESULT_LOCAL_NAME = "NotificationResult"; public static final QName NOTIFICATION_RESULT_QNAME = new QName(NOTIFICATION_NAMESPACE, NOTIFICATION_RESULT_LOCAL_NAME); public static final String NOTIFICATION_RESULT_QNAME_STRING = "{" + NOTIFICATION_NAMESPACE + "}" + NOTIFICATION_RESULT_LOCAL_NAME; @Autowired private final IAnnouncementService announcementService = null; private final ObjectMapper mapper = new ObjectMapper(); private final Log logger = LogFactory.getLog(getClass()); @Autowired private ITopicSubscriptionService tss = null; @Autowired private EhCacheCacheManager cm = null; @Autowired(required = true) private IViewNameSelector viewNameSelector; @Autowired private UserPermissionCheckerFactory userPermissionCheckerFactory; @Autowired private UserIdService userIdService; /** * Main method of this display controller. Calculates which topics should be shown to this user * and which announcements to show from those topics. */ @SuppressWarnings("unchecked") @RenderMapping() public String mainView(Model model, RenderRequest request, @RequestParam(value = "from", required = false) Integer from, @RequestParam(value = "to", required = false) Integer to) throws PortletException { if (from == null || to == null) { from = 0; to = (Integer) model.asMap().get("increment"); } final PortletPreferences prefs = request.getPreferences(); List[] lists = getLists(request); List<Announcement> announcements = lists[0]; List<Announcement> emergencyAnnouncements = lists[1]; // sort the list (since they are not sorted from the database) Comparator<Announcement> sortStrategy = AnnouncementSortStrategy .getStrategy(prefs.getValue(PREFERENCE_SORT_STRATEGY, DEFAULT_SORT_STRATEGY)); Collections.sort(announcements, sortStrategy); Collections.sort(emergencyAnnouncements, sortStrategy); // create a shortened list final Boolean useScrollingDisplay = (Boolean) model.asMap().get("useScrollingDisplay"); final List<Announcement> announcementsShort = useScrollingDisplay ? announcements : paginateAnnouncements(announcements, from, to, model); // Disable the edit link where appropriate Boolean disableEdit = Boolean.valueOf(prefs.getValue(PREFERENCE_DISABLE_EDIT, "false")); model.addAttribute("disableEdit", disableEdit); model.addAttribute("from", new Integer(from)); model.addAttribute("to", new Integer(to)); model.addAttribute("hasMore", (!useScrollingDisplay && announcements.size() > to)); model.addAttribute("announcements", announcementsShort); model.addAttribute("emergency", emergencyAnnouncements); model.addAttribute("hideAbstract", Boolean.valueOf(prefs.getValue(PREFERENCE_HIDE_ABSTRACT, "false"))); return viewNameSelector.select(request, "displayAnnouncements"); } private List[] getLists(PortletRequest request) throws PortletException { final String userId = userIdService.getUserId(request); // fetch the user's topic subscription from the database List<TopicSubscription> myTopics = tss.getTopicSubscription(request); Map<Boolean, List<Announcement>> topicLists = myTopics.stream().filter(TopicSubscription::getSubscribed) .map(ts -> ts.getTopic().getPublishedAnnouncements()).flatMap(x -> x.stream()) .collect(Collectors.partitioningBy( (Announcement a) -> a.getParent().getSubscriptionMethod() == Topic.EMERGENCY)); return new List[] { topicLists.get(false), topicLists.get(true) }; } @ResourceMapping(value = "emergencies") public void emergenciesResource(ResourceRequest request, ResourceResponse response) throws IOException, PortletException { logger.debug("Processing AJAX resource request for emergency alerts"); List[] lists = getLists(request); final String json = mapper.writeValueAsString(lists[1]); response.getWriter().write(json); } @ResourceMapping(value = "announcements") public void announcementsResource(ResourceRequest request, ResourceResponse response) throws IOException, PortletException { logger.debug("Processing AJAX resource request for announcements"); List[] lists = getLists(request); final String json = mapper.writeValueAsString(lists[0]); response.getWriter().write(json); } @RenderMapping(params = "action=" + ACTION_DISPLAY_FULL_ANNOUNCEMENT) public String displayFullAnnouncement(Model model, RenderRequest request, @RequestParam("announcementId") String announcementId) throws Exception { Announcement announcement = getAnnouncementById(request, announcementId); model.addAttribute("announcement", announcement); return viewNameSelector.select(request, "displayFullAnnouncement"); } @EventMapping(NOTIFICATION_QUERY_QNAME_STRING) public void syndicateAnnouncementsAsNotifications(final EventRequest req, final EventResponse res) throws PortletException { final NotificationQuery query = (NotificationQuery) req.getEvent().getValue(); logger.debug( "Syndicating announcements for Notification portlet with windowId=" + query.getQueryWindowId()); final PortletPreferences prefs = req.getPreferences(); final List<String> topicTitlesToSyndicate = Arrays .asList(prefs.getValues(PREFERENCE_SYNDICATE_TOPICS_AS_NOTIFICATIONS, new String[0])); // Get out if we know there's nothing to do... if (topicTitlesToSyndicate.isEmpty()) { logger.debug("No topics are defined for syndication with the Notification portlet"); return; } /* * Obtain the FName of the targeted AnnouncementsDisplay portlet for * building links. TODO: This logic needs to be moved to a pluggable * link-building strategy. */ final String announcementsDisplayFName = prefs .getValue(PREFERENCE_SYNDICATE_TOPICS_ANNOUNCEMENTS_DISPLAY_FNAME, "announcements"); logger.debug("Using announcementsDisplayFName=" + announcementsDisplayFName); final List<NotificationCategory> categories = new ArrayList<NotificationCategory>(); // fetch the user's topic subscription from the database final List<TopicSubscription> myTopics = tss.getTopicSubscription(req); for (TopicSubscription topicSub : myTopics) { final Topic topic = topicSub.getTopic(); // We only want the white-listed ones... if (!topicTitlesToSyndicate.contains(topic.getTitle())) { continue; } logger.debug("Considering topic '" + topic.getTitle() + "' for remoteUser=" + req.getRemoteUser()); final Set<Announcement> announcements = topic.getPublishedAnnouncements(); // Ignore any that are empty... if (announcements.isEmpty()) { continue; } final List<NotificationEntry> entries = new ArrayList<NotificationEntry>(); for (Announcement ann : announcements) { final NotificationEntry entry = new NotificationEntry(); entry.setTitle(ann.getTitle()); entry.setBody(ann.getAbstractText()); // Use abstract for body b/c notifications are intended to be smaller entry.setSource("Announcements"); // TODO: Don't hard-code /* * TODO: This area (building a URL) needs to be "factored out" * to an interface-based approach that supports pluggable, * configurable strategies. Ideally, furthermore, the strategy * for uPortal would leverage features that are not yet written -- * like the ability to get at uP's context name and the portlet's * fname -- or simply leverage a URL-generating API (which could * be added to the uPortal Platform API). * * EXAMPLE=/uPortal/p/AnnouncementsDisplay/max/render.uP?pP_action=displayFullAnnouncement&pP_announcementId=2 */ final StringBuilder url = new StringBuilder(); url.append("/uPortal") // TODO: Don't hard-code .append("/p/").append(announcementsDisplayFName) .append("/max/render.uP?pP_action=displayFullAnnouncement&pP_announcementId=") .append(ann.getId()); entry.setUrl(url.toString()); entries.add(entry); } final NotificationCategory category = new NotificationCategory(); category.setTitle(topic.getTitle()); category.setEntries(entries); categories.add(category); } logger.debug("Found the following categories for remoteUser '" + req.getRemoteUser() + "': " + categories); // We can bail if we haven't collected anything to share at this point... if (categories.isEmpty()) { return; } final NotificationResponse response = new NotificationResponse(); response.setCategories(categories); final NotificationResult result = new NotificationResult(); result.setQueryWindowId(query.getQueryWindowId()); result.setResultWindowId(req.getWindowID()); result.setNotificationResponse(response); res.setEvent(NOTIFICATION_RESULT_QNAME, result); } @ModelAttribute("displayPublishDate") public boolean getDisplayPublishDate(PortletRequest req) { PortletPreferences prefs = req.getPreferences(); return Boolean.parseBoolean(prefs.getValue(PREFERENCE_DISPLAY_STARTDATE, "false")); } public void setTss(ITopicSubscriptionService tss) { this.tss = tss; } public void setCm(EhCacheCacheManager cm) { this.cm = cm; } @RenderMapping(params = "action=displayFullAnnouncementHistory") public String displayFullAnnouncementHistory(Model model, RenderRequest request, @RequestParam("announcementId") String announcementId) throws Exception { Announcement announcement = getAnnouncementById(request, announcementId); model.addAttribute("announcement", announcement); return viewNameSelector.select(request, "displayFullAnnouncementHistory"); } @RenderMapping(params = "action=displayHistory") public String displayHistory(Model model, RenderRequest request) throws Exception { List<Announcement> announcements = new ArrayList<Announcement>(); // fetch the user's topic subscription from the database List<TopicSubscription> myTopics = tss.getTopicSubscription(request); // add all the published announcements of each subscribed topic to the // announcement list for (TopicSubscription ts : myTopics) { if (ts.getSubscribed() && ts.getTopic().getSubscriptionMethod() != Topic.EMERGENCY) { announcements.addAll(ts.getTopic().getHistoricAnnouncements()); } } // sort the list by end display date descending (since they are not // sorted from the database) Collections.sort(announcements, new Comparator<Announcement>() { @Override public int compare(Announcement s, Announcement s2) { return s2.getEndDisplay().compareTo(s.getEndDisplay()); } }); model.addAttribute("announcements", announcements); return viewNameSelector.select(request, "displayHistory"); } @ModelAttribute("increment") public int getPageSize(PortletRequest req) { final PortletPreferences prefs = req.getPreferences(); int rslt = 5; // default try { rslt = Integer.parseInt(prefs.getValue(PREFERENCE_PAGE_SIZE, "5")); } catch (NumberFormatException nfe) { // Log it, but roll on... logger.warn("Non-integer value encountered for " + PREFERENCE_PAGE_SIZE + ": " + prefs.getValue(PREFERENCE_PAGE_SIZE, null)); } return rslt; } @ModelAttribute("isGuest") public boolean isGuest(PortletRequest req) { boolean rslt = (req.getRemoteUser() == null); logger.debug("isGuest is: " + Boolean.toString(rslt)); logger.debug("remoteUser is: " + req.getRemoteUser()); return rslt; } @ModelAttribute("useScrollingDisplay") public boolean getUseScrollingDisplay(PortletRequest req) { final PortletPreferences prefs = req.getPreferences(); return Boolean.valueOf(prefs.getValue(PREFERENCE_USE_SCROLLING_DISPLAY, "false")); // default is false } @ModelAttribute("scrollingDisplayHeightPixels") public int getScrollingDisplayHeightPixels(PortletRequest req) { final PortletPreferences prefs = req.getPreferences(); int rslt = 500; // default try { rslt = Integer.parseInt(prefs.getValue(PREFERENCE_SCROLLING_DISPLAY_HEIGHT_PIXELS, "500")); } catch (NumberFormatException nfe) { // Log it, but roll on... logger.warn("Non-integer value encountered for " + PREFERENCE_SCROLLING_DISPLAY_HEIGHT_PIXELS + ": " + prefs.getValue(PREFERENCE_SCROLLING_DISPLAY_HEIGHT_PIXELS, null)); } return rslt; } /* * Implementation */ private List<Announcement> paginateAnnouncements(final List<Announcement> announcements, Integer from, Integer to, Model model) { List<Announcement> rslt; // if the announcement list is already short, then just reference it if (announcements.size() < to - from) { rslt = announcements; } // otherwise, just take the range requested and pass it along to the view else { rslt = new ArrayList<Announcement>(); for (int i = from; i < to && announcements.size() > i; i++) { if (announcements.get(i) != null) { rslt.add(announcements.get(i)); } } } return rslt; } private Announcement getAnnouncementById(PortletRequest request, String announcementId) throws Exception { Long annId = Long.valueOf(announcementId); Announcement announcement = announcementService.getAnnouncement(annId); if (!UserPermissionChecker.inRoleForTopic(request, UserRoles.AUDIENCE_ROLE_NAME, announcement.getParent())) { throw new UnauthorizedException(); } return announcement; } }