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  package org.opencastproject.job.jpa;
22  
23  import static org.opencastproject.job.api.Job.FailureReason.NONE;
24  
25  import org.opencastproject.job.api.Job;
26  import org.opencastproject.job.api.Job.FailureReason;
27  import org.opencastproject.job.api.Job.Status;
28  import org.opencastproject.job.api.JobImpl;
29  import org.opencastproject.security.api.Organization;
30  import org.opencastproject.security.api.User;
31  import org.opencastproject.serviceregistry.impl.jpa.ServiceRegistrationJpaImpl;
32  
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  
36  import java.net.URI;
37  import java.util.Date;
38  import java.util.List;
39  
40  import javax.persistence.Access;
41  import javax.persistence.AccessType;
42  import javax.persistence.Basic;
43  import javax.persistence.CascadeType;
44  import javax.persistence.CollectionTable;
45  import javax.persistence.Column;
46  import javax.persistence.ElementCollection;
47  import javax.persistence.Entity;
48  import javax.persistence.FetchType;
49  import javax.persistence.GeneratedValue;
50  import javax.persistence.Id;
51  import javax.persistence.Index;
52  import javax.persistence.JoinColumn;
53  import javax.persistence.Lob;
54  import javax.persistence.ManyToOne;
55  import javax.persistence.NamedQueries;
56  import javax.persistence.NamedQuery;
57  import javax.persistence.OneToMany;
58  import javax.persistence.OneToOne;
59  import javax.persistence.OrderColumn;
60  import javax.persistence.PostLoad;
61  import javax.persistence.Table;
62  import javax.persistence.Temporal;
63  import javax.persistence.TemporalType;
64  import javax.persistence.Transient;
65  import javax.persistence.Version;
66  
67  /**
68   * A long running, asynchronously executed job.
69   */
70  @Entity(name = "Job")
71  @Access(AccessType.FIELD)
72  @Table(name = "oc_job", indexes = {
73      @Index(name = "IX_oc_job_parent", columnList = ("parent")),
74      @Index(name = "IX_oc_job_root", columnList = ("root")),
75      @Index(name = "IX_oc_job_creator_service", columnList = ("creator_service")),
76      @Index(name = "IX_oc_job_processor_service", columnList = ("processor_service")),
77      @Index(name = "IX_oc_job_status", columnList = ("status")),
78      @Index(name = "IX_oc_job_date_created", columnList = ("date_created")),
79      @Index(name = "IX_oc_job_date_completed", columnList = ("date_completed")),
80      @Index(name = "IX_oc_job_dispatchable", columnList = ("dispatchable")),
81      @Index(name = "IX_oc_job_operation", columnList = ("operation")) })
82  @NamedQueries({
83      @NamedQuery(name = "Job", query = "SELECT j FROM Job j "
84          + "where j.status = :status and j.creatorServiceRegistration.serviceType = :serviceType "
85          + "order by j.dateCreated"),
86      @NamedQuery(name = "Job.type", query = "SELECT j FROM Job j "
87          + "where j.creatorServiceRegistration.serviceType = :serviceType order by j.dateCreated"),
88      @NamedQuery(name = "Job.status", query = "SELECT j FROM Job j "
89          + "where j.status = :status order by j.dateCreated"),
90      @NamedQuery(name = "Job.statuses", query = "SELECT j FROM Job j "
91          + "where j.status in :statuses order by j.dateCreated"),
92      @NamedQuery(name = "Job.countByOrganizationAndHost",
93          query = "SELECT j.organization, j.processorServiceRegistration.hostRegistration.baseUrl, count(j) FROM Job j "
94              + "WHERE j.status in :statuses "
95              + "GROUP BY j.organization, j.processorServiceRegistration.hostRegistration.baseUrl"),
96      @NamedQuery(name = "Job.countTypeByOrganization",
97          query = "SELECT j.organization, count(j) FROM Job j "
98              + "WHERE j.operation = :operation and j.status in :statuses "
99              + "GROUP BY j.organization"),
100     @NamedQuery(name = "Job.all", query = "SELECT j FROM Job j order by j.dateCreated"),
101     @NamedQuery(name = "Job.dispatchable.status", query = "SELECT j FROM Job j where j.dispatchable = true and "
102         + "j.status in :statuses order by j.dateCreated"),
103     @NamedQuery(name = "Job.dispatchable.status.idfilter", query = "SELECT j.id FROM Job j "
104         + "WHERE j.dispatchable = true AND j.status IN :statuses AND j.id IN :jobids ORDER BY j.dateCreated"),
105     @NamedQuery(name = "Job.undispatchable.status", query = "SELECT j FROM Job j where j.dispatchable = false and "
106         + "j.status in :statuses order by j.dateCreated"),
107     @NamedQuery(name = "Job.payload", query = "SELECT j.payload FROM Job j where j.operation = :operation "
108         + "order by j.dateCreated"),
109     @NamedQuery(name = "Job.processinghost.status", query = "SELECT j FROM Job j "
110         + "where j.status in :statuses and j.processorServiceRegistration is not null and "
111         + "j.processorServiceRegistration.serviceType = :serviceType and "
112         + "j.processorServiceRegistration.hostRegistration.baseUrl = :host order by j.dateCreated"),
113     @NamedQuery(name = "Job.root.children", query = "SELECT j FROM Job j "
114         + "WHERE j.rootJob.id = :id ORDER BY j.dateCreated"),
115     @NamedQuery(name = "Job.children", query = "SELECT j FROM Job j "
116         + "WHERE j.parentJob.id = :id ORDER BY j.dateCreated"),
117     @NamedQuery(name = "Job.withoutParent", query = "SELECT j FROM Job j WHERE j.parentJob IS NULL"),
118     @NamedQuery(name = "Job.avgOperation", query = "SELECT j.operation, AVG(j.runTime), AVG(j.queueTime) "
119         + "FROM Job j GROUP BY j.operation"),
120 
121     // Job count queries
122     @NamedQuery(name = "Job.count", query = "SELECT COUNT(j) FROM Job j "
123         + "where j.status = :status and j.creatorServiceRegistration.serviceType = :serviceType"),
124     @NamedQuery(name = "Job.count.all", query = "SELECT COUNT(j) FROM Job j"),
125     @NamedQuery(name = "Job.count.nullType", query = "SELECT COUNT(j) FROM Job j where j.status = :status"),
126     @NamedQuery(name = "Job.count.nullStatus", query = "SELECT COUNT(j) FROM Job j "
127         + "where j.creatorServiceRegistration.serviceType = :serviceType"),
128     @NamedQuery(name = "Job.countByHost", query = "SELECT COUNT(j) FROM Job j "
129         + "where j.status = :status and j.processorServiceRegistration is not null and "
130         + "j.processorServiceRegistration.serviceType = :serviceType and "
131         + "j.creatorServiceRegistration.hostRegistration.baseUrl = :host"),
132     @NamedQuery(name = "Job.countByHost.nullType", query = "SELECT COUNT(j) FROM Job j "
133         + "where j.status = :status and j.processorServiceRegistration is not null and "
134         + "j.creatorServiceRegistration.hostRegistration.baseUrl = :host"),
135     @NamedQuery(name = "Job.countByOperation", query = "SELECT COUNT(j) FROM Job j "
136         + "where j.status = :status and j.operation = :operation and "
137         + "j.creatorServiceRegistration.serviceType = :serviceType"),
138     @NamedQuery(name = "Job.countByOperationOnly", query = "SELECT COUNT(j) FROM Job j "
139         + "where j.operation = :operation"),
140     @NamedQuery(name = "Job.fullMonty", query = "SELECT COUNT(j) FROM Job j "
141         + "where j.status = :status and j.operation = :operation "
142         + "and j.processorServiceRegistration is not null and "
143         + "j.processorServiceRegistration.serviceType = :serviceType and "
144         + "j.creatorServiceRegistration.hostRegistration.baseUrl = :host"),
145     @NamedQuery(name = "Job.count.history.failed", query = "SELECT COUNT(j) FROM Job j "
146         + "WHERE j.status = 4 AND j.processorServiceRegistration IS NOT NULL "
147         + "AND j.processorServiceRegistration.serviceType = :serviceType "
148         + "AND j.processorServiceRegistration.hostRegistration.baseUrl = :host "
149         + "AND j.dateCompleted >= j.processorServiceRegistration.stateChanged"),
150     @NamedQuery(name = "Job.countPerHostService", query = "SELECT h.baseUrl, s.serviceType, j.status, count(j) "
151         + "FROM Job j, ServiceRegistration s, HostRegistration h "
152         + "WHERE ((j.processorServiceRegistration IS NOT NULL AND j.processorServiceRegistration = s) "
153         + "OR (j.creatorServiceRegistration IS NOT NULL AND j.creatorServiceRegistration = s)) "
154         + "AND s.hostRegistration = h GROUP BY h.baseUrl, s.serviceType, j.status")
155 })
156 public class JpaJob {
157 
158   /** The logger */
159   private static final Logger logger = LoggerFactory.getLogger(JpaJob.class);
160 
161   @Id
162   @GeneratedValue
163   @Column(name = "id")
164   private long id;
165 
166   @Lob
167   @Column(name = "creator", nullable = false, length = 65535)
168   private String creator;
169 
170   @Column(name = "organization", nullable = false, length = 128)
171   private String organization;
172 
173   @Version
174   @Column(name = "instance_version")
175   private long version;
176 
177   @Column(name = "status")
178   private int status;
179 
180   @Column(name = "operation", length = 128)
181   private String operation;
182 
183   @Lob
184   @Column(name = "argument", length = 2147483647)
185   @OrderColumn(name = "argument_index")
186   @ElementCollection(fetch = FetchType.EAGER)
187   @CollectionTable(name = "oc_job_argument",
188       joinColumns = @JoinColumn(name = "id", referencedColumnName = "id", nullable = false),
189       indexes = {
190           @Index(name = "IX_oc_job_argument_id", columnList = ("id")),
191       }
192   )
193   private List<String> arguments;
194 
195   @Column(name = "date_completed")
196   @Temporal(TemporalType.TIMESTAMP)
197   private Date dateCompleted;
198 
199   @Column(name = "date_created")
200   @Temporal(TemporalType.TIMESTAMP)
201   private Date dateCreated;
202 
203   public Date getDateStarted() {
204     return dateStarted;
205   }
206 
207   @Column(name = "date_started")
208   @Temporal(TemporalType.TIMESTAMP)
209   private Date dateStarted;
210 
211   @Column(name = "queue_time")
212   private Long queueTime = 0L;
213 
214   @Column(name = "run_time")
215   private Long runTime = 0L;
216 
217   @Lob
218   @Basic(fetch = FetchType.LAZY)
219   @Column(name = "payload", length = 16777215)
220   private String payload;
221 
222   @Column(name = "dispatchable")
223   private boolean dispatchable = true;
224 
225   @Column(name = "job_load", nullable = false)
226   private Float jobLoad = 1F;
227 
228   @ManyToOne
229   @JoinColumn(name = "creator_service")
230   private ServiceRegistrationJpaImpl creatorServiceRegistration;
231 
232   @ManyToOne
233   @JoinColumn(name = "processor_service")
234   private ServiceRegistrationJpaImpl processorServiceRegistration;
235 
236   /**
237    * This readonly field provides the raw value of processor_service database table column
238    * and is defined for performance reason.
239    * Do not use this field in Java!
240    * It should only be used by JPQL named queries.
241    */
242   @Column(name = "processor_service",  updatable = false, insertable = false)
243   private Long processorServiceRegistrationFK;
244 
245   @JoinColumn(name = "parent", referencedColumnName = "id", nullable = true)
246   private JpaJob parentJob = null;
247 
248   @OneToOne(fetch = FetchType.LAZY, targetEntity = JpaJob.class, optional = true)
249   @JoinColumn(name = "root", referencedColumnName = "id", nullable = true)
250   private JpaJob rootJob = null;
251 
252   @OneToMany(mappedBy = "parentJob", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REFRESH,
253           CascadeType.MERGE })
254   private List<JpaJob> childJobs;
255 
256   @Transient
257   private String createdHost;
258 
259   @Transient
260   private String processingHost;
261 
262   @Transient
263   private Long parentJobId = null;
264 
265   @Transient
266   private Long rootJobId = null;
267 
268   @Transient
269   private FailureReason failureReason = NONE;
270 
271   @Transient
272   private String jobType;
273 
274   @Transient
275   private URI uri;
276 
277   public JpaJob() {
278   }
279 
280   public JpaJob(User currentUser, Organization organization, ServiceRegistrationJpaImpl creatingService,
281           String operation, List<String> arguments, String payload, boolean dispatchable, float load) {
282     this.creator = currentUser.getUsername();
283     this.organization = organization.getId();
284     this.creatorServiceRegistration = creatingService;
285     this.jobType = creatingService.getServiceType();
286     this.operation = operation;
287     this.arguments = arguments;
288     this.payload = payload;
289     this.dispatchable = dispatchable;
290     this.jobLoad = load;
291     this.status = Status.INSTANTIATED.ordinal();
292   }
293 
294   public static JpaJob from(Job job) {
295     JpaJob newJob = new JpaJob();
296     newJob.id = job.getId();
297     newJob.dateCompleted = job.getDateCompleted();
298     newJob.dateCreated = job.getDateCreated();
299     newJob.dateStarted = job.getDateStarted();
300     newJob.queueTime = job.getQueueTime();
301     newJob.runTime = job.getRunTime();
302     newJob.version = job.getVersion();
303     newJob.payload = job.getPayload();
304     newJob.jobType = job.getJobType();
305     newJob.operation = job.getOperation();
306     newJob.arguments = job.getArguments();
307     newJob.status = job.getStatus().ordinal();
308     newJob.parentJobId = job.getParentJobId();
309     newJob.rootJobId = job.getRootJobId();
310     newJob.dispatchable = job.isDispatchable();
311     newJob.uri = job.getUri();
312     newJob.creator = job.getCreator();
313     newJob.organization = job.getOrganization();
314     newJob.jobLoad = job.getJobLoad();
315     return newJob;
316   }
317 
318   public Job toJob() {
319     return new JobImpl(id, creator, organization, version, jobType, operation, arguments, Status.values()[status],
320             createdHost, processingHost, dateCreated, dateStarted, dateCompleted, queueTime, runTime, payload,
321             parentJobId, rootJobId, dispatchable, uri, jobLoad);
322   }
323 
324   @PostLoad
325   public void postLoad() {
326     if (creatorServiceRegistration == null) {
327       logger.warn("creator service registration for job '{}' is null", id);
328     } else {
329       this.createdHost = creatorServiceRegistration.getHost();
330       this.jobType = creatorServiceRegistration.getServiceType();
331     }
332 
333     if (processorServiceRegistration == null) {
334       logger.debug("processor service registration for job '{}' is null", id);
335     } else {
336       this.processingHost = processorServiceRegistration.getHost();
337       this.jobType = processorServiceRegistration.getServiceType();
338     }
339 
340     if (rootJob != null) {
341       rootJobId = rootJob.id;
342     }
343     if (parentJob != null) {
344       parentJobId = parentJob.id;
345     }
346   }
347 
348   public void setProcessorServiceRegistration(ServiceRegistrationJpaImpl processorServiceRegistration) {
349     this.processorServiceRegistration = processorServiceRegistration;
350     if (processorServiceRegistration == null) {
351       this.processingHost = null;
352     } else {
353       this.processingHost = processorServiceRegistration.getHost();
354     }
355   }
356 
357   public void setPayload(String payload) {
358     this.payload = payload;
359   }
360 
361   public void setStatus(Status status) {
362     this.status = status.ordinal();
363   }
364 
365   public void setDispatchable(boolean dispatchable) {
366     this.dispatchable = dispatchable;
367   }
368 
369   public void setVersion(long version) {
370     this.version = version;
371   }
372 
373   public void setOperation(String operation) {
374     this.operation = operation;
375   }
376 
377   public void setArguments(List<String> arguments) {
378     this.arguments = arguments;
379   }
380 
381   public void setDateCreated(Date dateCreated) {
382     this.dateCreated = dateCreated;
383   }
384 
385   public void setDateStarted(Date dateStarted) {
386     this.dateStarted = dateStarted;
387   }
388 
389   public void setQueueTime(long queueTime) {
390     this.queueTime = queueTime;
391   }
392 
393   public void setDateCompleted(Date dateCompleted) {
394     this.dateCompleted = dateCompleted;
395   }
396 
397   public void setRunTime(long runTime) {
398     this.runTime = runTime;
399   }
400 
401   public void setParentJob(JpaJob parentJob) {
402     this.parentJob = parentJob;
403     this.parentJobId = parentJob.id;
404   }
405 
406   public void setRootJob(JpaJob rootJob) {
407     this.rootJob = rootJob;
408     this.rootJobId = rootJob.id;
409   }
410 
411   public void setStatus(Status status, FailureReason failureReason) {
412     this.status = status.ordinal();
413     this.failureReason = failureReason;
414   }
415 
416   public void setUri(URI uri) {
417     this.uri = uri;
418   }
419 
420   public long getId() {
421     return id;
422   }
423 
424   public ServiceRegistrationJpaImpl getProcessorServiceRegistration() {
425     return processorServiceRegistration;
426   }
427 
428   public String getJobType() {
429     return jobType;
430   }
431 
432   public String getOperation() {
433     return operation;
434   }
435 
436   public Float getJobLoad() {
437     return jobLoad;
438   }
439 
440   public Status getStatus() {
441     return Status.values()[status];
442   }
443 
444   public boolean isDispatchable() {
445     return dispatchable;
446   }
447 
448   public JpaJob getRootJob() {
449     return rootJob;
450   }
451 
452   public JpaJob getParentJob() {
453     return parentJob;
454   }
455 
456   public List<JpaJob> getChildJobs() {
457     return childJobs;
458   }
459 
460   public String getChildJobsString() {
461     StringBuilder sb = new StringBuilder();
462     for (JpaJob job : getChildJobs()) {
463       sb.append(job.getId());
464       sb.append(" ");
465     }
466     return sb.toString();
467   }
468 
469   public FailureReason getFailureReason() {
470     return failureReason;
471   }
472 
473   public Date getDateCreated() {
474     return dateCreated;
475   }
476 
477   public Date getDateCompleted() {
478     return dateCompleted;
479   }
480 
481   public String getCreator() {
482     return creator;
483   }
484 
485   public String getOrganization() {
486     return organization;
487   }
488 
489   @Override
490   public String toString() {
491     return String.format("Job {id:%d, operation:%s, status:%s}", id, operation, getStatus().toString());
492   }
493 
494 }