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.functions.Strings.trimToNone;
26
27 import org.opencastproject.db.DBSession;
28 import org.opencastproject.job.api.Incident;
29 import org.opencastproject.job.api.IncidentImpl;
30 import org.opencastproject.job.api.IncidentTree;
31 import org.opencastproject.job.api.IncidentTreeImpl;
32 import org.opencastproject.job.api.Job;
33 import org.opencastproject.serviceregistry.api.IncidentL10n;
34 import org.opencastproject.serviceregistry.api.IncidentService;
35 import org.opencastproject.serviceregistry.api.IncidentServiceException;
36 import org.opencastproject.serviceregistry.api.ServiceRegistry;
37 import org.opencastproject.serviceregistry.api.ServiceRegistryException;
38 import org.opencastproject.util.NotFoundException;
39 import org.opencastproject.util.data.Collections;
40 import org.opencastproject.util.data.Tuple;
41 import org.opencastproject.workflow.api.WorkflowOperationInstance;
42 import org.opencastproject.workflow.api.WorkflowOperationInstance.OperationState;
43 import org.opencastproject.workflow.api.WorkflowService;
44
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 import java.util.ArrayList;
49 import java.util.Date;
50 import java.util.HashMap;
51 import java.util.LinkedList;
52 import java.util.List;
53 import java.util.Locale;
54 import java.util.Map;
55 import java.util.Optional;
56 import java.util.stream.Collectors;
57 import java.util.stream.Stream;
58
59 public abstract class AbstractIncidentService implements IncidentService {
60 public static final String PERSISTENCE_UNIT_NAME = "org.opencastproject.serviceregistry";
61
62 public static final String NO_TITLE = "-";
63 public static final String NO_DESCRIPTION = "-";
64 public static final String FIELD_TITLE = "title";
65 public static final String FIELD_DESCRIPTION = "description";
66
67
68 private static final Logger logger = LoggerFactory.getLogger(AbstractIncidentService.class);
69
70 protected abstract ServiceRegistry getServiceRegistry();
71
72 protected abstract WorkflowService getWorkflowService();
73
74 protected abstract DBSession getDBSession();
75
76 @Override
77 public Incident storeIncident(Job job, Date timestamp, String code, Incident.Severity severity,
78 Map<String, String> descriptionParameters, List<Tuple<String, String>> details)
79 throws IncidentServiceException, IllegalStateException {
80 try {
81 job = getServiceRegistry().getJob(job.getId());
82
83 final IncidentDto dto = getDBSession().execTx(namedQuery.persist(
84 IncidentDto.mk(job.getId(), timestamp, code, severity, descriptionParameters, details)
85 ));
86 return toIncident(job, dto);
87 } catch (NotFoundException e) {
88 throw new IllegalStateException("Can't create incident for not-existing job");
89 } catch (Exception e) {
90 logger.error("Could not store job incident: {}", e.getMessage());
91 throw new IncidentServiceException(e);
92 }
93 }
94
95 @Override
96 public Incident getIncident(long id) throws IncidentServiceException, NotFoundException {
97 Optional<IncidentDto> dto = getDBSession().execTx(namedQuery.findByIdOpt(IncidentDto.class, id));
98 if (dto.isPresent()) {
99 final Job job = findJob(dto.get().getJobId());
100 if (job != null) {
101 return toIncident(job, dto.get());
102 }
103 }
104 throw new NotFoundException();
105 }
106
107 @Override
108 public List<Incident> getIncidentsOfJob(List<Long> jobIds) throws IncidentServiceException {
109 List<Incident> incidents = new ArrayList<>();
110 for (long jobId : jobIds) {
111 try {
112 incidents.addAll(getIncidentsOfJob(jobId));
113 } catch (NotFoundException ignore) {
114 }
115 }
116 return incidents;
117 }
118
119 @Override
120 public IncidentTree getIncidentsOfJob(long jobId, boolean cascade)
121 throws NotFoundException, IncidentServiceException {
122 List<Incident> incidents = getIncidentsOfJob(jobId);
123 List<IncidentTree> childIncidents = new ArrayList<>();
124
125 try {
126 Job job = getServiceRegistry().getJob(jobId);
127 if (cascade && !"START_WORKFLOW".equals(job.getOperation())) {
128 childIncidents = getChildIncidents(jobId);
129 } else if (cascade && "START_WORKFLOW".equals(job.getOperation())) {
130 for (WorkflowOperationInstance operation : getWorkflowService().getWorkflowById(jobId).getOperations()) {
131 if (operation.getState().equals(OperationState.INSTANTIATED)) {
132 continue;
133 }
134 IncidentTree operationResult = getIncidentsOfJob(operation.getId(), true);
135 if (hasIncidents(Collections.list(operationResult))) {
136 childIncidents.add(operationResult);
137 }
138 }
139 }
140 return new IncidentTreeImpl(incidents, childIncidents);
141 } catch (NotFoundException ignore) {
142
143 return new IncidentTreeImpl(incidents, childIncidents);
144 } catch (Exception e) {
145 logger.error("Error loading child jobs of: {}", jobId);
146 throw new IncidentServiceException(e);
147 }
148 }
149
150 private boolean hasIncidents(List<IncidentTree> incidentResults) {
151 for (IncidentTree result : incidentResults) {
152 if (result.getIncidents().size() > 0 || hasIncidents(result.getDescendants())) {
153 return true;
154 }
155 }
156 return false;
157 }
158
159 @Override
160 public IncidentL10n getLocalization(long id, Locale locale) throws IncidentServiceException, NotFoundException {
161 final Incident incident = getIncident(id);
162
163 final List<String> loc = localeToList(locale);
164
165
166 final String title = findText(loc, incident.getCode(), FIELD_TITLE).orElse(NO_TITLE);
167 final String description = findText(loc, incident.getCode(), FIELD_DESCRIPTION)
168 .map(text -> replaceVars(text, incident.getDescriptionParameters()))
169 .orElse(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 Optional<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 Optional<String> text = getText(key);
199 if (text.isPresent()) {
200 return text;
201 }
202 }
203 return Optional.empty();
204 }
205
206 private final Map<String, String> textCache = new HashMap<>();
207
208
209 private Optional<String> getText(String key) {
210 synchronized (textCache) {
211 if (textCache.isEmpty()) {
212 textCache.putAll(fetchTextsFromDb());
213 }
214 }
215 return Optional.ofNullable(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 }
235 List<Incident> incidentsForJob = getIncidentsOfJob(childJob.getId());
236 IncidentTree incidentTree = new IncidentTreeImpl(incidentsForJob, getChildIncidents(childJob.getId()));
237 if (hasIncidents(Collections.list(incidentTree))) {
238 incidentResults.add(incidentTree);
239 }
240 }
241 return incidentResults;
242 }
243
244 private List<Incident> getIncidentsOfJob(long jobId) throws NotFoundException, IncidentServiceException {
245 final Job job = findJob(jobId);
246 try {
247 return getDBSession().execTx(IncidentDto.findByJobIdQuery(jobId)).stream()
248 .map(i -> toIncident(job, i))
249 .collect(Collectors.toList());
250 } catch (Exception e) {
251 logger.error("Could not retrieve incidents of job '{}': {}", job.getId(), e.getMessage());
252 throw new IncidentServiceException(e);
253 }
254 }
255
256 private Job findJob(long jobId) throws NotFoundException, IncidentServiceException {
257 try {
258 return getServiceRegistry().getJob(jobId);
259 } catch (NotFoundException e) {
260 logger.info("Job with Id {} does not exist", jobId);
261 throw e;
262 } catch (ServiceRegistryException e) {
263 logger.error("Could not retrieve job {}: {}", jobId, e.getMessage());
264 throw new IncidentServiceException(e);
265 }
266 }
267
268 private static Incident toIncident(Job job, IncidentDto dto) {
269 return new IncidentImpl(dto.getId(), job.getId(), job.getJobType(), job.getProcessingHost(), dto.getTimestamp(),
270 dto.getSeverity(), dto.getCode(), dto.getTechnicalInformation(), dto.getParameters());
271 }
272
273
274
275
276
277
278
279 public static List<String> genDbKeys(List<String> locale, String base) {
280 List<String> result = new LinkedList<>();
281 StringBuilder key = new StringBuilder(base);
282 result.add(base);
283 for (String s: locale) {
284 key.append('.').append(s);
285 result.add(0, key.toString());
286 }
287 return result;
288 }
289
290
291 public static List<String> localeToList(Locale locale) {
292 return Stream.of(locale.getLanguage(), locale.getCountry(), locale.getVariant())
293 .flatMap(s -> trimToNone(s).stream())
294 .collect(Collectors.toList());
295 }
296
297
298 public static String replaceVars(String template, Map<String, String> params) {
299 String s = template;
300 for (Map.Entry<String, String> e : params.entrySet()) {
301 s = s.replace("#{" + e.getKey() + "}", e.getValue());
302 }
303 return s;
304 }
305 }