[SYNCOPE-669] Search filter in the notifications
Project: http://git-wip-us.apache.org/repos/asf/syncope/repo Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/d19133ef Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/d19133ef Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/d19133ef Branch: refs/heads/master Commit: d19133ef55221978395a1a933c03a8ba91018f12 Parents: eb99e24 b68778e Author: Marco Di Sabatino Di Diodoro <[email protected]> Authored: Thu May 21 17:09:57 2015 +0200 Committer: Marco Di Sabatino Di Diodoro <[email protected]> Committed: Thu May 21 17:09:57 2015 +0200 ---------------------------------------------------------------------- .../java/notification/NotificationManagerImpl.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/syncope/blob/d19133ef/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java ---------------------------------------------------------------------- diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java index c052971,0000000..7153529 mode 100644,000000..100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java @@@ -1,409 -1,0 +1,410 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 + * + * 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.apache.syncope.core.provisioning.java.notification; + +import org.apache.syncope.core.provisioning.api.notification.NotificationManager; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.AttributableType; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.syncope.common.lib.types.AuditElements.Result; +import org.apache.syncope.common.lib.types.AuditLoggerName; +import org.apache.syncope.common.lib.types.IntMappingType; +import org.apache.syncope.common.lib.types.SubjectType; +import org.apache.syncope.common.lib.CollectionUtils2; +import org.apache.syncope.core.persistence.api.dao.ConfDAO; +import org.apache.syncope.core.persistence.api.dao.NotificationDAO; +import org.apache.syncope.core.persistence.api.dao.GroupDAO; +import org.apache.syncope.core.persistence.api.dao.SubjectSearchDAO; +import org.apache.syncope.core.persistence.api.dao.TaskDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause; +import org.apache.syncope.core.persistence.api.entity.Attributable; +import org.apache.syncope.core.persistence.api.entity.AttributableUtilsFactory; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.Notification; +import org.apache.syncope.core.persistence.api.entity.PlainAttr; +import org.apache.syncope.core.persistence.api.entity.Subject; +import org.apache.syncope.core.persistence.api.entity.group.Group; +import org.apache.syncope.core.persistence.api.entity.task.NotificationTask; +import org.apache.syncope.core.persistence.api.entity.task.TaskExec; +import org.apache.syncope.core.persistence.api.entity.user.UDerAttr; +import org.apache.syncope.core.persistence.api.entity.user.UPlainAttr; +import org.apache.syncope.core.persistence.api.entity.user.UVirAttr; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.data.GroupDataBinder; +import org.apache.syncope.core.provisioning.api.data.UserDataBinder; +import org.apache.syncope.core.misc.ConnObjectUtils; +import org.apache.syncope.core.misc.search.SearchCondConverter; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.context.Context; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.tools.ToolManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@Transactional(rollbackFor = { Throwable.class }) +public class NotificationManagerImpl implements NotificationManager { + + /** + * Logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(NotificationManager.class); + + public static final String MAIL_TEMPLATES = "mailTemplates/"; + + public static final String MAIL_TEMPLATE_HTML_SUFFIX = ".html.vm"; + + public static final String MAIL_TEMPLATE_TEXT_SUFFIX = ".txt.vm"; + + /** + * Notification DAO. + */ + @Autowired + private NotificationDAO notificationDAO; + + /** + * Configuration DAO. + */ + @Autowired + private ConfDAO confDAO; + + /** + * User DAO. + */ + @Autowired + private UserDAO userDAO; + + /** + * Group DAO. + */ + @Autowired + private GroupDAO groupDAO; + + /** + * User Search DAO. + */ + @Autowired + private SubjectSearchDAO searchDAO; + + /** + * Task DAO. + */ + @Autowired + private TaskDAO taskDAO; + + /** + * Velocity template engine. + */ + @Autowired + private VelocityEngine velocityEngine; + + /** + * Velocity tool manager. + */ + @Autowired + private ToolManager velocityToolManager; + + @Autowired + private ConnObjectUtils connObjectUtils; + + @Autowired + private UserDataBinder userDataBinder; + + @Autowired + private GroupDataBinder groupDataBinder; + + @Autowired + private EntityFactory entityFactory; + + @Autowired + private AttributableUtilsFactory attrUtilsFactory; + + @Transactional(readOnly = true) + @Override + public long getMaxRetries() { + return confDAO.find("notification.maxRetries", "0").getValues().get(0).getLongValue(); + } + + /** + * Create a notification task. + * + * @param notification notification to take as model + * @param attributable the user this task is about + * @param model Velocity model + * @return notification task, fully populated + */ + private NotificationTask getNotificationTask( + final Notification notification, + final Attributable<?, ?, ?> attributable, + final Map<String, Object> model) { + + if (attributable != null) { + connObjectUtils.retrieveVirAttrValues(attributable, + attrUtilsFactory.getInstance( + attributable instanceof User ? AttributableType.USER : AttributableType.GROUP)); + } + + final List<User> recipients = new ArrayList<>(); + + if (notification.getRecipients() != null) { + recipients.addAll(searchDAO.<User>search(SyncopeConstants.FULL_ADMIN_REALMS, + SearchCondConverter.convert(notification.getRecipients()), + Collections.<OrderByClause>emptyList(), SubjectType.USER)); + } + + if (notification.isSelfAsRecipient() && attributable instanceof User) { + recipients.add((User) attributable); + } + + final Set<String> recipientEmails = new HashSet<>(); + final List<UserTO> recipientTOs = new ArrayList<>(recipients.size()); + for (User recipient : recipients) { + connObjectUtils.retrieveVirAttrValues(recipient, attrUtilsFactory.getInstance(AttributableType.USER)); + + String email = getRecipientEmail(notification.getRecipientAttrType(), + notification.getRecipientAttrName(), recipient); + if (email == null) { + LOG.warn("{} cannot be notified: {} not found", recipient, notification.getRecipientAttrName()); + } else { + recipientEmails.add(email); + recipientTOs.add(userDataBinder.getUserTO(recipient)); + } + } + + if (notification.getStaticRecipients() != null) { + recipientEmails.addAll(notification.getStaticRecipients()); + } + + model.put("recipients", recipientTOs); + model.put("syncopeConf", this.findAllSyncopeConfs()); + model.put("events", notification.getEvents()); + + NotificationTask task = entityFactory.newEntity(NotificationTask.class); + task.setTraceLevel(notification.getTraceLevel()); + task.getRecipients().addAll(recipientEmails); + task.setSender(notification.getSender()); + task.setSubject(notification.getSubject()); + + String htmlBody = mergeTemplateIntoString( + MAIL_TEMPLATES + notification.getTemplate() + MAIL_TEMPLATE_HTML_SUFFIX, model); + String textBody = mergeTemplateIntoString( + MAIL_TEMPLATES + notification.getTemplate() + MAIL_TEMPLATE_TEXT_SUFFIX, model); + + task.setHtmlBody(htmlBody); + task.setTextBody(textBody); + + return task; + } + + private String mergeTemplateIntoString(final String templateLocation, final Map<String, Object> model) { + StringWriter result = new StringWriter(); + try { + Context velocityContext = createVelocityContext(model); + velocityEngine.mergeTemplate(templateLocation, SyncopeConstants.DEFAULT_ENCODING, velocityContext, result); + } catch (VelocityException e) { + LOG.error("Could not get mail body", e); + } catch (RuntimeException e) { + // ensure same behaviour as by using Spring VelocityEngineUtils.mergeTemplateIntoString() + throw e; + } catch (Exception e) { + LOG.error("Could not get mail body", e); + } + + return result.toString(); + } + + /** + * Create a Velocity Context for the given model, to be passed to the template for merging. + * + * @param model Velocity model + * @return Velocity context + */ + protected Context createVelocityContext(final Map<String, Object> model) { + Context toolContext = velocityToolManager.createContext(); + return new VelocityContext(model, toolContext); + } + + @Override + public void createTasks( + final AuditElements.EventCategoryType type, + final String category, + final String subcategory, + final String event, + final Result condition, + final Object before, + final Object output, + final Object... input) { + + SubjectType subjectType = null; + Subject<?, ?, ?> subject = null; + + if (before instanceof UserTO) { + subjectType = SubjectType.USER; + subject = userDAO.find(((UserTO) before).getKey()); + } else if (output instanceof UserTO) { + subjectType = SubjectType.USER; + subject = userDAO.find(((UserTO) output).getKey()); + } else if (before instanceof GroupTO) { + subjectType = SubjectType.GROUP; + subject = groupDAO.find(((GroupTO) before).getKey()); + } else if (output instanceof GroupTO) { + subjectType = SubjectType.GROUP; + subject = groupDAO.find(((GroupTO) output).getKey()); + } + + LOG.debug("Search notification for [{}]{}", subjectType, subject); + + for (Notification notification : notificationDAO.findAll()) { + LOG.debug("Notification available user about {}", notification.getUserAbout()); + LOG.debug("Notification available group about {}", notification.getGroupAbout()); + + if (notification.isActive()) { + String currentEvent = AuditLoggerName.buildEvent(type, category, subcategory, event, condition); + if (!notification.getEvents().contains(currentEvent)) { + LOG.debug("No events found about {}", subject); + } else if (subjectType == null || subject == null - || notification.getUserAbout() == null || notification.getGroupAbout() == null ++ || (subjectType == SubjectType.USER && (notification.getUserAbout() == null + || searchDAO.matches(subject, - SearchCondConverter.convert(notification.getUserAbout()), subjectType) ++ SearchCondConverter.convert(notification.getUserAbout()), subjectType))) ++ || subjectType == SubjectType.GROUP && (notification.getGroupAbout() == null + || searchDAO.matches(subject, - SearchCondConverter.convert(notification.getGroupAbout()), subjectType)) { ++ SearchCondConverter.convert(notification.getGroupAbout()), subjectType))) { + + LOG.debug("Creating notification task for event {} about {}", currentEvent, subject); + + final Map<String, Object> model = new HashMap<>(); + model.put("type", type); + model.put("category", category); + model.put("subcategory", subcategory); + model.put("event", event); + model.put("condition", condition); + model.put("before", before); + model.put("output", output); + model.put("input", input); + + if (subject instanceof User) { + model.put("user", userDataBinder.getUserTO((User) subject)); + } else if (subject instanceof Group) { + model.put("group", groupDataBinder.getGroupTO((Group) subject)); + } + + taskDAO.save(getNotificationTask(notification, subject, model)); + } + } else { + LOG.debug("Notification {}, userAbout {}, groupAbout {} is deactivated, " + + "notification task will not be created", notification.getKey(), + notification.getUserAbout(), notification.getGroupAbout()); + } + } + } + + private String getRecipientEmail( + final IntMappingType recipientAttrType, final String recipientAttrName, final User user) { + + String email = null; + + switch (recipientAttrType) { + case Username: + email = user.getUsername(); + break; + + case UserPlainSchema: + UPlainAttr attr = user.getPlainAttr(recipientAttrName); + if (attr != null) { + email = CollectionUtils2.getFirstOrNull(attr.getValuesAsStrings()); + } + break; + + case UserDerivedSchema: + UDerAttr derAttr = user.getDerAttr(recipientAttrName); + if (derAttr != null) { + email = derAttr.getValue(user.getPlainAttrs()); + } + break; + + case UserVirtualSchema: + UVirAttr virAttr = user.getVirAttr(recipientAttrName); + if (virAttr != null) { + email = CollectionUtils2.getFirstOrNull(virAttr.getValues()); + } + break; + + default: + } + + return email; + } + + @Override + public TaskExec storeExec(final TaskExec execution) { + NotificationTask task = taskDAO.find(execution.getTask().getKey()); + task.addExec(execution); + task.setExecuted(true); + taskDAO.save(task); + // this flush call is needed to generate a value for the execution id + taskDAO.flush(); + return execution; + } + + @Override + public void setTaskExecuted(final Long taskId, final boolean executed) { + NotificationTask task = taskDAO.find(taskId); + task.setExecuted(executed); + taskDAO.save(task); + } + + @Override + public long countExecutionsWithStatus(final Long taskId, final String status) { + NotificationTask task = taskDAO.find(taskId); + long count = 0; + for (TaskExec taskExec : task.getExecs()) { + if (status == null) { + if (taskExec.getStatus() == null) { + count++; + } + } else if (status.equals(taskExec.getStatus())) { + count++; + } + } + return count; + } + + protected Map<String, String> findAllSyncopeConfs() { + Map<String, String> syncopeConfMap = new HashMap<>(); + for (PlainAttr attr : confDAO.get().getPlainAttrs()) { + syncopeConfMap.put(attr.getSchema().getKey(), attr.getValuesAsStrings().get(0)); + } + return syncopeConfMap; + } +}
