1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.logic;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.OutputStream;
23 import java.lang.reflect.Method;
24 import java.time.OffsetDateTime;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Optional;
29 import java.util.stream.Collectors;
30 import java.util.zip.ZipInputStream;
31 import javax.ws.rs.core.Response;
32 import org.apache.commons.lang3.ArrayUtils;
33 import org.apache.commons.lang3.StringUtils;
34 import org.apache.commons.lang3.tuple.Pair;
35 import org.apache.commons.lang3.tuple.Triple;
36 import org.apache.syncope.common.lib.SyncopeClientException;
37 import org.apache.syncope.common.lib.to.ExecTO;
38 import org.apache.syncope.common.lib.to.JobTO;
39 import org.apache.syncope.common.lib.to.ReportTO;
40 import org.apache.syncope.common.lib.types.ClientExceptionType;
41 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
42 import org.apache.syncope.common.lib.types.JobAction;
43 import org.apache.syncope.common.lib.types.JobType;
44 import org.apache.syncope.common.rest.api.RESTHeaders;
45 import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
46 import org.apache.syncope.common.rest.api.beans.ExecSpecs;
47 import org.apache.syncope.core.persistence.api.dao.JobStatusDAO;
48 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
49 import org.apache.syncope.core.persistence.api.dao.ReportDAO;
50 import org.apache.syncope.core.persistence.api.dao.ReportExecDAO;
51 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
52 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
53 import org.apache.syncope.core.persistence.api.entity.Report;
54 import org.apache.syncope.core.persistence.api.entity.ReportExec;
55 import org.apache.syncope.core.provisioning.api.data.ReportDataBinder;
56 import org.apache.syncope.core.provisioning.api.job.JobManager;
57 import org.apache.syncope.core.provisioning.api.job.JobNamer;
58 import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
59 import org.apache.syncope.core.provisioning.java.job.report.ReportJob;
60 import org.apache.syncope.core.spring.security.AuthContextUtils;
61 import org.quartz.JobKey;
62 import org.quartz.SchedulerException;
63 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
64 import org.springframework.security.access.prepost.PreAuthorize;
65 import org.springframework.transaction.annotation.Transactional;
66
67 public class ReportLogic extends AbstractExecutableLogic<ReportTO> {
68
69 protected final ReportDAO reportDAO;
70
71 protected final ReportExecDAO reportExecDAO;
72
73 protected final ReportDataBinder binder;
74
75 protected final EntityFactory entityFactory;
76
77 public ReportLogic(
78 final JobManager jobManager,
79 final SchedulerFactoryBean scheduler,
80 final JobStatusDAO jobStatusDAO,
81 final ReportDAO reportDAO,
82 final ReportExecDAO reportExecDAO,
83 final ReportDataBinder binder,
84 final EntityFactory entityFactory) {
85
86 super(jobManager, scheduler, jobStatusDAO);
87
88 this.reportDAO = reportDAO;
89 this.reportExecDAO = reportExecDAO;
90 this.binder = binder;
91 this.entityFactory = entityFactory;
92 }
93
94 @PreAuthorize("hasRole('" + IdRepoEntitlement.REPORT_CREATE + "')")
95 public ReportTO create(final ReportTO reportTO) {
96 Report report = entityFactory.newEntity(Report.class);
97 binder.getReport(report, reportTO);
98 report = reportDAO.save(report);
99 try {
100 jobManager.register(
101 report,
102 null,
103 AuthContextUtils.getUsername());
104 } catch (Exception e) {
105 LOG.error("While registering quartz job for report " + report.getKey(), e);
106
107 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
108 sce.getElements().add(e.getMessage());
109 throw sce;
110 }
111
112 return binder.getReportTO(report);
113 }
114
115 @PreAuthorize("hasRole('" + IdRepoEntitlement.REPORT_UPDATE + "')")
116 public ReportTO update(final ReportTO reportTO) {
117 Report report = Optional.ofNullable(reportDAO.find(reportTO.getKey())).
118 orElseThrow(() -> new NotFoundException("Report " + reportTO.getKey()));
119
120 binder.getReport(report, reportTO);
121 report = reportDAO.save(report);
122 try {
123 jobManager.register(
124 report,
125 null,
126 AuthContextUtils.getUsername());
127 } catch (Exception e) {
128 LOG.error("While registering quartz job for report " + report.getKey(), e);
129
130 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
131 sce.getElements().add(e.getMessage());
132 throw sce;
133 }
134
135 return binder.getReportTO(report);
136 }
137
138 @PreAuthorize("hasRole('" + IdRepoEntitlement.REPORT_LIST + "')")
139 @Transactional(readOnly = true)
140 public List<ReportTO> list() {
141 return reportDAO.findAll().stream().map(binder::getReportTO).collect(Collectors.toList());
142 }
143
144 @PreAuthorize("hasRole('" + IdRepoEntitlement.REPORT_READ + "')")
145 @Transactional(readOnly = true)
146 public ReportTO read(final String key) {
147 Report report = Optional.ofNullable(reportDAO.find(key)).
148 orElseThrow(() -> new NotFoundException("Report " + key));
149 return binder.getReportTO(report);
150 }
151
152 @PreAuthorize("hasRole('" + IdRepoEntitlement.REPORT_EXECUTE + "')")
153 @Override
154 public ExecTO execute(final ExecSpecs specs) {
155 Report report = Optional.ofNullable(reportDAO.find(specs.getKey())).
156 orElseThrow(() -> new NotFoundException("Report " + specs.getKey()));
157
158 if (!report.isActive()) {
159 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
160 sce.getElements().add("Report " + specs.getKey() + " is not active");
161 throw sce;
162 }
163
164 if (specs.getStartAt() != null && specs.getStartAt().isBefore(OffsetDateTime.now())) {
165 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
166 sce.getElements().add("Cannot schedule in the past");
167 throw sce;
168 }
169
170 try {
171 Map<String, Object> jobDataMap = jobManager.register(
172 report,
173 specs.getStartAt(),
174 AuthContextUtils.getUsername());
175 jobDataMap.put(JobManager.DRY_RUN_JOBDETAIL_KEY, specs.getDryRun());
176
177 if (specs.getStartAt() == null) {
178 scheduler.getScheduler().triggerJob(JobNamer.getJobKey(report));
179 }
180 } catch (Exception e) {
181 LOG.error("While executing report {}", report, e);
182
183 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
184 sce.getElements().add(e.getMessage());
185 throw sce;
186 }
187
188 ExecTO result = new ExecTO();
189 result.setJobType(JobType.REPORT);
190 result.setRefKey(report.getKey());
191 result.setRefDesc(binder.buildRefDesc(report));
192 result.setStart(OffsetDateTime.now());
193 result.setStatus("JOB_FIRED");
194 result.setMessage("Job fired; waiting for results...");
195 result.setExecutor(AuthContextUtils.getUsername());
196 return result;
197 }
198
199 @PreAuthorize("hasRole('" + IdRepoEntitlement.REPORT_READ + "')")
200 @Transactional(readOnly = true)
201 public String getFilename(final String executionKey) {
202 ReportExec reportExec = Optional.ofNullable(reportExecDAO.find(executionKey)).
203 orElseThrow(() -> new NotFoundException("Report execution " + executionKey));
204
205 return reportExec.getReport().getName()
206 + "."
207 + StringUtils.removeStart(reportExec.getReport().getFileExt(), ".");
208 }
209
210 @PreAuthorize("hasRole('" + IdRepoEntitlement.REPORT_READ + "')")
211 @Transactional(readOnly = true)
212 public void exportExecutionResult(
213 final OutputStream os,
214 final String executionKey) {
215
216 ReportExec reportExec = Optional.ofNullable(reportExecDAO.find(executionKey)).
217 orElseThrow(() -> new NotFoundException("Report execution " + executionKey));
218
219 if (reportExec.getExecResult() == null || !ReportJob.Status.SUCCESS.name().equals(reportExec.getStatus())) {
220 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidReportExec);
221 sce.getElements().add(reportExec.getExecResult() == null
222 ? "No report data produced"
223 : "Report did not run successfully");
224 throw sce;
225 }
226
227
228 try (ByteArrayInputStream bais = new ByteArrayInputStream(reportExec.getExecResult());
229 ZipInputStream zis = new ZipInputStream(bais)) {
230
231
232 zis.getNextEntry();
233
234 zis.transferTo(os);
235 } catch (Exception e) {
236 LOG.error("While exporting content", e);
237 }
238 }
239
240 @PreAuthorize("hasRole('" + IdRepoEntitlement.REPORT_DELETE + "')")
241 public ReportTO delete(final String key) {
242 Report report = Optional.ofNullable(reportDAO.find(key)).
243 orElseThrow(() -> new NotFoundException("Report " + key));
244
245 ReportTO deletedReport = binder.getReportTO(report);
246 jobManager.unregister(report);
247 reportDAO.delete(report);
248 return deletedReport;
249 }
250
251 @PreAuthorize("hasRole('" + IdRepoEntitlement.REPORT_READ + "')")
252 @Override
253 public Pair<Integer, List<ExecTO>> listExecutions(
254 final String key,
255 final OffsetDateTime before,
256 final OffsetDateTime after,
257 final int page,
258 final int size,
259 final List<OrderByClause> orderByClauses) {
260
261 Report report = Optional.ofNullable(reportDAO.find(key)).
262 orElseThrow(() -> new NotFoundException("Report " + key));
263
264 Integer count = reportExecDAO.count(report, before, after);
265
266 List<ExecTO> result = reportExecDAO.findAll(report, before, after, page, size, orderByClauses).stream().
267 map(reportExec -> binder.getExecTO(reportExec)).collect(Collectors.toList());
268
269 return Pair.of(count, result);
270 }
271
272 @PreAuthorize("hasRole('" + IdRepoEntitlement.REPORT_LIST + "')")
273 @Override
274 public List<ExecTO> listRecentExecutions(final int max) {
275 return reportExecDAO.findRecent(max).stream().
276 map(reportExec -> binder.getExecTO(reportExec)).collect(Collectors.toList());
277 }
278
279 @PreAuthorize("hasRole('" + IdRepoEntitlement.REPORT_DELETE + "')")
280 @Override
281 public ExecTO deleteExecution(final String executionKey) {
282 ReportExec reportExec = Optional.ofNullable(reportExecDAO.find(executionKey)).
283 orElseThrow(() -> new NotFoundException("Report execution " + executionKey));
284
285 ExecTO reportExecToDelete = binder.getExecTO(reportExec);
286 reportExecDAO.delete(reportExec);
287 return reportExecToDelete;
288 }
289
290 @PreAuthorize("hasRole('" + IdRepoEntitlement.REPORT_DELETE + "')")
291 @Override
292 public List<BatchResponseItem> deleteExecutions(
293 final String key,
294 final OffsetDateTime before,
295 final OffsetDateTime after) {
296
297 Report report = Optional.ofNullable(reportDAO.find(key)).
298 orElseThrow(() -> new NotFoundException("Report " + key));
299
300 List<BatchResponseItem> batchResponseItems = new ArrayList<>();
301
302 reportExecDAO.findAll(report, before, after, -1, -1, List.of()).forEach(exec -> {
303 BatchResponseItem item = new BatchResponseItem();
304 item.getHeaders().put(RESTHeaders.RESOURCE_KEY, List.of(exec.getKey()));
305 batchResponseItems.add(item);
306
307 try {
308 reportExecDAO.delete(exec);
309 item.setStatus(Response.Status.OK.getStatusCode());
310 } catch (Exception e) {
311 LOG.error("Error deleting execution {} of report {}", exec.getKey(), key, e);
312 item.setStatus(Response.Status.BAD_REQUEST.getStatusCode());
313 item.setContent(ExceptionUtils2.getFullStackTrace(e));
314 }
315 });
316
317 return batchResponseItems;
318 }
319
320 @Override
321 protected Triple<JobType, String, String> getReference(final JobKey jobKey) {
322 String key = JobNamer.getReportKeyFromJobName(jobKey.getName());
323
324 return Optional.ofNullable(reportDAO.find(key)).
325 map(f -> Triple.of(JobType.REPORT, key, binder.buildRefDesc(f))).orElse(null);
326 }
327
328 @PreAuthorize("hasRole('" + IdRepoEntitlement.REPORT_LIST + "')")
329 @Override
330 public List<JobTO> listJobs() {
331 return super.doListJobs(false);
332 }
333
334 @PreAuthorize("hasRole('" + IdRepoEntitlement.REPORT_READ + "')")
335 @Override
336 public JobTO getJob(final String key) {
337 Report report = Optional.ofNullable(reportDAO.find(key)).
338 orElseThrow(() -> new NotFoundException("Report " + key));
339
340 JobTO jobTO = null;
341 try {
342 jobTO = getJobTO(JobNamer.getJobKey(report), false);
343 } catch (SchedulerException e) {
344 LOG.error("Problems while retrieving scheduled job {}", JobNamer.getJobKey(report), e);
345
346 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
347 sce.getElements().add(e.getMessage());
348 throw sce;
349 }
350 if (jobTO == null) {
351 throw new NotFoundException("Job for report " + key);
352 }
353 return jobTO;
354 }
355
356 @PreAuthorize("hasRole('" + IdRepoEntitlement.REPORT_EXECUTE + "')")
357 @Override
358 public void actionJob(final String key, final JobAction action) {
359 Report report = Optional.ofNullable(reportDAO.find(key)).
360 orElseThrow(() -> new NotFoundException("Report " + key));
361
362 doActionJob(JobNamer.getJobKey(report), action);
363 }
364
365 @Override
366 protected ReportTO resolveReference(final Method method, final Object... args)
367 throws UnresolvedReferenceException {
368
369 String key = null;
370
371 if (ArrayUtils.isNotEmpty(args) && ("create".equals(method.getName())
372 || "update".equals(method.getName())
373 || "delete".equals(method.getName()))) {
374 for (int i = 0; key == null && i < args.length; i++) {
375 if (args[i] instanceof String) {
376 key = (String) args[i];
377 } else if (args[i] instanceof ReportTO) {
378 key = ((ReportTO) args[i]).getKey();
379 }
380 }
381 }
382
383 if (key != null) {
384 try {
385 return binder.getReportTO(reportDAO.find(key));
386 } catch (Throwable ignore) {
387 LOG.debug("Unresolved reference", ignore);
388 throw new UnresolvedReferenceException(ignore);
389 }
390 }
391
392 throw new UnresolvedReferenceException();
393 }
394 }