/* * Copyright 2007 Glencoe Software, Inc. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.services; import java.sql.Timestamp; import ome.annotations.RolesAllowed; import ome.api.ITypes; import ome.api.JobHandle; import ome.api.ServiceInterface; import ome.conditions.ApiUsageException; import ome.conditions.ValidationException; import ome.model.IObject; import ome.model.internal.Details; import ome.model.jobs.Job; import ome.model.jobs.JobStatus; import ome.parameters.Parameters; import ome.security.SecureAction; import ome.services.procs.IProcessManager; import ome.services.procs.Process; import ome.services.procs.ProcessCallback; import ome.system.EventContext; import ome.util.ShallowCopy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.annotation.Transactional; /** * Provides methods for submitting asynchronous tasks. * * @author Josh Moore, josh at glencoesoftware.com * @since 3.0-Beta2 * */ @Transactional(readOnly = true) public class JobBean extends AbstractStatefulBean implements JobHandle, ProcessCallback { /** * */ private static final long serialVersionUID = 49809384038000069L; /** The logger for this class. */ private transient static Logger log = LoggerFactory.getLogger(JobBean.class); private Long jobId, resetId; private transient ITypes iTypes; private transient IProcessManager pm; /** default constructor */ public JobBean() { } public Class<? extends ServiceInterface> getServiceInterface() { return JobHandle.class; } // Lifecycle methods // =================================================== /** * Does nothing. The only non-shared state that this instance * holds on to is the jobId and resetId -- two longs -- making * passivation for the moment unimportant. This method should do * what errorIfInvalidState does and reattach the process if we've * been passivated. That will wait for larger changes later. At * which time, proper locking will be necessary! */ @RolesAllowed("user") @Transactional public void passivate() { // Nothing necessary } /** * Does almost nothing. Since nothing is passivated, nothing needs * to be activated. However, since we are still using * errorIfInvalidState, if the {@link #jobId} is non-null, then * this instance will need to handle re-loading on first * access. (Previously it could not be done here, because the * security system was not configured for transactions during * JavaEE callbacks. This is no longer true.) */ @RolesAllowed("user") @Transactional public void activate() { if (jobId != null) { resetId = jobId; jobId = null; } } /* * (non-Javadoc) * * @see ome.api.StatefulServiceInterface#close() */ @RolesAllowed("user") @Transactional(readOnly = true) public void close() { // id is the only thing passivated. // FIXME do we need to check on the process here? // or callbacks? probably. } /* * (non-Javadoc) * * @see ome.api.JobHandle#submit(Job) */ @Transactional(readOnly = false) @RolesAllowed("user") public long submit(Job newJob) { reset(); // TODO or do we want to just checkState // and throw an exception if this is a stale handle. EventContext ec = getCurrentEventContext(); long ms = System.currentTimeMillis(); Timestamp now = new Timestamp(ms); // Values that can't be set by the user newJob.setUsername(ec.getCurrentUserName()); newJob.setGroupname(ec.getCurrentGroupName()); newJob.setType(ec.getCurrentEventType()); newJob.setStarted(null); newJob.setFinished(null); newJob.setSubmitted(now); // Values that the user can optionally set Timestamp t = newJob.getScheduledFor(); if (t == null || t.getTime() < now.getTime()) { newJob.setScheduledFor(now); } JobStatus s = newJob.getStatus(); if (s == null) { newJob.setStatus(new JobStatus(JobHandle.SUBMITTED)); } else { // Verifying the status if (s.getId() != null) { try { s = iQuery.get(JobStatus.class, s.getId()); } catch (Exception e) { throw new ApiUsageException("Unknown job status: " + s); } } if (s.getValue() == null) { throw new ApiUsageException( "JobStatus must have id or value set."); } else { if (!(s.getValue().equals(SUBMITTED) || s.getValue().equals( WAITING))) { throw new ApiUsageException( "Currently only SUBMITTED and WAITING are accepted as JobStatus"); } } } String m = newJob.getMessage(); if (m == null) { newJob.setMessage(""); } // Here it is necessary to perform a {@link SecureAction} since // SecuritySystem#isSystemType() returns true for all Jobs newJob.getDetails().copy(sec.newTransientDetails(newJob)); newJob = secureSave(newJob); jobId = newJob.getId(); return jobId; } /* * (non-Javadoc) * * @see ome.api.JobHandle#attach(long) */ @RolesAllowed("user") public JobStatus attach(long id) { if (jobId == null || jobId.longValue() != id) { reset(); jobId = Long.valueOf(id); } checkAndRegister(); return getJob().getStatus(); } private void reset() { resetId = null; jobId = null; } /** * Types service Bean injector. * * @param typesService * an <code>ITypes</code>. */ public void setTypesService(ITypes typesService) { getBeanHelper().throwIfAlreadySet(this.iTypes, typesService); this.iTypes = typesService; } /** * Process Manager Bean injector. * * @param procMgr * a <code>ProcessManager</code>. */ public void setProcessManager(IProcessManager procMgr) { getBeanHelper().throwIfAlreadySet(this.pm, procMgr); this.pm = procMgr; } // Usage methods // =================================================== protected void errorIfInvalidState() { if (resetId != null) { long reset = resetId.longValue(); attach(reset); } else if (jobId == null) { throw new ApiUsageException( "JobHandle not ready: Please submit() or attach() to a Job."); } } protected void checkAndRegister() { Process p = pm.runningProcess(jobId); if (p != null && p.isActive()) { p.registerCallback(this); } } @RolesAllowed("user") public Job getJob() { Job job = internalJobOnly(); JobStatus status = job.getStatus(); Details unloadedDetails = status.getDetails().shallowCopy(); status.getDetails().shallowCopy(unloadedDetails); Job copy = new ShallowCopy().copy(job); copy.setStatus(job.getStatus()); return copy; } @RolesAllowed("user") public Timestamp jobFinished() { return getJob().getFinished(); } @RolesAllowed("user") public JobStatus jobStatus() { return getJob().getStatus(); } @RolesAllowed("user") public String jobMessage() { return getJob().getMessage(); } @RolesAllowed("user") public boolean jobRunning() { return JobHandle.RUNNING.equals(getJob().getStatus().getValue()); } @RolesAllowed("user") public boolean jobError() { return JobHandle.ERROR.equals(getJob().getStatus().getValue()); } @Transactional(readOnly = false) @RolesAllowed("user") public void cancelJob() { setStatus(JobHandle.CANCELLED); } @Transactional(readOnly = false) @RolesAllowed("user") public String setStatus(String status) { return setStatusAndMessage(status, null); } @Transactional(readOnly = false) @RolesAllowed("user") public String setMessage(String message) { return setStatusAndMessage(null, message); } @Transactional(readOnly = false) @RolesAllowed("user") public String setStatusAndMessage(String status, String message) { // status can only be null if this is invoked locally, otherwise // the @NotNull will prevent that. therefore the return value // will only be the message when called by setMessage() String rv = null; errorIfInvalidState(); Job job = internalJobOnly(); if (message != null) { rv = job.getMessage(); job.setMessage(message); } if (status != null) { JobStatus s = iTypes.getEnumeration(JobStatus.class, status); rv = job.getStatus().getValue(); job.setStatus(s); Timestamp t = new Timestamp(System.currentTimeMillis()); if (status.equals(RUNNING)) { job.setStarted(t); } else if (status.equals(CANCELLED)) { job.setFinished(t); Process p = pm.runningProcess(jobId); if (p != null) { p.cancel(); } } else if (status.equals(FINISHED) || status.equals(ERROR)) { job.setFinished(t); } else { throw new ValidationException("Currently unsupported: " + status); } } secureSave(job); return rv; } // ProcessCallback ~ // ========================================================================= public void processCancelled(Process proc) { throw new UnsupportedOperationException("NYI"); } public void processFinished(Process proc) { throw new UnsupportedOperationException("NYI"); } // Helpers ~ // ========================================================================= private Job internalJobOnly() { errorIfInvalidState(); checkAndRegister(); Job job = iQuery.findByQuery("select j from Job j " + "left outer join fetch j.status status " + "left outer join fetch j.originalFileLinks links " + "left outer join fetch links.child file " + "left outer join fetch j.details.owner owner " + "left outer join fetch owner.groupExperimenterMap map " + "left outer join fetch map.parent where j.id = :id", new Parameters().addId(jobId)); if (job == null) { throw new ApiUsageException("Unknown job:" + jobId); } return job; } private Job secureSave(Job job) { job = sec.doAction(new SecureAction() { public <T extends IObject> T updateObject(T... objs) { T result = iUpdate.saveAndReturnObject(objs[0]); iUpdate.flush(); // was commit return result; } }, job); return job; } }