View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.serviceregistry.api;
23  
24  import static org.opencastproject.util.data.Monadics.mlist;
25  import static org.opencastproject.util.data.Tuple.tuple;
26  
27  import org.opencastproject.job.api.Incident;
28  import org.opencastproject.job.api.Incident.Severity;
29  import org.opencastproject.job.api.IncidentTree;
30  import org.opencastproject.job.api.Job;
31  import org.opencastproject.util.NotFoundException;
32  import org.opencastproject.util.data.Function;
33  import org.opencastproject.util.data.Tuple;
34  
35  import org.apache.commons.lang3.exception.ExceptionUtils;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  import java.util.Collections;
42  import java.util.Date;
43  import java.util.List;
44  import java.util.Map;
45  
46  /** Create and record job incidents. Facade for {@link IncidentService}. */
47  public final class Incidents {
48  
49    private static final Logger log = LoggerFactory.getLogger(Incident.class);
50  
51    /**
52     * System error codes
53     */
54    private static final String SYSTEM_UNHANDLED_EXCEPTION = "org.opencastproject.system.unhandled-exception";
55    private static final String SYSTEM_JOB_CREATION_EXCEPTION = "org.opencastproject.system.job-creation-exception";
56  
57    public static final Map<String, String> NO_PARAMS = Collections.emptyMap();
58    public static final List<Tuple<String, String>> NO_DETAILS = Collections.emptyList();
59  
60    private final IncidentService is;
61    private final ServiceRegistry sr;
62  
63    public Incidents(ServiceRegistry sr, IncidentService is) {
64      this.is = is;
65      this.sr = sr;
66    }
67  
68    /**
69     * Record an incident for a given job. This method is intended to record client incidents, i.e. incidents crafted by
70     * the programmer.
71     *
72     * @param code
73     *          A code number. This incident factory method enforces an incident code schema of <code>job_type.code</code>
74     *          , e.g. <code>org.opencastproject.service.1511</code> . So instead of aligning
75     *          <code>job.getJobType()</code> and the incident's code prefix manually this is done automatically for you
76     *          by this method. See {@link org.opencastproject.job.api.Incident#getCode()}.
77     * @see org.opencastproject.job.api.Incident
78     */
79    public void record(Job job, Severity severity, int code, Map<String, String> params,
80            List<Tuple<String, String>> details) {
81      try {
82        is.storeIncident(job, new Date(), job.getJobType() + "." + code, severity, params, details);
83      } catch (IncidentServiceException e) {
84        logException(e);
85      }
86    }
87  
88    /**
89     * Record an incident for a given job. This method is intended to record client incidents, i.e. incidents crafted by
90     * the programmer.
91     *
92     * @see #record(org.opencastproject.job.api.Job, org.opencastproject.job.api.Incident.Severity, int, java.util.Map,
93     *      java.util.List)
94     * @see org.opencastproject.job.api.Incident
95     */
96    public void record(Job job, Severity severity, int code) {
97      record(job, severity, code, NO_PARAMS, NO_DETAILS);
98    }
99  
100   /**
101    * Record a failure incident for a given job.
102    *
103    * @see #record(org.opencastproject.job.api.Job, org.opencastproject.job.api.Incident.Severity, int, java.util.Map,
104    *      java.util.List)
105    * @see org.opencastproject.job.api.Incident
106    */
107   public void recordFailure(Job job, int code, Map<String, String> params) {
108     record(job, Severity.FAILURE, code, params, NO_DETAILS);
109   }
110 
111   /**
112    * Record a failure incident for a given job.
113    *
114    * @see #record(org.opencastproject.job.api.Job, org.opencastproject.job.api.Incident.Severity, int, java.util.Map,
115    *      java.util.List)
116    * @see org.opencastproject.job.api.Incident
117    */
118   public void recordFailure(Job job, int code, Throwable t, Map<String, String> params,
119           List<Tuple<String, String>> details) {
120     List<Tuple<String, String>> detailList = new ArrayList<>(details);
121     detailList.add(tuple("stack-trace", ExceptionUtils.getStackTrace(t)));
122     record(job, Severity.FAILURE, code, params, detailList);
123   }
124 
125   public void recordJobCreationIncident(Job job, Throwable t) {
126     unhandledException(job, SYSTEM_JOB_CREATION_EXCEPTION, Severity.FAILURE, t);
127   }
128 
129   /**
130    * Record an incident for a given job caused by an uncatched exception. This method is intended to record incidents by
131    * the job system itself, e.g. the job dispatcher.
132    */
133   public void unhandledException(Job job, Severity severity, Throwable t) {
134     unhandledException(job, SYSTEM_UNHANDLED_EXCEPTION, severity, t);
135   }
136 
137   /**
138    * Record an incident for a given job caused by an uncatched exception. This method is intended to record incidents by
139    * the job system itself, e.g. the job dispatcher. Please note that an incident will <em>only</em> be recorded if none
140    * of severity {@link org.opencastproject.job.api.Incident.Severity#FAILURE} has already been recorded by the job or
141    * one of its child jobs. If no job with the given job id exists nothing happens.
142    */
143   public void unhandledException(long jobId, Severity severity, Throwable t) {
144     try {
145       unhandledException(sr.getJob(jobId), severity, t);
146     } catch (NotFoundException ignore) {
147     } catch (ServiceRegistryException e) {
148       logException(e);
149     }
150   }
151 
152   /**
153    * Record an incident for a given job caused by an uncatched exception. This method is intended to record incidents by
154    * the job system itself, e.g. the job dispatcher.
155    */
156   private void unhandledException(Job job, String code, Severity severity, Throwable t) {
157     if (!alreadyRecordedFailureIncident(job.getId())) {
158       try {
159         is.storeIncident(
160                 job,
161                 new Date(),
162                 code,
163                 severity,
164                 Collections.singletonMap("exception", ExceptionUtils.getMessage(t)),
165                 Arrays.asList(tuple("job-type", job.getJobType()), tuple("job-operation", job.getOperation()),
166                         tuple("stack-trace", ExceptionUtils.getStackTrace(t))));
167       } catch (IncidentServiceException e) {
168         logException(e);
169       }
170     }
171   }
172 
173   private void logException(Throwable t) {
174     log.error("Error recording job incident. Log exception and move on.", t);
175   }
176 
177   public boolean alreadyRecordedFailureIncident(long jobId) {
178     try {
179       return findFailure(is.getIncidentsOfJob(jobId, true));
180     } catch (Exception e) {
181       return false;
182     }
183   }
184 
185   static boolean findFailure(IncidentTree r) {
186     return mlist(r.getIncidents()).exists(isFailure) || mlist(r.getDescendants()).exists(findFailureFn);
187   }
188 
189   static final Function<IncidentTree, Boolean> findFailureFn = new Function<IncidentTree, Boolean>() {
190     @Override
191     public Boolean apply(IncidentTree r) {
192       return findFailure(r);
193     }
194   };
195 
196   static final Function<Incident, Boolean> isFailure = new Function<Incident, Boolean>() {
197     @Override
198     public Boolean apply(Incident i) {
199       return i.getSeverity() == Severity.FAILURE;
200     }
201   };
202 }