1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.serviceregistry.impl;
23
24 import static org.opencastproject.db.Queries.namedQuery;
25 import static org.opencastproject.util.data.Monadics.mlist;
26 import static org.opencastproject.util.data.Option.none;
27 import static org.opencastproject.util.data.Option.option;
28
29 import org.opencastproject.db.DBSession;
30 import org.opencastproject.job.api.Incident;
31 import org.opencastproject.job.api.IncidentImpl;
32 import org.opencastproject.job.api.IncidentTree;
33 import org.opencastproject.job.api.IncidentTreeImpl;
34 import org.opencastproject.job.api.Job;
35 import org.opencastproject.serviceregistry.api.IncidentL10n;
36 import org.opencastproject.serviceregistry.api.IncidentService;
37 import org.opencastproject.serviceregistry.api.IncidentServiceException;
38 import org.opencastproject.serviceregistry.api.ServiceRegistry;
39 import org.opencastproject.serviceregistry.api.ServiceRegistryException;
40 import org.opencastproject.util.NotFoundException;
41 import org.opencastproject.util.data.Collections;
42 import org.opencastproject.util.data.Function;
43 import org.opencastproject.util.data.Option;
44 import org.opencastproject.util.data.Tuple;
45 import org.opencastproject.util.data.functions.Functions;
46 import org.opencastproject.util.data.functions.Strings;
47 import org.opencastproject.workflow.api.WorkflowOperationInstance;
48 import org.opencastproject.workflow.api.WorkflowOperationInstance.OperationState;
49 import org.opencastproject.workflow.api.WorkflowService;
50
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 import java.util.ArrayList;
55 import java.util.Date;
56 import java.util.HashMap;
57 import java.util.LinkedList;
58 import java.util.List;
59 import java.util.Locale;
60 import java.util.Map;
61 import java.util.Optional;
62 import java.util.stream.Collectors;
63
64 public abstract class AbstractIncidentService implements IncidentService {
65 public static final String PERSISTENCE_UNIT_NAME = "org.opencastproject.serviceregistry";
66
67 public static final String NO_TITLE = "-";
68 public static final String NO_DESCRIPTION = "-";
69 public static final String FIELD_TITLE = "title";
70 public static final String FIELD_DESCRIPTION = "description";
71
72
73 private static final Logger logger = LoggerFactory.getLogger(AbstractIncidentService.class);
74
75 protected abstract ServiceRegistry getServiceRegistry();
76
77 protected abstract WorkflowService getWorkflowService();
78
79 protected abstract DBSession getDBSession();
80
81 @Override
82 public Incident storeIncident(Job job, Date timestamp, String code, Incident.Severity severity,
83 Map<String, String> descriptionParameters, List<Tuple<String, String>> details)
84 throws IncidentServiceException, IllegalStateException {
85 try {
86 job = getServiceRegistry().getJob(job.getId());
87
88 final IncidentDto dto = getDBSession().execTx(namedQuery.persist(
89 IncidentDto.mk(job.getId(), timestamp, code, severity, descriptionParameters, details)
90 ));
91 return toIncident(job, dto);
92 } catch (NotFoundException e) {
93 throw new IllegalStateException("Can't create incident for not-existing job");
94 } catch (Exception e) {
95 logger.error("Could not store job incident: {}", e.getMessage());
96 throw new IncidentServiceException(e);
97 }
98 }
99
100 @Override
101 public Incident getIncident(long id) throws IncidentServiceException, NotFoundException {
102 Optional<IncidentDto> dto = getDBSession().execTx(namedQuery.findByIdOpt(IncidentDto.class, id));
103 if (dto.isPresent()) {
104 final Job job = findJob(dto.get().getJobId());
105 if (job != null) {
106 return toIncident(job, dto.get());
107 }
108 }
109 throw new NotFoundException();
110 }
111
112 @Override
113 public List<Incident> getIncidentsOfJob(List<Long> jobIds) throws IncidentServiceException {
114 List<Incident> incidents = new ArrayList<>();
115 for (long jobId : jobIds) {
116 try {
117 incidents.addAll(getIncidentsOfJob(jobId));
118 } catch (NotFoundException ignore) {
119 }
120 }
121 return incidents;
122 }
123
124 @Override
125 public IncidentTree getIncidentsOfJob(long jobId, boolean cascade) throws NotFoundException, IncidentServiceException {
126 List<Incident> incidents = getIncidentsOfJob(jobId);
127 List<IncidentTree> childIncidents = new ArrayList<>();
128
129 try {
130 Job job = getServiceRegistry().getJob(jobId);
131 if (cascade && !"START_WORKFLOW".equals(job.getOperation())) {
132 childIncidents = getChildIncidents(jobId);
133 } else if (cascade && "START_WORKFLOW".equals(job.getOperation())) {
134 for (WorkflowOperationInstance operation : getWorkflowService().getWorkflowById(jobId).getOperations()) {
135 if (operation.getState().equals(OperationState.INSTANTIATED))
136 continue;
137 IncidentTree operationResult = getIncidentsOfJob(operation.getId(), true);
138 if (hasIncidents(Collections.list(operationResult)))
139 childIncidents.add(operationResult);
140 }
141 }
142 return new IncidentTreeImpl(incidents, childIncidents);
143 } catch (NotFoundException ignore) {
144
145 return new IncidentTreeImpl(incidents, childIncidents);
146 } catch (Exception e) {
147 logger.error("Error loading child jobs of: {}", jobId);
148 throw new IncidentServiceException(e);
149 }
150 }
151
152 private boolean hasIncidents(List<IncidentTree> incidentResults) {
153 for (IncidentTree result : incidentResults) {
154 if (result.getIncidents().size() > 0 || hasIncidents(result.getDescendants()))
155 return true;
156 }
157 return false;
158 }
159
160 @Override
161 public IncidentL10n getLocalization(long id, Locale locale) throws IncidentServiceException, NotFoundException {
162 final Incident incident = getIncident(id);
163
164 final List<String> loc = localeToList(locale);
165
166
167 final String title = findText(loc, incident.getCode(), FIELD_TITLE).getOrElse(NO_TITLE);
168 final String description = findText(loc, incident.getCode(), FIELD_DESCRIPTION).map(
169 replaceVarsF(incident.getDescriptionParameters())).getOrElse(NO_DESCRIPTION);
170 return new IncidentL10n() {
171 @Override
172 public String getTitle() {
173 return title;
174 }
175
176 @Override
177 public String getDescription() {
178 return description;
179 }
180 };
181 }
182
183
184
185
186
187
188
189
190
191
192
193
194
195 private Option<String> findText(List<String> locale, String incidentCode, String field) {
196 final List<String> keys = genDbKeys(locale, incidentCode + "." + field);
197 for (String key : keys) {
198 final Option<String> text = getText(key);
199 if (text.isSome()) {
200 return text;
201 }
202 }
203 return none();
204 }
205
206 private final Map<String, String> textCache = new HashMap<>();
207
208
209 private Option<String> getText(String key) {
210 synchronized (textCache) {
211 if (textCache.isEmpty()) {
212 textCache.putAll(fetchTextsFromDb());
213 }
214 }
215 return option(textCache.get(key));
216 }
217
218
219 private Map<String, String> fetchTextsFromDb() {
220 final Map<String, String> locs = new HashMap<String, String>();
221 for (IncidentTextDto a : getDBSession().execTx(IncidentTextDto.findAllQuery)) {
222 locs.put(a.getId(), a.getText());
223 }
224 return locs;
225 }
226
227 private List<IncidentTree> getChildIncidents(long jobId) throws NotFoundException, ServiceRegistryException,
228 IncidentServiceException {
229 List<Job> childJobs = getServiceRegistry().getChildJobs(jobId);
230 List<IncidentTree> incidentResults = new ArrayList<>();
231 for (Job childJob : childJobs) {
232 if (childJob.getParentJobId() != jobId)
233 continue;
234 List<Incident> incidentsForJob = getIncidentsOfJob(childJob.getId());
235 IncidentTree incidentTree = new IncidentTreeImpl(incidentsForJob, getChildIncidents(childJob.getId()));
236 if (hasIncidents(Collections.list(incidentTree)))
237 incidentResults.add(incidentTree);
238 }
239 return incidentResults;
240 }
241
242 private List<Incident> getIncidentsOfJob(long jobId) throws NotFoundException, IncidentServiceException {
243 final Job job = findJob(jobId);
244 try {
245 return getDBSession().execTx(IncidentDto.findByJobIdQuery(jobId)).stream()
246 .map(i -> toIncident(job, i))
247 .collect(Collectors.toList());
248 } catch (Exception e) {
249 logger.error("Could not retrieve incidents of job '{}': {}", job.getId(), e.getMessage());
250 throw new IncidentServiceException(e);
251 }
252 }
253
254 private Job findJob(long jobId) throws NotFoundException, IncidentServiceException {
255 try {
256 return getServiceRegistry().getJob(jobId);
257 } catch (NotFoundException e) {
258 logger.info("Job with Id {} does not exist", jobId);
259 throw e;
260 } catch (ServiceRegistryException e) {
261 logger.error("Could not retrieve job {}: {}", jobId, e.getMessage());
262 throw new IncidentServiceException(e);
263 }
264 }
265
266 private static Incident toIncident(Job job, IncidentDto dto) {
267 return new IncidentImpl(dto.getId(), job.getId(), job.getJobType(), job.getProcessingHost(), dto.getTimestamp(),
268 dto.getSeverity(), dto.getCode(), dto.getTechnicalInformation(), dto.getParameters());
269 }
270
271
272
273
274
275
276
277 public static List<String> genDbKeys(List<String> locale, String base) {
278 List<String> result = new LinkedList<>();
279 StringBuilder key = new StringBuilder(base);
280 result.add(base);
281 for (String s: locale) {
282 key.append('.').append(s);
283 result.add(0, key.toString());
284 }
285 return result;
286 }
287
288
289 public static List<String> localeToList(Locale locale) {
290 return mlist(Strings.trimToNone(locale.getLanguage()), Strings.trimToNone(locale.getCountry()),
291 Strings.trimToNone(locale.getVariant()))
292
293 .bind(Functions.<Option<String>> identity()).value();
294 }
295
296
297 public static String replaceVars(String template, Map<String, String> params) {
298 String s = template;
299 for (Map.Entry<String, String> e : params.entrySet()) {
300 s = s.replace("#{" + e.getKey() + "}", e.getValue());
301 }
302 return s;
303 }
304
305
306
307
308
309 public static Function<String, String> replaceVarsF(final Map<String, String> params) {
310 return new Function<String, String>() {
311 @Override
312 public String apply(String s) {
313 return replaceVars(s, params);
314 }
315 };
316 }
317 }