1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.provisioning.java.job.notification;
20
21 import java.time.OffsetDateTime;
22 import java.util.List;
23 import org.apache.commons.lang3.StringUtils;
24 import org.apache.syncope.common.lib.types.AuditElements;
25 import org.apache.syncope.common.lib.types.TaskType;
26 import org.apache.syncope.common.lib.types.TraceLevel;
27 import org.apache.syncope.core.persistence.api.dao.TaskDAO;
28 import org.apache.syncope.core.persistence.api.entity.task.NotificationTask;
29 import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
30 import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
31 import org.apache.syncope.core.provisioning.api.AuditManager;
32 import org.apache.syncope.core.provisioning.api.event.JobStatusEvent;
33 import org.apache.syncope.core.provisioning.api.job.JobManager;
34 import org.apache.syncope.core.provisioning.api.notification.NotificationJobDelegate;
35 import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
36 import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
37 import org.apache.syncope.core.spring.security.AuthContextUtils;
38 import org.quartz.JobExecutionException;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41 import org.springframework.context.ApplicationEventPublisher;
42 import org.springframework.transaction.annotation.Transactional;
43
44 public abstract class AbstractNotificationJobDelegate implements NotificationJobDelegate {
45
46 protected static final Logger LOG = LoggerFactory.getLogger(NotificationJobDelegate.class);
47
48 protected final TaskDAO taskDAO;
49
50 protected final TaskUtilsFactory taskUtilsFactory;
51
52 protected final AuditManager auditManager;
53
54 protected final NotificationManager notificationManager;
55
56 protected final ApplicationEventPublisher publisher;
57
58 protected boolean interrupt;
59
60 protected boolean interrupted;
61
62 protected AbstractNotificationJobDelegate(
63 final TaskDAO taskDAO,
64 final TaskUtilsFactory taskUtilsFactory,
65 final AuditManager auditManager,
66 final NotificationManager notificationManager,
67 final ApplicationEventPublisher publisher) {
68
69 this.taskDAO = taskDAO;
70 this.taskUtilsFactory = taskUtilsFactory;
71 this.auditManager = auditManager;
72 this.notificationManager = notificationManager;
73 this.publisher = publisher;
74 }
75
76 protected void setStatus(final String status) {
77 publisher.publishEvent(new JobStatusEvent(this, JobManager.NOTIFICATION_JOB.getName(), status));
78 }
79
80 @Override
81 public void interrupt() {
82 interrupt = true;
83 }
84
85 @Override
86 public boolean isInterrupted() {
87 return interrupted;
88 }
89
90 protected abstract void notify(String to, NotificationTask task, TaskExec<NotificationTask> execution)
91 throws Exception;
92
93 @Transactional
94 @Override
95 public TaskExec<NotificationTask> executeSingle(final NotificationTask task, final String executor) {
96 TaskExec<NotificationTask> execution = taskUtilsFactory.getInstance(TaskType.NOTIFICATION).newTaskExec();
97 execution.setTask(task);
98 execution.setStart(OffsetDateTime.now());
99 execution.setExecutor(executor);
100 boolean retryPossible = true;
101
102 if (StringUtils.isBlank(task.getSubject()) || task.getRecipients().isEmpty()
103 || StringUtils.isBlank(task.getHtmlBody()) || StringUtils.isBlank(task.getTextBody())) {
104
105 String message = "Could not fetch all required information for sending e-mails:\n"
106 + task.getRecipients() + '\n'
107 + task.getSender() + '\n'
108 + task.getSubject() + '\n'
109 + task.getHtmlBody() + '\n'
110 + task.getTextBody();
111 LOG.error(message);
112
113 execution.setStatus(NotificationJob.Status.NOT_SENT.name());
114 retryPossible = false;
115
116 if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) {
117 execution.setMessage(message);
118 }
119 } else {
120 if (LOG.isDebugEnabled()) {
121 LOG.debug("About to send notifications:\n"
122 + task.getRecipients() + '\n'
123 + task.getSender() + '\n'
124 + task.getSubject() + '\n'
125 + task.getHtmlBody() + '\n'
126 + task.getTextBody() + '\n');
127 }
128
129 setStatus("Sending notifications to " + task.getRecipients());
130
131 for (String to : task.getRecipients()) {
132 try {
133 notify(to, task, execution);
134
135 notificationManager.createTasks(
136 AuthContextUtils.getWho(),
137 AuditElements.EventCategoryType.TASK,
138 "notification",
139 null,
140 "send",
141 AuditElements.Result.SUCCESS,
142 null,
143 null,
144 task,
145 "Successfully sent notification to " + to);
146 } catch (Exception e) {
147 LOG.error("Could not send out notification", e);
148
149 execution.setStatus(NotificationJob.Status.NOT_SENT.name());
150 if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) {
151 execution.setMessage(ExceptionUtils2.getFullStackTrace(e));
152 }
153
154 notificationManager.createTasks(
155 AuthContextUtils.getWho(),
156 AuditElements.EventCategoryType.TASK,
157 "notification",
158 null,
159 "send",
160 AuditElements.Result.FAILURE,
161 null,
162 null,
163 task,
164 "Could not send notification to " + to, e);
165 }
166
167 execution.setEnd(OffsetDateTime.now());
168 }
169 }
170
171 if (hasToBeRegistered(execution)) {
172 execution = notificationManager.storeExec(execution);
173 if (retryPossible
174 && (NotificationJob.Status.valueOf(execution.getStatus()) == NotificationJob.Status.NOT_SENT)) {
175
176 handleRetries(execution);
177 }
178 } else {
179 notificationManager.setTaskExecuted(execution.getTask().getKey(), true);
180 }
181
182 return execution;
183 }
184
185 @Transactional
186 @Override
187 public void execute(final String executor) throws JobExecutionException {
188 List<NotificationTask> tasks = taskDAO.<NotificationTask>findToExec(TaskType.NOTIFICATION);
189
190 setStatus("Sending out " + tasks.size() + " notifications");
191
192 for (int i = 0; i < tasks.size() && !interrupt; i++) {
193 LOG.debug("Found notification task {} to be executed: starting...", tasks.get(i));
194 executeSingle(tasks.get(i), executor);
195 LOG.debug("Notification task {} executed", tasks.get(i));
196 }
197 if (interrupt) {
198 LOG.debug("Notification job interrupted");
199 interrupted = true;
200 }
201
202 setStatus(null);
203 }
204
205 protected static boolean hasToBeRegistered(final TaskExec<NotificationTask> execution) {
206 NotificationTask task = execution.getTask();
207
208
209
210 return (NotificationJob.Status.valueOf(execution.getStatus()) == NotificationJob.Status.NOT_SENT
211 && task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
212 || task.getTraceLevel() == TraceLevel.ALL;
213 }
214
215 protected void handleRetries(final TaskExec<NotificationTask> execution) {
216 if (notificationManager.getMaxRetries() <= 0) {
217 return;
218 }
219
220 long failedExecutionsCount = notificationManager.countExecutionsWithStatus(
221 execution.getTask().getKey(), NotificationJob.Status.NOT_SENT.name());
222
223 if (failedExecutionsCount <= notificationManager.getMaxRetries()) {
224 LOG.debug("Execution of notification task {} will be retried [{}/{}]",
225 execution.getTask(), failedExecutionsCount, notificationManager.getMaxRetries());
226 notificationManager.setTaskExecuted(execution.getTask().getKey(), false);
227
228 auditManager.audit(
229 AuthContextUtils.getWho(),
230 AuditElements.EventCategoryType.TASK,
231 "notification",
232 null,
233 "retry",
234 AuditElements.Result.SUCCESS,
235 null,
236 null,
237 execution,
238 "Notification task " + execution.getTask().getKey() + " will be retried");
239 } else {
240 LOG.error("Maximum number of retries reached for task {} - giving up", execution.getTask());
241
242 auditManager.audit(
243 AuthContextUtils.getWho(),
244 AuditElements.EventCategoryType.TASK,
245 "notification",
246 null,
247 "retry",
248 AuditElements.Result.FAILURE,
249 null,
250 null,
251 execution,
252 "Giving up retries on notification task " + execution.getTask().getKey());
253 }
254 }
255 }