View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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         // True if either failed and failures have to be registered, or if ALL
209         // has to be registered.
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 }