/** * Licensed to The Apereo Foundation under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * * The Apereo Foundation licenses this file to you under the Educational * Community License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License * at: * * http://opensource.org/licenses/ecl2.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * */ package org.opencastproject.job.jpa; import static org.opencastproject.job.api.Job.FailureReason.NONE; import org.opencastproject.job.api.Job; import org.opencastproject.job.api.Job.FailureReason; import org.opencastproject.job.api.Job.Status; import org.opencastproject.job.api.JobImpl; import org.opencastproject.security.api.Organization; import org.opencastproject.security.api.User; import org.opencastproject.serviceregistry.impl.jpa.ServiceRegistrationJpaImpl; import com.entwinemedia.fn.Fn; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; import java.util.Date; import java.util.LinkedList; import java.util.List; import javax.persistence.Access; import javax.persistence.AccessType; import javax.persistence.Basic; import javax.persistence.CascadeType; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.Lob; import javax.persistence.ManyToOne; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToMany; import javax.persistence.OneToOne; import javax.persistence.OrderColumn; import javax.persistence.PostLoad; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.Transient; import javax.persistence.Version; /** * A long running, asynchronously executed job. */ @Entity(name = "Job") @Access(AccessType.FIELD) @Table(name = "mh_job") @NamedQueries({ @NamedQuery(name = "Job", query = "SELECT j FROM Job j " + "where j.status = :status and j.creatorServiceRegistration.serviceType = :serviceType " + "order by j.dateCreated"), @NamedQuery(name = "Job.type", query = "SELECT j FROM Job j " + "where j.creatorServiceRegistration.serviceType = :serviceType order by j.dateCreated"), @NamedQuery(name = "Job.status", query = "SELECT j FROM Job j " + "where j.status = :status order by j.dateCreated"), @NamedQuery(name = "Job.statuses", query = "SELECT j FROM Job j " + "where j.status in :statuses order by j.dateCreated"), @NamedQuery(name = "Job.all", query = "SELECT j FROM Job j order by j.dateCreated"), @NamedQuery(name = "Job.dispatchable.status", query = "SELECT j FROM Job j where j.dispatchable = true and " + "j.status in :statuses order by j.dateCreated"), @NamedQuery(name = "Job.dispatchable.status.idfilter", query = "SELECT j.id FROM Job j " + "WHERE j.dispatchable = true AND j.status IN :statuses AND j.id IN :jobids ORDER BY j.dateCreated"), @NamedQuery(name = "Job.undispatchable.status", query = "SELECT j FROM Job j where j.dispatchable = false and " + "j.status in :statuses order by j.dateCreated"), @NamedQuery(name = "Job.processinghost.status", query = "SELECT j FROM Job j " + "where j.status in :statuses and j.processorServiceRegistration is not null and " + "j.processorServiceRegistration.serviceType = :serviceType and " + "j.processorServiceRegistration.hostRegistration.baseUrl = :host order by j.dateCreated"), @NamedQuery(name = "Job.root.children", query = "SELECT j FROM Job j WHERE j.rootJob.id = :id ORDER BY j.dateCreated"), @NamedQuery(name = "Job.children", query = "SELECT j FROM Job j WHERE j.parentJob.id = :id ORDER BY j.dateCreated"), @NamedQuery(name = "Job.withoutParent", query = "SELECT j FROM Job j WHERE j.parentJob IS NULL"), @NamedQuery(name = "Job.avgOperation", query = "SELECT j.operation, AVG(j.runTime), AVG(j.queueTime) FROM Job j GROUP BY j.operation"), // Job count queries @NamedQuery(name = "Job.count", query = "SELECT COUNT(j) FROM Job j " + "where j.status = :status and j.creatorServiceRegistration.serviceType = :serviceType"), @NamedQuery(name = "Job.count.all", query = "SELECT COUNT(j) FROM Job j"), @NamedQuery(name = "Job.count.nullType", query = "SELECT COUNT(j) FROM Job j " + "where j.status = :status"), @NamedQuery(name = "Job.count.nullStatus", query = "SELECT COUNT(j) FROM Job j " + "where j.creatorServiceRegistration.serviceType = :serviceType"), @NamedQuery(name = "Job.countByHost", query = "SELECT COUNT(j) FROM Job j " + "where j.status = :status and j.processorServiceRegistration is not null and " + "j.processorServiceRegistration.serviceType = :serviceType and " + "j.creatorServiceRegistration.hostRegistration.baseUrl = :host"), @NamedQuery(name = "Job.countByOperation", query = "SELECT COUNT(j) FROM Job j " + "where j.status = :status and j.operation = :operation and " + "j.creatorServiceRegistration.serviceType = :serviceType"), @NamedQuery(name = "Job.fullMonty", query = "SELECT COUNT(j) FROM Job j " + "where j.status = :status and j.operation = :operation " + "and j.processorServiceRegistration is not null and " + "j.processorServiceRegistration.serviceType = :serviceType and " + "j.creatorServiceRegistration.hostRegistration.baseUrl = :host"), @NamedQuery(name = "Job.count.history.failed", query = "SELECT COUNT(j) FROM Job j " + "WHERE j.status = 4 AND j.processorServiceRegistration IS NOT NULL " + "AND j.processorServiceRegistration.serviceType = :serviceType AND j.processorServiceRegistration.hostRegistration.baseUrl = :host " + "AND j.dateCompleted >= j.processorServiceRegistration.stateChanged"), @NamedQuery(name = "Job.countPerHostService", query = "SELECT h.baseUrl, s.serviceType, j.status, count(j) " + "FROM Job j, ServiceRegistration s, HostRegistration h " + "WHERE ((j.processorServiceRegistration IS NOT NULL AND j.processorServiceRegistration = s) " + "OR (j.creatorServiceRegistration IS NOT NULL AND j.creatorServiceRegistration = s)) " + "AND s.hostRegistration = h GROUP BY h.baseUrl, s.serviceType, j.status") }) public class JpaJob { /** The logger */ private static final Logger logger = LoggerFactory.getLogger(JpaJob.class); @Id @GeneratedValue @Column(name = "id") private long id; @Lob @Column(name = "creator", nullable = false, length = 65535) private String creator; @Lob @Column(name = "organization", nullable = false, length = 128) private String organization; @Version @Column(name = "instance_version") private long version; @Column(name = "status") private int status; @Lob @Column(name = "operation", length = 65535) private String operation; @Lob @Column(name = "argument", length = 2147483647) @OrderColumn(name = "argument_index") @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "mh_job_argument", joinColumns = @JoinColumn(name = "id", referencedColumnName = "id")) private List<String> arguments; @Column(name = "date_completed") @Temporal(TemporalType.TIMESTAMP) private Date dateCompleted; @Column(name = "date_created") @Temporal(TemporalType.TIMESTAMP) private Date dateCreated; @Column(name = "date_started") @Temporal(TemporalType.TIMESTAMP) private Date dateStarted; @Column(name = "queue_time") private Long queueTime = 0L; @Column(name = "run_time") private Long runTime = 0L; @Lob @Basic(fetch = FetchType.LAZY) @Column(name = "payload", length = 16777215) private String payload; @Column(name = "dispatchable") private boolean dispatchable; @Column(name = "job_load") private Float jobLoad; /** The list of job IDs that are blocking this job from continuing. */ @Column(name = "blocking_job_list") @OrderColumn(name = "job_index") @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "mh_blocking_job", joinColumns = @JoinColumn(name = "id", referencedColumnName = "id")) private List<Long> blockedJobIds = new LinkedList<Long>(); /** The job that this job is blocking from continuing. */ @Column(name = "blocking_job") private Long blockingJobId = null; @ManyToOne @JoinColumn(name = "creator_service") private ServiceRegistrationJpaImpl creatorServiceRegistration; @ManyToOne @JoinColumn(name = "processor_service") private ServiceRegistrationJpaImpl processorServiceRegistration; @JoinColumn(name = "parent", referencedColumnName = "id", nullable = true) private JpaJob parentJob = null; @OneToOne(fetch = FetchType.LAZY, targetEntity = JpaJob.class, optional = true) @JoinColumn(name = "root", referencedColumnName = "id", nullable = true) private JpaJob rootJob = null; @OneToMany(mappedBy = "parentJob", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE }) private List<JpaJob> childJobs; @Transient private String createdHost; @Transient private String processingHost; @Transient private Long parentJobId = null; @Transient private Long rootJobId = null; @Transient private FailureReason failureReason = NONE; @Transient private String jobType; @Transient private URI uri; public JpaJob() { } public JpaJob(User currentUser, Organization organization, ServiceRegistrationJpaImpl creatingService, String operation, List<String> arguments, String payload, boolean dispatchable, float load) { this.creator = currentUser.getUsername(); this.organization = organization.getId(); this.creatorServiceRegistration = creatingService; this.operation = operation; this.arguments = arguments; this.payload = payload; this.dispatchable = dispatchable; this.jobLoad = load; this.status = Status.INSTANTIATED.ordinal(); } public static JpaJob from(Job job) { JpaJob newJob = new JpaJob(); newJob.id = job.getId(); newJob.dateCompleted = job.getDateCompleted(); newJob.dateCreated = job.getDateCreated(); newJob.dateStarted = job.getDateStarted(); newJob.queueTime = job.getQueueTime(); newJob.runTime = job.getRunTime(); newJob.version = job.getVersion(); newJob.payload = job.getPayload(); newJob.jobType = job.getJobType(); newJob.operation = job.getOperation(); newJob.arguments = job.getArguments(); newJob.status = job.getStatus().ordinal(); newJob.parentJobId = job.getParentJobId(); newJob.rootJobId = job.getRootJobId(); newJob.dispatchable = job.isDispatchable(); newJob.uri = job.getUri(); newJob.creator = job.getCreator(); newJob.organization = job.getOrganization(); return newJob; } public Job toJob() { return new JobImpl(id, creator, organization, version, jobType, operation, arguments, Status.values()[status], createdHost, processingHost, dateCreated, dateStarted, dateCompleted, queueTime, runTime, payload, parentJobId, rootJobId, dispatchable, uri, jobLoad, blockedJobIds, blockingJobId); } public static Fn<JpaJob, Job> fnToJob() { return new Fn<JpaJob, Job>() { @Override public Job apply(JpaJob jobJpa) { return jobJpa.toJob(); } }; } @PostLoad public void postLoad() { if (creatorServiceRegistration == null) { logger.warn("creator service registration for job '{}' is null", id); } else { this.createdHost = creatorServiceRegistration.getHost(); this.jobType = creatorServiceRegistration.getServiceType(); } if (processorServiceRegistration == null) { logger.debug("processor service registration for job '{}' is null", id); } else { this.processingHost = processorServiceRegistration.getHost(); this.jobType = processorServiceRegistration.getServiceType(); } if (rootJob != null) rootJobId = rootJob.id; if (parentJob != null) parentJobId = parentJob.id; } public void setProcessorServiceRegistration(ServiceRegistrationJpaImpl processorServiceRegistration) { this.processorServiceRegistration = processorServiceRegistration; if (processorServiceRegistration == null) { this.processingHost = null; } else { this.processingHost = processorServiceRegistration.getHost(); } } public void setPayload(String payload) { this.payload = payload; } public void setStatus(Status status) { this.status = status.ordinal(); } public void setDispatchable(boolean dispatchable) { this.dispatchable = dispatchable; } public void setVersion(long version) { this.version = version; } public void setOperation(String operation) { this.operation = operation; } public void setArguments(List<String> arguments) { this.arguments = arguments; } public void setBlockedJobIds(List<Long> blockedJobIds) { this.blockedJobIds = blockedJobIds; } public void setBlockingJobId(Long blockingJobId) { this.blockingJobId = blockingJobId; } public void setDateCreated(Date dateCreated) { this.dateCreated = dateCreated; } public void setDateStarted(Date dateStarted) { this.dateStarted = dateStarted; } public void setQueueTime(long queueTime) { this.queueTime = queueTime; } public void setDateCompleted(Date dateCompleted) { this.dateCompleted = dateCompleted; } public void setRunTime(long runTime) { this.runTime = runTime; } public void setParentJob(JpaJob parentJob) { this.parentJob = parentJob; this.parentJobId = parentJob.id; } public void setRootJob(JpaJob rootJob) { this.rootJob = rootJob; this.rootJobId = rootJob.id; } public void setStatus(Status status, FailureReason failureReason) { this.status = status.ordinal(); this.failureReason = failureReason; } public void setUri(URI uri) { this.uri = uri; } public long getId() { return id; } public ServiceRegistrationJpaImpl getProcessorServiceRegistration() { return processorServiceRegistration; } public String getJobType() { return jobType; } public String getOperation() { return operation; } public Float getJobLoad() { return jobLoad; } public Status getStatus() { return Status.values()[status]; } public boolean isDispatchable() { return dispatchable; } public JpaJob getRootJob() { return rootJob; } public JpaJob getParentJob() { return parentJob; } public List<JpaJob> getChildJobs() { return childJobs; } public FailureReason getFailureReason() { return failureReason; } public Date getDateCreated() { return dateCreated; } public String getCreator() { return creator; } public String getOrganization() { return organization; } @Override public String toString() { return String.format("Job {id:%d, operation:%s, status:%s}", id, operation, getStatus().toString()); } }