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) throws NotFoundException, IncidentServiceException {
121 List<Incident> incidents = getIncidentsOfJob(jobId);
122 List<IncidentTree> childIncidents = new ArrayList<>();
123
124 try {
125 Job job = getServiceRegistry().getJob(jobId);
126 if (cascade && !"START_WORKFLOW".equals(job.getOperation())) {
127 childIncidents = getChildIncidents(jobId);
128 } else if (cascade && "START_WORKFLOW".equals(job.getOperation())) {
129 for (WorkflowOperationInstance operation : getWorkflowService().getWorkflowById(jobId).getOperations()) {
130 if (operation.getState().equals(OperationState.INSTANTIATED))
131 continue;
132 IncidentTree operationResult = getIncidentsOfJob(operation.getId(), true);
133 if (hasIncidents(Collections.list(operationResult)))
134 childIncidents.add(operationResult);
135 }
136 }
137 return new IncidentTreeImpl(incidents, childIncidents);
138 } catch (NotFoundException ignore) {
139
140 return new IncidentTreeImpl(incidents, childIncidents);
141 } catch (Exception e) {
142 logger.error("Error loading child jobs of: {}", jobId);
143 throw new IncidentServiceException(e);
144 }
145 }
146
147 private boolean hasIncidents(List<IncidentTree> incidentResults) {
148 for (IncidentTree result : incidentResults) {
149 if (result.getIncidents().size() > 0 || hasIncidents(result.getDescendants()))
150 return true;
151 }
152 return false;
153 }
154
155 @Override
156 public IncidentL10n getLocalization(long id, Locale locale) throws IncidentServiceException, NotFoundException {
157 final Incident incident = getIncident(id);
158
159 final List<String> loc = localeToList(locale);
160
161
162 final String title = findText(loc, incident.getCode(), FIELD_TITLE).orElse(NO_TITLE);
163 final String description = findText(loc, incident.getCode(), FIELD_DESCRIPTION)
164 .map(text -> replaceVars(text, incident.getDescriptionParameters()))
165 .orElse(NO_DESCRIPTION);
166 return new IncidentL10n() {
167 @Override
168 public String getTitle() {
169 return title;
170 }
171
172 @Override
173 public String getDescription() {
174 return description;
175 }
176 };
177 }
178
179
180
181
182
183
184
185
186
187
188
189
190
191 private Optional<String> findText(List<String> locale, String incidentCode, String field) {
192 final List<String> keys = genDbKeys(locale, incidentCode + "." + field);
193 for (String key : keys) {
194 final Optional<String> text = getText(key);
195 if (text.isPresent()) {
196 return text;
197 }
198 }
199 return Optional.empty();
200 }
201
202 private final Map<String, String> textCache = new HashMap<>();
203
204
205 private Optional<String> getText(String key) {
206 synchronized (textCache) {
207 if (textCache.isEmpty()) {
208 textCache.putAll(fetchTextsFromDb());
209 }
210 }
211 return Optional.ofNullable(textCache.get(key));
212 }
213
214
215 private Map<String, String> fetchTextsFromDb() {
216 final Map<String, String> locs = new HashMap<String, String>();
217 for (IncidentTextDto a : getDBSession().execTx(IncidentTextDto.findAllQuery)) {
218 locs.put(a.getId(), a.getText());
219 }
220 return locs;
221 }
222
223 private List<IncidentTree> getChildIncidents(long jobId) throws NotFoundException, ServiceRegistryException,
224 IncidentServiceException {
225 List<Job> childJobs = getServiceRegistry().getChildJobs(jobId);
226 List<IncidentTree> incidentResults = new ArrayList<>();
227 for (Job childJob : childJobs) {
228 if (childJob.getParentJobId() != jobId)
229 continue;
230 List<Incident> incidentsForJob = getIncidentsOfJob(childJob.getId());
231 IncidentTree incidentTree = new IncidentTreeImpl(incidentsForJob, getChildIncidents(childJob.getId()));
232 if (hasIncidents(Collections.list(incidentTree)))
233 incidentResults.add(incidentTree);
234 }
235 return incidentResults;
236 }
237
238 private List<Incident> getIncidentsOfJob(long jobId) throws NotFoundException, IncidentServiceException {
239 final Job job = findJob(jobId);
240 try {
241 return getDBSession().execTx(IncidentDto.findByJobIdQuery(jobId)).stream()
242 .map(i -> toIncident(job, i))
243 .collect(Collectors.toList());
244 } catch (Exception e) {
245 logger.error("Could not retrieve incidents of job '{}': {}", job.getId(), e.getMessage());
246 throw new IncidentServiceException(e);
247 }
248 }
249
250 private Job findJob(long jobId) throws NotFoundException, IncidentServiceException {
251 try {
252 return getServiceRegistry().getJob(jobId);
253 } catch (NotFoundException e) {
254 logger.info("Job with Id {} does not exist", jobId);
255 throw e;
256 } catch (ServiceRegistryException e) {
257 logger.error("Could not retrieve job {}: {}", jobId, e.getMessage());
258 throw new IncidentServiceException(e);
259 }
260 }
261
262 private static Incident toIncident(Job job, IncidentDto dto) {
263 return new IncidentImpl(dto.getId(), job.getId(), job.getJobType(), job.getProcessingHost(), dto.getTimestamp(),
264 dto.getSeverity(), dto.getCode(), dto.getTechnicalInformation(), dto.getParameters());
265 }
266
267
268
269
270
271
272
273 public static List<String> genDbKeys(List<String> locale, String base) {
274 List<String> result = new LinkedList<>();
275 StringBuilder key = new StringBuilder(base);
276 result.add(base);
277 for (String s: locale) {
278 key.append('.').append(s);
279 result.add(0, key.toString());
280 }
281 return result;
282 }
283
284
285 public static List<String> localeToList(Locale locale) {
286 return Stream.of(locale.getLanguage(), locale.getCountry(), locale.getVariant())
287 .flatMap(s -> trimToNone(s).stream())
288 .collect(Collectors.toList());
289 }
290
291
292 public static String replaceVars(String template, Map<String, String> params) {
293 String s = template;
294 for (Map.Entry<String, String> e : params.entrySet()) {
295 s = s.replace("#{" + e.getKey() + "}", e.getValue());
296 }
297 return s;
298 }
299 }