/** * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at the * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Initial code contributed and copyrighted by<br> * frentix GmbH, http://www.frentix.com * <p> */ package org.olat.modules.portfolio.manager; import static org.apache.commons.lang.time.DateUtils.isSameDay; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.olat.basesecurity.IdentityNames; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.services.notifications.NotificationsHandler; import org.olat.core.commons.services.notifications.NotificationsManager; import org.olat.core.commons.services.notifications.Publisher; import org.olat.core.commons.services.notifications.Subscriber; import org.olat.core.commons.services.notifications.SubscriptionInfo; import org.olat.core.commons.services.notifications.model.SubscriptionListItem; import org.olat.core.commons.services.notifications.model.TitleItem; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.modules.forms.EvaluationFormSession; import org.olat.modules.portfolio.Binder; import org.olat.modules.portfolio.BinderSecurityCallback; import org.olat.modules.portfolio.BinderSecurityCallbackFactory; import org.olat.modules.portfolio.Page; import org.olat.modules.portfolio.PortfolioRoles; import org.olat.modules.portfolio.PortfolioService; import org.olat.modules.portfolio.Section; import org.olat.modules.portfolio.model.AccessRights; import org.olat.modules.portfolio.ui.PortfolioHomeController; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryService; import org.olat.user.UserManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * * Initial date: 11.07.2016<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ @Service public class PortfolioNotificationsHandler implements NotificationsHandler { public static final String TYPE_NAME = Binder.class.getSimpleName(); @Autowired private DB dbInstance; @Autowired private BinderDAO binderDao; @Autowired private UserManager userManager; @Autowired private PortfolioService portfolioService; @Autowired private RepositoryService repositoryService; @Override public SubscriptionInfo createSubscriptionInfo(Subscriber subscriber, Locale locale, Date compareDate) { SubscriptionInfo si = null; Publisher publisher = subscriber.getPublisher(); Binder binder = binderDao.loadByKey(publisher.getResId()); if (isInkoveValid(binder, compareDate, publisher)) { BinderSecurityCallback secCallback = null; Identity identity = subscriber.getIdentity(); if(binderDao.isMember(binder, identity, PortfolioRoles.owner.name())) { secCallback = BinderSecurityCallbackFactory.getCallbackForOwnedBinder(binder); } else { List<AccessRights> rights = portfolioService.getAccessRights(binder, identity); if(rights.size() > 0) { secCallback = BinderSecurityCallbackFactory.getCallbackForCoach(binder, rights); } } if(secCallback != null) { si = new SubscriptionInfo(subscriber.getKey(), publisher.getType(), getTitleItemForBinder(binder), null); List<SubscriptionListItem> allItems = getAllItems(binder, secCallback, compareDate, locale); for (SubscriptionListItem item : allItems) { //only a type of icon SubscriptionListItem clonedItem = new SubscriptionListItem(item.getDescription(), item.getDescriptionTooltip(), item.getLink(), item.getBusinessPath(), item.getDate(), "o_ep_icon"); si.addSubscriptionListItem(clonedItem); } } } if (si == null) { // no info, return empty si = NotificationsManager.getInstance().getNoSubscriptionInfo(); } return si; } private boolean isInkoveValid(Binder binder, Date compareDate, Publisher publisher) { return (binder != null && compareDate != null && NotificationsManager.getInstance().isPublisherValid(publisher)); } @Override public String createTitleInfo(Subscriber subscriber, Locale locale) { TitleItem title = getTitleItemForPublisher(subscriber.getPublisher()); return title.getInfoContent("text/plain"); } @Override public String getType() { return TYPE_NAME; } public List<SubscriptionListItem> getAllItems(Binder binder, BinderSecurityCallback secCallback, Date compareDate, Locale locale) { String rootBusinessPath = "[Binder:" + binder.getKey() + "]"; if(binder.getOlatResource() != null) { RepositoryEntry re = repositoryService.loadByResourceKey(binder.getOlatResource().getKey()); rootBusinessPath = "[RepositoryEntry:" + re.getKey() + "]"; } else { rootBusinessPath = "[Binder:" + binder.getKey() + "]"; } Translator translator = Util.createPackageTranslator(PortfolioHomeController.class, locale); List<SubscriptionListItem> items = new ArrayList<>(); items.addAll(getCommentNotifications(binder, secCallback, compareDate, rootBusinessPath, translator)); items.addAll(getPageNotifications(binder, secCallback, compareDate, rootBusinessPath, translator)); items.addAll(getSectionNotifications(binder, secCallback, compareDate, rootBusinessPath, translator)); items.addAll(getEvaluationNotifications(binder, secCallback, compareDate, rootBusinessPath, translator)); Collections.sort(items, new PortfolioNotificationComparator()); return items; } public List<SubscriptionListItem> getSectionNotifications(Binder binder, BinderSecurityCallback secCallback, Date compareDate, String rootBusinessPath, Translator translator) { StringBuilder sb = new StringBuilder(); sb.append("select section") .append(" from pfsection as section") .append(" inner join fetch section.binder as binder") .append(" where binder.key=:binderKey and section.lastModified>=:compareDate"); List<Section> sections = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Section.class) .setParameter("binderKey", binder.getKey()) .setParameter("compareDate", compareDate) .getResultList(); Set<Long> uniqueSectionKeys = new HashSet<>(); Set<Long> uniqueCreateSectionKeys = new HashSet<>(); List<SubscriptionListItem> items = new ArrayList<>(sections.size()); for (Section section:sections) { //section Long sectionKey = section.getKey(); String sectionTitle = section.getTitle(); Date sectionCreationDate = section.getCreationDate(); Date sectionLastModified = section.getLastModified(); if(secCallback.canViewElement(section)) { if(isSameDay(sectionCreationDate, sectionLastModified)) { if(!uniqueCreateSectionKeys.contains(sectionKey)) { uniqueCreateSectionKeys.add(sectionKey); SubscriptionListItem item = sectionCreateItem(sectionKey, sectionTitle, sectionCreationDate, rootBusinessPath, translator); items.add(item); } } else if(!uniqueSectionKeys.contains(sectionKey)) { uniqueSectionKeys.add(sectionKey); SubscriptionListItem item = sectionModifiedItem(sectionKey, sectionTitle, sectionLastModified, rootBusinessPath, translator); items.add(item); } } } return items; } /** * Query the changes in the binder from the section to the page part. * * * @param binder * @param compareDate * @param rootBusinessPath * @param translator * @return */ public List<SubscriptionListItem> getPageNotifications(Binder binder, BinderSecurityCallback secCallback, Date compareDate, String rootBusinessPath, Translator translator) { StringBuilder sb = new StringBuilder(); sb.append("select page,") .append(" pagepart.lastModified as pagepartLastModified") .append(" from pfpage as page") .append(" inner join fetch page.section as section") .append(" inner join fetch section.binder as binder") .append(" left join pfpagepart as pagepart on (pagepart.body.key = page.body.key)") .append(" where binder.key=:binderKey and (pagepart.lastModified>=:compareDate or page.lastModified>=:compareDate)"); List<Object[]> objects = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Object[].class) .setParameter("binderKey", binder.getKey()) .setParameter("compareDate", compareDate) .getResultList(); Map<Long,SubscriptionListItem> uniquePartKeys = new HashMap<>(); Map<Long,SubscriptionListItem> uniqueCreatePageKeys = new HashMap<>(); List<SubscriptionListItem> items = new ArrayList<>(objects.size()); for (Object[] object : objects) { //page Page page = (Page)object[0]; Long pageKey = page.getKey(); String pageTitle = page.getTitle(); Date pageCreationDate = page.getCreationDate(); Date pageLastModified = page.getLastModified(); //page part Date partLastModified = (Date)object[1]; if(secCallback.canViewElement(page)) { // page created if(isSameDay(pageCreationDate, pageLastModified) && pageCreationDate.compareTo(compareDate) >= 0) { if(!uniqueCreatePageKeys.containsKey(pageKey)) { SubscriptionListItem item = pageCreateItem(pageKey, pageTitle, pageCreationDate, rootBusinessPath, translator); uniqueCreatePageKeys.put(pageKey, item); } } else { if(uniquePartKeys.containsKey(pageKey)) { SubscriptionListItem item = uniquePartKeys.get(pageKey); SubscriptionListItem potentitalItem = pageModifiedItem( pageKey, pageTitle, pageLastModified, partLastModified, rootBusinessPath, translator); if(item.getDate().before(potentitalItem.getDate())) { uniquePartKeys.put(pageKey, potentitalItem); } } else if(pageLastModified.compareTo(compareDate) >= 0 || (partLastModified != null && partLastModified.compareTo(compareDate) >= 0)) { SubscriptionListItem item = pageModifiedItem( pageKey, pageTitle, pageLastModified, partLastModified, rootBusinessPath, translator); boolean overlapCreate = false; if(uniqueCreatePageKeys.containsKey(pageKey)) { SubscriptionListItem createItem = uniqueCreatePageKeys.get(pageKey); overlapCreate = isSameDay(item.getDate(), createItem.getDate()); } if(!overlapCreate) { uniquePartKeys.put(pageKey, item); } } } } } items.addAll(uniquePartKeys.values()); items.addAll(uniqueCreatePageKeys.values()); return items; } public List<SubscriptionListItem> getEvaluationNotifications(Binder binder, BinderSecurityCallback secCallback, Date compareDate, String rootBusinessPath, Translator translator) { StringBuilder sb = new StringBuilder(); sb.append("select page, evasession") .append(" from pfpage as page") .append(" inner join fetch page.section as section") .append(" inner join fetch section.binder as binder") .append(" left join evaluationformsession as evasession on (page.body.key = evasession.pageBody.key)") .append(" where binder.key=:binderKey and evasession.status='done' and evasession.submissionDate>=:compareDate"); List<Object[]> objects = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Object[].class) .setParameter("binderKey", binder.getKey()) .setParameter("compareDate", compareDate) .getResultList(); List<SubscriptionListItem> items = new ArrayList<>(objects.size()); for (Object[] object : objects) { //page Page page = (Page)object[0]; Long pageKey = page.getKey(); String pageTitle = page.getTitle(); //session EvaluationFormSession evaluationSession = (EvaluationFormSession)object[1]; Date submissionDate = evaluationSession.getSubmissionDate(); Date firstSubmissionDate = evaluationSession.getFirstSubmissionDate(); if(submissionDate != null && secCallback.canViewElement(page)) { if(submissionDate.compareTo(firstSubmissionDate) == 0) { SubscriptionListItem item = evaluationNewItem(pageKey, pageTitle, submissionDate, rootBusinessPath, translator); items.add(item); } else { SubscriptionListItem item = evaluationModifiedItem(pageKey, pageTitle, submissionDate, rootBusinessPath, translator); items.add(item); } } } return items; } private SubscriptionListItem sectionCreateItem(Long sectionKey, String sectionTitle, Date sectionCreationDate, String rootBusinessPath, Translator translator) { String title = translator.translate("notifications.new.section", new String[]{ sectionTitle }); String bPath = rootBusinessPath + "[Section:" + sectionKey + "]"; String linkUrl = BusinessControlFactory.getInstance().getURLFromBusinessPathString(bPath); SubscriptionListItem item = new SubscriptionListItem(title, linkUrl, bPath, sectionCreationDate, "o_icon_pf_section"); item.setUserObject(sectionKey); return item; } private SubscriptionListItem sectionModifiedItem(Long sectionKey, String sectionTitle, Date sectionLastModified, String rootBusinessPath, Translator translator) { String title = translator.translate("notifications.modified.section", new String[]{ sectionTitle }); String bPath = rootBusinessPath + "[Section:" + sectionKey + "]"; String linkUrl = BusinessControlFactory.getInstance().getURLFromBusinessPathString(bPath); SubscriptionListItem item = new SubscriptionListItem(title, linkUrl, bPath, sectionLastModified, "o_icon_pf_section"); item.setUserObject(sectionKey); return item; } private SubscriptionListItem pageCreateItem(Long pageKey, String pageTitle, Date pageCreationDate, String rootBusinessPath, Translator translator) { String title = translator.translate("notifications.new.page", new String[]{ pageTitle }); String bPath = rootBusinessPath + "[Page:" + pageKey + "]"; String linkUrl = BusinessControlFactory.getInstance().getURLFromBusinessPathString(bPath); SubscriptionListItem item = new SubscriptionListItem(title, linkUrl, bPath, pageCreationDate, "o_icon_pf_page"); item.setUserObject(pageKey); return item; } private SubscriptionListItem pageModifiedItem(Long pageKey, String pageTitle, Date pageLastModified, Date partLastModified, String rootBusinessPath, Translator translator) { String title = translator.translate("notifications.modified.page", new String[]{ pageTitle }); Date date; if(partLastModified != null && partLastModified.compareTo(pageLastModified) > 0) { date = partLastModified; } else { date = pageLastModified; } String bPath = rootBusinessPath + "[Page:" + pageKey + "]"; String linkUrl = BusinessControlFactory.getInstance().getURLFromBusinessPathString(bPath); SubscriptionListItem item = new SubscriptionListItem(title, linkUrl, bPath, date, "o_icon_pf_page"); item.setUserObject(pageKey); return item; } private SubscriptionListItem evaluationNewItem(Long pageKey, String pageTitle, Date pageCreationDate, String rootBusinessPath, Translator translator) { String title = translator.translate("notifications.new.evaluation", new String[]{ pageTitle }); String bPath = rootBusinessPath + "[Page:" + pageKey + "]"; String linkUrl = BusinessControlFactory.getInstance().getURLFromBusinessPathString(bPath); SubscriptionListItem item = new SubscriptionListItem(title, linkUrl, bPath, pageCreationDate, "o_icon_pf_page"); item.setUserObject(pageKey); return item; } private SubscriptionListItem evaluationModifiedItem(Long pageKey, String pageTitle, Date pageCreationDate, String rootBusinessPath, Translator translator) { String title = translator.translate("notifications.modified.evaluation", new String[]{ pageTitle }); String bPath = rootBusinessPath + "[Page:" + pageKey + "]"; String linkUrl = BusinessControlFactory.getInstance().getURLFromBusinessPathString(bPath); SubscriptionListItem item = new SubscriptionListItem(title, linkUrl, bPath, pageCreationDate, "o_icon_pf_page"); item.setUserObject(pageKey); return item; } public List<SubscriptionListItem> getCommentNotifications(Binder binder, BinderSecurityCallback secCallback, Date compareDate, String rootBusinessPath, Translator translator) { StringBuilder sb = new StringBuilder(); sb.append("select") .append(" comment.id as commentId,") .append(" comment.creationDate as commentDate,") .append(" author.key as authorKey,") .append(" author.name as authorName,") .append(" authorUser.firstName as authorFirstName,") .append(" authorUser.lastName as authorLastName, ") .append(" page") .append(" from usercomment as comment") .append(" inner join comment.creator as author") .append(" inner join author.user as authorUser") .append(" inner join pfpage as page on (comment.resId=page.key and comment.resName='Page')") .append(" inner join fetch pfsection as section on (section.key = page.section.key)") .append(" inner join fetch pfbinder as binder on (binder.key=section.binder.key)") .append(" where binder.key=:binderKey and comment.creationDate>=:compareDate"); List<Object[]> objects = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Object[].class) .setParameter("binderKey", binder.getKey()) .setParameter("compareDate", compareDate) .getResultList(); List<SubscriptionListItem> items = new ArrayList<>(objects.size()); for (Object[] object : objects) { Long commentId = (Long)object[0]; Date commentDate = (Date)object[1]; NotificationIdentityNames author = getIdentityNames(object, 2); Page page = (Page)object[6]; Long pageKey = page.getKey(); String pageTitle = page.getTitle(); if(secCallback.canViewElement(page)) { String bPath = rootBusinessPath + "[Page:" + pageKey + "][Comment:" + commentId + "]"; String linkUrl = BusinessControlFactory.getInstance().getURLFromBusinessPathString(bPath); String[] title = new String[] { pageTitle, userManager.getUserDisplayName(author) }; SubscriptionListItem item = new SubscriptionListItem(translator.translate("notifications.new.comment", title), linkUrl, bPath, commentDate, "o_icon_comments"); item.setUserObject(pageKey); items.add(item); } } return items; } private NotificationIdentityNames getIdentityNames(Object[] object, int startIndex) { Long key = (Long)object[startIndex++]; String name = (String)object[startIndex++]; String firstName = (String)object[startIndex++]; String lastName = (String)object[startIndex++]; return new NotificationIdentityNames(key, name, firstName, lastName); } /** * returns a TitleItem instance for the given Publisher p If you already * have a reference to the map, use * <code>getTitleItemForMap(EPAbstractMap amap)</code> * * @param p * @return */ private TitleItem getTitleItemForPublisher(Publisher p) { Binder binder = binderDao.loadByKey(p.getResId()); return getTitleItemForBinder(binder); } /** * returns a TitleItem instance for the given AbstractMap * * @param amap * @return */ private TitleItem getTitleItemForBinder(Binder binder) { StringBuilder sb = new StringBuilder(); if (binder != null) { sb.append(StringHelper.escapeHtml(binder.getTitle())); List<Identity> owners = binderDao.getMembers(binder, PortfolioRoles.owner.name()); if(owners.size() > 0) { sb.append(" ("); for(int i=0; i<owners.size(); i++) { if(i > 0) sb.append(", "); String fullname = userManager.getUserDisplayName(owners.get(0)); sb.append(StringHelper.escapeHtml(fullname)); } sb.append(")"); } } return new TitleItem(sb.toString(), "o_icon_pf_binder"); } public static class NotificationIdentityNames implements IdentityNames { private final Long key; private final String name; private final String firstName; private final String lastName; public NotificationIdentityNames(Long key, String name, String firstName, String lastName) { this.key = key; this.name = name; this.firstName = firstName; this.lastName = lastName; } @Override public Long getKey() { return key; } @Override public String getName() { return name; } @Override public String getFirstName() { return firstName; } @Override public String getLastName() { return lastName; } } public static class PortfolioNotificationComparator implements Comparator<SubscriptionListItem> { @Override public int compare(SubscriptionListItem o1, SubscriptionListItem o2) { return -o1.getDate().compareTo(o2.getDate()); } } }