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