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.report;
20  
21  import java.io.ByteArrayOutputStream;
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.time.OffsetDateTime;
25  import java.util.Objects;
26  import java.util.Optional;
27  import java.util.zip.Deflater;
28  import java.util.zip.ZipEntry;
29  import java.util.zip.ZipOutputStream;
30  import org.apache.syncope.common.lib.report.ReportConf;
31  import org.apache.syncope.common.lib.types.AuditElements;
32  import org.apache.syncope.core.persistence.api.dao.ReportDAO;
33  import org.apache.syncope.core.persistence.api.entity.EntityFactory;
34  import org.apache.syncope.core.persistence.api.entity.Report;
35  import org.apache.syncope.core.persistence.api.entity.ReportExec;
36  import org.apache.syncope.core.provisioning.api.AuditManager;
37  import org.apache.syncope.core.provisioning.api.data.ReportDataBinder;
38  import org.apache.syncope.core.provisioning.api.event.JobStatusEvent;
39  import org.apache.syncope.core.provisioning.api.job.JobManager;
40  import org.apache.syncope.core.provisioning.api.job.report.ReportJobDelegate;
41  import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
42  import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
43  import org.apache.syncope.core.spring.security.SecurityProperties;
44  import org.quartz.JobExecutionContext;
45  import org.quartz.JobExecutionException;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  import org.springframework.beans.factory.annotation.Autowired;
49  import org.springframework.context.ApplicationEventPublisher;
50  import org.springframework.transaction.annotation.Transactional;
51  
52  public abstract class AbstractReportJobDelegate implements ReportJobDelegate {
53  
54      protected static final Logger LOG = LoggerFactory.getLogger(ReportJobDelegate.class);
55  
56      @Autowired
57      protected SecurityProperties securityProperties;
58  
59      /**
60       * The actual report to be executed.
61       */
62      protected Report report;
63  
64      protected ReportConf conf;
65  
66      /**
67       * Report DAO.
68       */
69      @Autowired
70      protected ReportDAO reportDAO;
71  
72      @Autowired
73      protected EntityFactory entityFactory;
74  
75      @Autowired
76      protected ReportDataBinder reportDataBinder;
77  
78      /**
79       * Notification manager.
80       */
81      @Autowired
82      protected NotificationManager notificationManager;
83  
84      /**
85       * Audit manager.
86       */
87      @Autowired
88      protected AuditManager auditManager;
89  
90      @Autowired
91      protected ApplicationEventPublisher publisher;
92  
93      protected boolean interrupt;
94  
95      protected boolean interrupted;
96  
97      @Override
98      public void setConf(final ReportConf conf) {
99          this.conf = conf;
100     }
101 
102     protected void setStatus(final String status) {
103         Objects.requireNonNull(report, "Report cannot be undefined");
104         publisher.publishEvent(new JobStatusEvent(this, reportDataBinder.buildRefDesc(report), status));
105     }
106 
107     @Override
108     public void interrupt() {
109         interrupt = true;
110     }
111 
112     @Override
113     public boolean isInterrupted() {
114         return interrupted;
115     }
116 
117     @Transactional
118     @Override
119     public void execute(
120             final String reportKey,
121             final boolean dryRun,
122             final JobExecutionContext context) throws JobExecutionException {
123 
124         report = reportDAO.find(reportKey);
125         if (report == null) {
126             throw new JobExecutionException("Report " + reportKey + " not found");
127         }
128 
129         if (!report.isActive()) {
130             LOG.info("Report {} not active, aborting...", reportKey);
131             return;
132         }
133 
134         String executor = Optional.ofNullable(context.getMergedJobDataMap().getString(JobManager.EXECUTOR_KEY)).
135                 orElse(securityProperties.getAdminUser());
136         ReportExec execution = entityFactory.newEntity(ReportExec.class);
137         execution.setStart(OffsetDateTime.now());
138         execution.setReport(report);
139         execution.setExecutor(executor);
140 
141         setStatus("Initialization completed");
142 
143         AuditElements.Result result;
144 
145         ByteArrayOutputStream baos = new ByteArrayOutputStream();
146         ZipOutputStream zos = new ZipOutputStream(baos);
147         zos.setLevel(Deflater.BEST_COMPRESSION);
148         try {
149             // a single ZipEntry in the ZipOutputStream
150             zos.putNextEntry(new ZipEntry(report.getName()));
151         } catch (IOException e) {
152             throw new JobExecutionException("While configuring for output", e, true);
153         }
154 
155         setStatus("Starting");
156         try {
157             execution.setMessage(doExecute(dryRun, zos, executor, context));
158             execution.setStatus(ReportJob.Status.SUCCESS.name());
159 
160             result = AuditElements.Result.SUCCESS;
161         } catch (JobExecutionException e) {
162             LOG.error("While executing report {}", reportKey, e);
163             result = AuditElements.Result.FAILURE;
164 
165             execution.setMessage(ExceptionUtils2.getFullStackTrace(e));
166             execution.setStatus(ReportJob.Status.FAILURE.name());
167         } finally {
168             setStatus(null);
169 
170             try {
171                 zos.closeEntry();
172                 zos.close();
173             } catch (IOException e) {
174                 LOG.error("While closing output", e);
175             }
176         }
177         if (result == AuditElements.Result.SUCCESS) {
178             execution.setExecResult(baos.toByteArray());
179         }
180         execution.setEnd(OffsetDateTime.now());
181 
182         report.add(execution);
183         report = reportDAO.save(report);
184 
185         setStatus(null);
186 
187         notificationManager.createTasks(
188                 executor,
189                 AuditElements.EventCategoryType.REPORT,
190                 this.getClass().getSimpleName(),
191                 null,
192                 this.getClass().getSimpleName(), // searching for before object is too much expensive ...
193                 result,
194                 report,
195                 execution);
196 
197         auditManager.audit(
198                 executor,
199                 AuditElements.EventCategoryType.REPORT,
200                 report.getClass().getSimpleName(),
201                 null,
202                 null, // searching for before object is too much expensive ...
203                 result,
204                 report,
205                 null);
206     }
207 
208     /**
209      * The actual execution, delegated to child classes.
210      *
211      * @param dryRun whether to actually touch the data
212      * @param os where to stream report execution's data
213      * @param executor the user executing this report
214      * @param context Quartz' execution context, can be used to pass parameters to the job
215      * @return the report execution status to be set
216      * @throws JobExecutionException if anything goes wrong
217      */
218     protected abstract String doExecute(
219             boolean dryRun, OutputStream os, String executor, JobExecutionContext context)
220             throws JobExecutionException;
221 }