/* * JAME 6.2.1 * http://jame.sourceforge.net * * Copyright 2001, 2016 Andrea Medeghini * * This file is part of JAME. * * JAME is an application for creating fractals and other graphics artifacts. * * JAME is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * JAME is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with JAME. If not, see <http://www.gnu.org/licenses/>. * */ package net.sf.jame.queue.network.spool; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.ThreadFactory; import java.util.logging.Level; import java.util.logging.Logger; import net.sf.jame.core.util.DefaultThreadFactory; import net.sf.jame.core.util.Worker; import net.sf.jame.queue.io.ChunkedRandomAccessFile; import net.sf.jame.queue.network.DiscoveryService; import net.sf.jame.queue.network.EventMessage; import net.sf.jame.queue.network.RequestIDFactory; import net.sf.jame.queue.network.RequestMessage; import net.sf.jame.queue.network.ResponseMessage; import net.sf.jame.queue.network.ServiceEndpoint; import net.sf.jame.queue.network.ServiceException; import net.sf.jame.queue.network.ServiceListener; import net.sf.jame.queue.network.ServiceMessage; import net.sf.jame.queue.network.ServiceSession; import net.sf.jame.queue.network.SessionHandler; import net.sf.jame.queue.spool.DistributedSpoolJobInterface; import net.sf.jame.queue.spool.JobData; import net.sf.jame.queue.spool.JobEvent; import net.sf.jame.queue.spool.JobFactory; import net.sf.jame.queue.spool.JobIDFactory; import net.sf.jame.queue.spool.JobListener; import net.sf.jame.queue.spool.JobService; import net.sf.jame.queue.spool.JobServiceListener; import net.sf.jame.queue.spool.JobStatus; import net.sf.jame.queue.spool.SpoolJobInterface; import net.sf.jame.queue.spool.job.DistributedJobEncoder; /** * @author Andrea Medeghini */ public abstract class AbstractSpoolJobService implements JobService<SpoolJobInterface> { private static final Logger logger = Logger.getLogger(AbstractSpoolJobService.class.getName()); private static final long CLEANUP_POLLINGTIME = 60 * 1000L; private static final long DISPATCHER_POLLINGTIME = 10 * 1000L; private static final long KEEPALIVE_POLLINGTIME = 60 * 1000L; private static final long EXECUTOR_POLLINGTIME = 60 * 1000L; private static final long MESSAGE_POLLINGTIME = 60 * 1000L; private static final int MAX_STARTED_JOBS = 50; private static final int MAX_DISPATCHED_JOBS = 20; private static final long JOB_TIMEOUT = 60 * 1000L; private static final long JOB_LIFETIME = 120 * 1000L; private static final long REQUEST_TIMEOUT = 30 * 1000L; private static final ThreadFactory factory = new DefaultThreadFactory("Thread", true, Thread.MIN_PRIORITY); private final List<JobServiceListener> listeners = new LinkedList<JobServiceListener>(); private final List<ExecutorTask> tasks = new LinkedList<ExecutorTask>(); private final HashMap<String, ScheduledJob> scheduledJobs = new HashMap<String, ScheduledJob>(); private final HashMap<String, ScheduledJob> startedJobs = new HashMap<String, ScheduledJob>(); private final HashMap<String, ScheduledJob> terminatedJobs = new HashMap<String, ScheduledJob>(); private final HashMap<String, ScheduledJob> spooledJobs = new HashMap<String, ScheduledJob>(); private final Set<JobSession> sessionHandlers = new LinkedHashSet<JobSession>(); private volatile List<ServiceEndpoint> services = new LinkedList<ServiceEndpoint>(); private final DiscoveryService discoveryService; private final JobFactory<? extends DistributedSpoolJobInterface> jobFactory; private final Object dispatchMonitor = new Object(); private final Object serviceMonitor = new Object(); private final Object messageMonitor = new Object(); private final Object monitor = new Object(); private final String serviceName; private final Worker worker; private Thread thread; private boolean running; private boolean dispatchDirty; private boolean serviceDirty; private boolean messageDirty; private boolean dirty; private volatile int jobCount; private final int serviceId; /** * @param serviceId * @param serviceName * @param discoveryService * @param jobFactory * @param worker */ public AbstractSpoolJobService(final int serviceId, final String serviceName, final DiscoveryService discoveryService, final JobFactory<? extends DistributedSpoolJobInterface> jobFactory, final Worker worker) { this.serviceId = serviceId; this.serviceName = serviceName; this.discoveryService = discoveryService; this.jobFactory = jobFactory; this.worker = worker; } /** * @see net.sf.jame.queue.spool.JobService#getName() */ public String getName() { return serviceName; } /** * @see net.sf.jame.queue.spool.JobService#start() */ public void start() { if (thread == null) { thread = factory.newThread(new ServiceHandler()); thread.setName(serviceName + " JobService Thread"); running = true; thread.start(); } } /** * @see net.sf.jame.queue.spool.JobService#stop() */ public void stop() { if (thread != null) { running = false; thread.interrupt(); try { thread.join(); } catch (final InterruptedException e) { } thread = null; } synchronized (spooledJobs) { synchronized (scheduledJobs) { synchronized (startedJobs) { synchronized (terminatedJobs) { spooledJobs.clear(); scheduledJobs.clear(); startedJobs.clear(); terminatedJobs.clear(); } } } } } /** * @see net.sf.jame.queue.spool.JobService#getJobCount() */ public int getJobCount() { synchronized (spooledJobs) { return spooledJobs.size(); } } private boolean isBusy() { return (scheduledJobs.size() != 0) || (startedJobs.size() != 0) || (terminatedJobs.size() != 0); } /** * @see net.sf.jame.queue.spool.JobService#deleteJob(java.lang.String) */ public void deleteJob(final String jobId) { if (jobId == null) { throw new NullPointerException("jobId == null"); } synchronized (spooledJobs) { synchronized (scheduledJobs) { synchronized (terminatedJobs) { final ScheduledJob scheduledJob = spooledJobs.remove(jobId); scheduledJobs.remove(jobId); terminatedJobs.remove(jobId); if (scheduledJob != null) { worker.addTask(new DeleteTask(scheduledJob)); } fireStateChanged(isBusy() ? JobServiceListener.STATUS_BUSY : JobServiceListener.STATUS_IDLE, createMessage()); } } } } /** * @see net.sf.jame.queue.spool.JobService#stopJob(java.lang.String) */ public void stopJob(final String jobId) { if (jobId == null) { throw new NullPointerException("jobId == null"); } synchronized (spooledJobs) { synchronized (scheduledJobs) { synchronized (startedJobs) { scheduledJobs.remove(jobId); final ScheduledJob scheduledJob = startedJobs.remove(jobId); if (scheduledJob != null) { worker.addTask(new StopTask(scheduledJob)); } } } } } /** * @see net.sf.jame.queue.spool.JobService#abortJob(java.lang.String) */ public void abortJob(final String jobId) { if (jobId == null) { throw new NullPointerException("jobId == null"); } synchronized (spooledJobs) { synchronized (scheduledJobs) { synchronized (startedJobs) { final ScheduledJob scheduledJob = startedJobs.get(jobId); if (scheduledJob != null) { worker.addTask(new AbortTask(scheduledJob)); } } } } } /** * @see net.sf.jame.queue.spool.JobService#createJob(net.sf.jame.queue.spool.JobListener) */ public String createJob(final JobListener listener) { if (listener == null) { throw new NullPointerException("listener == null"); } synchronized (spooledJobs) { final DistributedSpoolJobInterface job = jobFactory.createJob(JobIDFactory.newJobId(), listener); spooledJobs.put(job.getJobId(), new ScheduledJob(job)); return job.getJobId(); } } /** * @see net.sf.jame.queue.spool.JobService#setJobData(java.lang.String, JobData, int) */ public void setJobData(final String jobId, final JobData jobData, final int frameNumber) { if (jobId == null) { throw new NullPointerException("jobId == null"); } if (jobData == null) { throw new NullPointerException("jobData == null"); } synchronized (spooledJobs) { ScheduledJob job = spooledJobs.get(jobId); if (job != null) { job.getJob().setJobDataRow(jobData); job.getJob().setFirstFrameNumber(frameNumber); } } } /** * @see net.sf.jame.queue.spool.JobService#runJob(java.lang.String) */ public void runJob(final String jobId) { if (jobId == null) { throw new NullPointerException("jobId == null"); } synchronized (spooledJobs) { synchronized (scheduledJobs) { synchronized (startedJobs) { synchronized (terminatedJobs) { if (terminatedJobs.get(jobId) == null) { if (startedJobs.get(jobId) == null) { ScheduledJob scheduledJob = scheduledJobs.get(jobId); if (scheduledJob != null) { worker.addTask(new ResetTask(scheduledJob)); } else { scheduledJob = spooledJobs.get(jobId); if (scheduledJob != null) { scheduledJobs.put(jobId, scheduledJob); worker.addTask(new ResetTask(scheduledJob)); } } } } fireStateChanged(isBusy() ? JobServiceListener.STATUS_BUSY : JobServiceListener.STATUS_IDLE, createMessage()); } } } } synchronized (dispatchMonitor) { dispatchDirty = true; dispatchMonitor.notify(); } } private class ServiceHandler implements Runnable { /** * @see java.lang.Runnable#run() */ public void run() { boolean interrupted = false; fireStateChanged(JobServiceListener.STATUS_BORN, serviceName + " service started"); final Thread dispatcherThread = factory.newThread(new DispatcherHandler()); dispatcherThread.setName(serviceName + " Dispatcher Thread"); final Thread executorThread = factory.newThread(new ExecutorHandler()); executorThread.setName(serviceName + " Executor Thread"); final Thread keepaliveThread = factory.newThread(new KeepaliveHandler()); keepaliveThread.setName(serviceName + " Keepalive Thread"); final Thread messageThread = factory.newThread(new MessageHandler()); messageThread.setName(serviceName + " Message Thread"); try { dispatcherThread.start(); executorThread.start(); messageThread.start(); keepaliveThread.start(); discoveryService.start(); Iterator<ScheduledJob> jobIterator = null; while (running) { synchronized (spooledJobs) { synchronized (scheduledJobs) { synchronized (startedJobs) { jobIterator = startedJobs.values().iterator(); while (jobIterator.hasNext()) { final ScheduledJob scheduledJob = jobIterator.next(); if (scheduledJob.isTerminated()) { jobIterator.remove(); terminatedJobs.put(scheduledJob.getJob().getJobId(), scheduledJob); worker.addTask(new StopTask(scheduledJob)); } else if (scheduledJob.isFailed() || ((System.currentTimeMillis() - scheduledJob.getJob().getLastUpdate()) > JOB_TIMEOUT)) { jobIterator.remove(); worker.addTask(new ResetTask(scheduledJob)); } } jobIterator = scheduledJobs.values().iterator(); while (jobIterator.hasNext()) { final ScheduledJob scheduledJob = jobIterator.next(); if (scheduledJob.isTerminated()) { jobIterator.remove(); } } synchronized (terminatedJobs) { jobIterator = terminatedJobs.values().iterator(); while (jobIterator.hasNext()) { final ScheduledJob scheduledJob = jobIterator.next(); if ((System.currentTimeMillis() - scheduledJob.getJob().getLastUpdate()) > JOB_LIFETIME) { jobIterator.remove(); spooledJobs.remove(scheduledJob.getJob().getJobId()); worker.addTask(new DeleteTask(scheduledJob)); } } fireStateChanged(isBusy() ? JobServiceListener.STATUS_BUSY : JobServiceListener.STATUS_IDLE, createMessage()); } } } } synchronized (serviceMonitor) { if (!serviceDirty) { serviceMonitor.wait(CLEANUP_POLLINGTIME); } // else { // Thread.yield(); // } serviceDirty = false; } } } catch (final InterruptedException e) { interrupted = true; } finally { discoveryService.stop(); dispatcherThread.interrupt(); executorThread.interrupt(); keepaliveThread.interrupt(); messageThread.interrupt(); try { dispatcherThread.join(); } catch (InterruptedException e) { interrupted = true; } try { executorThread.join(); } catch (InterruptedException e) { interrupted = true; } try { keepaliveThread.join(); } catch (InterruptedException e) { interrupted = true; } try { messageThread.join(); } catch (InterruptedException e) { interrupted = true; } } fireStateChanged(JobServiceListener.STATUS_DEAD, serviceName + " service stopped"); if (interrupted) { Thread.currentThread().interrupt(); } } } private class DispatcherHandler implements Runnable { private final List<ScheduledJob> jobs = new LinkedList<ScheduledJob>(); /** * @see java.lang.Runnable#run() */ public void run() { try { while (running) { try { fetchJobs(jobs); services = discoveryService.getEndpoints(); dispatchJobs(services, jobs); jobs.clear(); } catch (final Exception e) { e.printStackTrace(); } synchronized (dispatchMonitor) { if (!dispatchDirty) { dispatchMonitor.wait(DISPATCHER_POLLINGTIME); } // else { // Thread.yield(); // } dispatchDirty = false; } } } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } private void fetchJobs(final List<ScheduledJob> jobs) { synchronized (scheduledJobs) { synchronized (startedJobs) { for (ScheduledJob scheduledJob : scheduledJobs.values()) { if (!scheduledJob.isAborted() && !startedJobs.containsKey(scheduledJob.getJob().getJobId()) && (startedJobs.size() < MAX_STARTED_JOBS)) { startedJobs.put(scheduledJob.getJob().getJobId(), scheduledJob); jobs.add(scheduledJob); } } } } } private void dispatchJobs(final List<ServiceEndpoint> services, final List<ScheduledJob> jobs) { try { if (jobs.size() > 0) { for (int i = 0; i < services.size(); i++) { ServiceEndpoint service = services.get(i); if (System.currentTimeMillis() - service.getJobCountLastUpdate() > 30000) { try { if (jobs.size() == 0) { break; } ScheduledJob job = jobs.remove(0); while ((jobs.size() > 0) && job.isStarted()) { job = jobs.remove(0); } if (!job.isStarted()) { scheduleJob(job, service); } } catch (final Exception e) { e.printStackTrace(); } } } Thread.sleep(2000); for (int i = 0; i < MAX_DISPATCHED_JOBS; i++) { try { if (jobs.size() == 0) { break; } ServiceEndpoint service = selectEndpoint(services); if (service == null) { break; } ScheduledJob job = jobs.remove(0); if (!job.isStarted()) { scheduleJob(job, service); } } catch (final Exception e) { e.printStackTrace(); } } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } private void scheduleJob(final ScheduledJob scheduledJob, final ServiceEndpoint service) throws ServiceException { if (scheduledJob.getSession() == null) { final ServiceSession session = service.createSession(scheduledJob); if (session != null) { scheduledJob.setSession(session); service.setJobCount(service.getJobCount() + 1); // sono ottimista, mi aspetto che il job verrĂ  elaborato da questo service } } if (scheduledJob.getSession() != null) { worker.addTask(new StartTask(scheduledJob)); } } private ServiceEndpoint selectEndpoint(final List<ServiceEndpoint> services) { if (services.size() > 0) { ServiceEndpoint bestService = services.get(0); for (int i = 1; i < services.size(); i++) { ServiceEndpoint service = services.get(i); if (service.getJobCount() < bestService.getJobCount()) { bestService = service; } } return bestService; } return null; } } private class KeepaliveHandler implements Runnable { private final List<ScheduledJob> jobs = new LinkedList<ScheduledJob>(); /** * @see java.lang.Runnable#run() */ public void run() { try { while (running) { try { jobs.clear(); fetchJobs(jobs); processJobs(jobs); } catch (final Exception e) { e.printStackTrace(); } Thread.sleep(KEEPALIVE_POLLINGTIME); } } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } private void processJobs(final List<ScheduledJob> jobs) { final Iterator<ScheduledJob> jobIterator = jobs.iterator(); while (jobIterator.hasNext()) { ScheduledJob scheduledJob = jobIterator.next(); scheduledJob.keepalive(); } } private void fetchJobs(final List<ScheduledJob> jobs) { synchronized (startedJobs) { for (ScheduledJob scheduledJob : startedJobs.values()) { if (scheduledJob.getSession() != null) { jobs.add(scheduledJob); } } } } } private class MessageHandler implements Runnable { /** * @see java.lang.Runnable#run() */ public void run() { List<JobSession> tmpSessionHandlers = new LinkedList<JobSession>(); try { while (running) { try { synchronized (sessionHandlers) { tmpSessionHandlers.clear(); tmpSessionHandlers.addAll(sessionHandlers); } for (JobSession sessionHandler : tmpSessionHandlers) { sessionHandler.update(); } tmpSessionHandlers.clear(); } catch (final Exception e) { e.printStackTrace(); } synchronized (messageMonitor) { if (!messageDirty) { messageMonitor.wait(MESSAGE_POLLINGTIME); } // else { // Thread.yield(); // } messageDirty = false; } } } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } } private class ExecutorHandler implements Runnable { /** * @see java.lang.Runnable#run() */ public void run() { final List<ExecutorTask> tasksToRun = new LinkedList<ExecutorTask>(); try { while (running) { try { synchronized (tasks) { tasksToRun.addAll(tasks); tasks.clear(); } for (final ExecutorTask task : tasksToRun) { task.run(); Thread.yield(); } tasksToRun.clear(); } catch (final Exception e) { e.printStackTrace(); } synchronized (monitor) { if (!dirty) { monitor.wait(EXECUTOR_POLLINGTIME); } dirty = false; } } } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } } private class ExecutorTask implements Runnable { private final ServiceSession session; private final ServiceMessage message; /** * @param session * @param message */ public ExecutorTask(final ServiceSession session, final ServiceMessage message) { this.session = session; this.message = message; } /** * @see java.lang.Runnable#run() */ public void run() { if (session == null) { logger.warning("Failed to send message " + message + ", session is null"); return; } try { if (logger.isLoggable(Level.FINE)) { logger.fine("Ready to send message " + message + " on session " + session.getSessionId()); } if (!session.isExpired()) { session.sendMessage(message); } else { logger.warning("Failed to send message " + message + " on session " + session.getSessionId()); session.invalidate(); } } catch (final Exception e) { logger.warning("Failed to send message " + message + " on session " + session.getSessionId()); session.invalidate(); e.printStackTrace(); } } } private class DeleteTask implements Runnable { private final ScheduledJob job; /** * @param job */ protected DeleteTask(final ScheduledJob job) { this.job = job; } /** * @see java.lang.Runnable#run() */ public void run() { job.dispose(); logger.info(serviceName + ": Job deleted " + job.getJob() + " (jobs = " + jobCount + ")"); } } private class StartTask implements Runnable { private final ScheduledJob job; /** * @param job */ protected StartTask(final ScheduledJob job) { this.job = job; } /** * @see java.lang.Runnable#run() */ public void run() { job.start(); jobCount += 1; logger.info(serviceName + ": Job started " + job.getJob() + " (jobs = " + jobCount + ")"); } } private class StopTask implements Runnable { private final ScheduledJob job; /** * @param job */ protected StopTask(final ScheduledJob job) { this.job = job; } /** * @see java.lang.Runnable#run() */ public void run() { job.stop(); if (jobCount > 0) { jobCount -= 1; } logger.info(serviceName + ": Job stopped " + job.getJob() + " (jobs = " + jobCount + ")"); } } private class AbortTask implements Runnable { private final ScheduledJob job; /** * @param job */ protected AbortTask(final ScheduledJob job) { this.job = job; } /** * @see java.lang.Runnable#run() */ public void run() { job.abort(); logger.info(serviceName + ": Job aborted " + job.getJob() + " (jobs = " + jobCount + ")"); } } private class ResetTask implements Runnable { private final ScheduledJob job; /** * @param job */ protected ResetTask(final ScheduledJob job) { this.job = job; } /** * @see java.lang.Runnable#run() */ public void run() { job.reset(); if (jobCount > 0) { jobCount -= 1; } logger.info(serviceName + ": Job resetted " + job.getJob() + " (jobs = " + jobCount + ")"); } } private class ScheduledJob implements ServiceListener { private final JobSession jobSession; /** * @param job */ public ScheduledJob(final DistributedSpoolJobInterface job) { jobSession = new JobSession(job); } /** * */ public void keepalive() { jobSession.keepalive(); } /** * @return */ public boolean isFailed() { return jobSession.isFailed(); } /** * @return */ public boolean isStarted() { return jobSession.isStarted(); } /** * @return */ public boolean isAborted() { return jobSession.isAborted(); } /** * @return */ public boolean isTerminated() { return jobSession.isTerminated(); } /** * */ public void reset() { jobSession.reset(); } /** * */ public void start() { jobSession.start(); } /** * */ public void abort() { jobSession.abort(); } /** * */ public void stop() { jobSession.stop(); } /** * */ public void dispose() { jobSession.dispose(); } /** * @param session */ public void setSession(final ServiceSession session) { jobSession.setSession(session); } /** * @return */ public ServiceSession getSession() { return jobSession.getSession(); } /** * @return the job */ public DistributedSpoolJobInterface getJob() { return jobSession.job; } /** * @see net.sf.jame.queue.network.ServiceListener#onMessage(net.sf.jame.queue.network.ServiceMessage) */ public void onMessage(final ServiceMessage message) throws ServiceException { jobSession.onMessage(message); } } private class JobSession implements SessionHandler { private final InitializedStatus INITIALIZED_STATUS = new InitializedStatus(); private final ResettedStatus RESETTED_STATUS = new ResettedStatus(); private final DisposedStatus DISPOSED_STATUS = new DisposedStatus(); private final AbortedStatus ABORTED_STATUS = new AbortedStatus(); private final StartedStatus STARTED_STATUS = new StartedStatus(); private final StoppedStatus STOPPED_STATUS = new StoppedStatus(); private final DeletedStatus DELETED_STATUS = new DeletedStatus(); private final ExpiredStatus EXPIRED_STATUS = new ExpiredStatus(); private final FailedStatus FAILED_STATUS = new FailedStatus(); private final WaitingHelloStatus WAITING_HELLO_STATUS = new WaitingHelloStatus(); private final WaitingPutStatus WAITING_PUT_STATUS = new WaitingPutStatus(); private final WaitingGetStatus WAITING_GET_STATUS = new WaitingGetStatus(); private final WaitingEventStatus WAITING_EVENT_STATUS = new WaitingEventStatus(); private final WaitingAbortStatus WAITING_ABORT_STATUS = new WaitingAbortStatus(); private final WaitingDeleteStatus WAITING_DELETE_STATUS = new WaitingDeleteStatus(); private final HelloCompletedStatus HELLO_COMPLETED_STATUS = new HelloCompletedStatus(); private final PutCompletedStatus PUT_COMPLETED_STATUS = new PutCompletedStatus(); private final GetCompletedStatus GET_COMPLETED_STATUS = new GetCompletedStatus(); private final AbortCompletedStatus ABORT_COMPLETED_STATUS = new AbortCompletedStatus(); private final DeleteCompletedStatus DELETE_COMPLETED_STATUS = new DeleteCompletedStatus(); private final List<ServiceMessage> messages = new LinkedList<ServiceMessage>(); private final List<Status> requiredStatusList = new LinkedList<Status>(); private final List<JobEvent> jobEventList = new LinkedList<JobEvent>(); private final DistributedSpoolJobInterface job; private volatile ServiceSession session; private volatile long lastSentMessage; private volatile Status status = INITIALIZED_STATUS; private volatile Status oldStatus = status; private volatile int lastFrameNumber; private volatile int frameNumber; /** * @param job */ public JobSession(final DistributedSpoolJobInterface job) { this.job = job; synchronized (sessionHandlers) { sessionHandlers.add(this); } } /** * */ public synchronized void keepalive() { try { if ((session != null) && !session.isExpired()) { session.sendKeepAliveMessage(); } } catch (ServiceException e) { e.printStackTrace(); } } /** * @param message */ public synchronized void onMessage(final ServiceMessage message) { messages.add(message); synchronized (messageMonitor) { messageDirty = true; messageMonitor.notify(); } } /** * */ public synchronized void update() { status.process(); if (oldStatus != status) { if (logger.isLoggable(Level.FINE)) { logger.fine("Job " + job.getJobId() + " status changed from " + oldStatus.getName() + " to " + status.getName()); } oldStatus = status; synchronized (messageMonitor) { messageDirty = true; messageMonitor.notify(); } if (status == FAILED_STATUS) { synchronized (serviceMonitor) { serviceDirty = true; serviceMonitor.notify(); } } } } private boolean isRequestTimeout() { return ((System.currentTimeMillis() - lastSentMessage) > REQUEST_TIMEOUT) || isExpired(); } /** * @return */ public synchronized boolean isTerminated() { return job.isTerminated(); } /** * @return */ public synchronized boolean isAborted() { return job.isAborted(); } /** * @return */ public synchronized boolean isStarted() { return job.isStarted(); } /** * */ public synchronized void reset() { status.reset(); } /** * */ public synchronized void start() { status.start(); } /** * */ public synchronized void abort() { status.abort(); } /** * */ public synchronized void stop() { status.stop(); } /** * */ public synchronized void dispose() { status.dispose(); } /** * @return */ public synchronized boolean isExpired() { return (session != null) && session.isExpired(); } /** * @return */ public synchronized boolean isFailed() { return status == FAILED_STATUS; } /** * @return the session */ public synchronized ServiceSession getSession() { return session; } /** * @param session the session to set */ public synchronized void setSession(final ServiceSession session) { if (session == null) { throw new IllegalArgumentException("session == null"); } if (this.session != null) { throw new IllegalArgumentException("session already set"); } this.session = session; } private void processFailHelloResponse(final ResponseMessage response) { final int jobCount = (Integer) ((Object[]) response.getUserData())[0]; session.setEndpointJobCount(jobCount); } private void sendHelloRequest() { lastSentMessage = System.currentTimeMillis(); synchronized (tasks) { try { final RequestMessage request = createHelloRequest(); tasks.add(new ExecutorTask(session, request)); } catch (final Exception e) { e.printStackTrace(); } } synchronized (monitor) { dirty = true; monitor.notify(); } } private void sendAbortRequest() { lastSentMessage = System.currentTimeMillis(); synchronized (tasks) { try { final RequestMessage request = createAbortRequest(job); tasks.add(new ExecutorTask(session, request)); } catch (final Exception e) { e.printStackTrace(); } } synchronized (monitor) { dirty = true; monitor.notify(); } } private void sendDeleteRequest() { lastSentMessage = System.currentTimeMillis(); synchronized (tasks) { try { final RequestMessage request = createDeleteRequest(job); tasks.add(new ExecutorTask(session, request)); } catch (final Exception e) { e.printStackTrace(); } } synchronized (monitor) { dirty = true; monitor.notify(); } } private void sendPutRequest() { lastSentMessage = System.currentTimeMillis(); synchronized (tasks) { try { final RequestMessage request = createPutRequest(job); tasks.add(new ExecutorTask(session, request)); } catch (final Exception e) { e.printStackTrace(); } } synchronized (monitor) { dirty = true; monitor.notify(); } } private void sendGetRequest(final int frameNumber) { lastSentMessage = System.currentTimeMillis(); synchronized (tasks) { try { final RequestMessage request = createGetRequest(job, frameNumber); tasks.add(new ExecutorTask(session, request)); } catch (final Exception e) { e.printStackTrace(); } } synchronized (monitor) { dirty = true; monitor.notify(); } } private RequestMessage createPutRequest(final DistributedSpoolJobInterface job) throws Exception { final RequestMessage message = new RequestMessage(); message.setRequestId(RequestIDFactory.newRequestId()); message.setRequestType(RequestMessage.TYPE_PUT); if (!session.isLocalSession()) { byte[] jobData = null; if (job.getFrameNumber() > 0) { ChunkedRandomAccessFile raf = job.getRAF(); try { raf = job.getRAF(); final int tw = job.getJobDataRow().getTileWidth(); final int th = job.getJobDataRow().getTileHeight(); final int bw = job.getJobDataRow().getBorderWidth(); final int bh = job.getJobDataRow().getBorderHeight(); final int sw = tw + 2 * bw; final int sh = th + 2 * bh; final byte[] data = new byte[sw * sh * 4]; final long pos = job.getFrameNumber() * sw * sh * 4; raf.seek(pos); raf.readFully(data); jobData = data; } finally { if (raf != null) { try { raf.close(); } catch (final IOException e) { } } } } final DistributedJobEncoder encoder = new DistributedJobEncoder(job.getClip(), job.getJobDataRow(), jobData); message.setUserData(new Object[] { job.getRemoteJobId(), job.getFrameNumber(), encoder.getBytes() }); } else { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(job.getJobDataRow()); oos.close(); baos.close(); message.setUserData(new Object[] { job.getRemoteJobId(), job.getFrameNumber(), baos.toByteArray() }); } return message; } private RequestMessage createHelloRequest() { final RequestMessage message = new RequestMessage(); message.setRequestId(RequestIDFactory.newRequestId()); message.setRequestType(RequestMessage.TYPE_HELLO); return message; } private RequestMessage createGetRequest(final DistributedSpoolJobInterface job, final int frameNumber) throws Exception { final RequestMessage message = new RequestMessage(); message.setRequestId(RequestIDFactory.newRequestId()); message.setRequestType(RequestMessage.TYPE_GET); message.setUserData(new Object[] { job.getRemoteJobId(), frameNumber }); return message; } private RequestMessage createAbortRequest(final DistributedSpoolJobInterface job) throws Exception { final RequestMessage message = new RequestMessage(); message.setRequestId(RequestIDFactory.newRequestId()); message.setRequestType(RequestMessage.TYPE_ABORT); message.setUserData(job.getRemoteJobId()); return message; } private RequestMessage createDeleteRequest(final DistributedSpoolJobInterface job) throws Exception { final RequestMessage message = new RequestMessage(); message.setRequestId(RequestIDFactory.newRequestId()); message.setRequestType(RequestMessage.TYPE_DELETE); message.setUserData(job.getRemoteJobId()); return message; } private void processHelloResponse(final ResponseMessage response) { final int jobCount = (Integer) ((Object[]) response.getUserData())[0]; final String jobId = (String) ((Object[]) response.getUserData())[1]; job.setRemoteJobId(jobId); session.setEndpointJobCount(jobCount); } private void processGetResponse(final ResponseMessage response) { final int frameNumber = (Integer) ((Object[]) response.getUserData())[1]; if (!session.isLocalSession()) { final byte[] data = (byte[]) ((Object[]) response.getUserData())[2]; ChunkedRandomAccessFile raf = null; final int tw = job.getJobDataRow().getTileWidth(); final int th = job.getJobDataRow().getTileHeight(); final int bw = job.getJobDataRow().getBorderWidth(); final int bh = job.getJobDataRow().getBorderHeight(); final int sw = tw + 2 * bw; final int sh = th + 2 * bh; final long pos = frameNumber * sw * sh * 4; try { raf = job.getRAF(); raf.seek(pos); raf.write(data); lastFrameNumber = frameNumber; job.setFrameNumber(frameNumber); } catch (final IOException e) { e.printStackTrace(); } finally { if (raf != null) { try { raf.close(); } catch (final IOException e) { } } } } else { lastFrameNumber = frameNumber; job.setFrameNumber(frameNumber); } } private void processEvent(final ServiceMessage message) { final EventMessage event = (EventMessage) message; final JobEvent jobEvent = (JobEvent) event.getUserData(); jobEventList.add(jobEvent); } private class InitializedStatus extends Status { @Override public void update() { updateStatus(STARTED_STATUS); } @Override public String getName() { return "INITIALIZED"; } } private class DeletedStatus extends Status { @Override public void update() { updateStatus(RESETTED_STATUS, STARTED_STATUS); } @Override public String getName() { return "DELETED"; } } private class FailedStatus extends Status { @Override public void update() { updateStatus(RESETTED_STATUS, STARTED_STATUS); } @Override public String getName() { return "FAILED"; } } private class DisposedStatus extends Status { @Override public void update() { synchronized (sessionHandlers) { sessionHandlers.remove(this); } job.dispose(); messages.clear(); jobEventList.clear(); if (session != null) { session.invalidate(); session.dispose(); session = null; } synchronized (serviceMonitor) { serviceDirty = true; serviceMonitor.notify(); } synchronized (dispatchMonitor) { dispatchDirty = true; dispatchMonitor.notify(); } } @Override public String getName() { return "DISPOSED"; } } private class ExpiredStatus extends Status { @Override public void update() { status = FAILED_STATUS; if (session != null) { session.invalidate(); session.dispose(); session = null; } synchronized (serviceMonitor) { serviceDirty = true; serviceMonitor.notify(); } synchronized (dispatchMonitor) { dispatchDirty = true; dispatchMonitor.notify(); } } @Override public void process() { update(); } @Override public String getName() { return "EXPIRED"; } } private class StartedStatus extends Status { @Override public void update() { lastFrameNumber = -1; frameNumber = -1; messages.clear(); jobEventList.clear(); job.start(); status = WAITING_HELLO_STATUS; sendHelloRequest(); } @Override public String getName() { return "STARTED"; } } private class StoppedStatus extends Status { @Override public void update() { job.stop(); if (session != null) { status = WAITING_ABORT_STATUS; sendAbortRequest(); } else { status = DELETE_COMPLETED_STATUS; } } @Override public String getName() { return "STOPPED"; } } private class AbortedStatus extends Status { @Override public void update() { job.abort(); if (session != null) { status = WAITING_ABORT_STATUS; sendAbortRequest(); } else { status = DELETE_COMPLETED_STATUS; } } @Override public String getName() { return "ABORTED"; } } private class ResettedStatus extends Status { @Override public void update() { job.reset(); lastFrameNumber = -1; frameNumber = -1; messages.clear(); jobEventList.clear(); if (session != null) { status = WAITING_ABORT_STATUS; sendAbortRequest(); } else { status = DELETE_COMPLETED_STATUS; } } @Override public String getName() { return "RESETTED"; } } private class WaitingAbortStatus extends Status { @Override public void update() { if (session != null) { for (ServiceMessage message : messages) { consumeMessage(message); } messages.clear(); } if (isRequestTimeout()) { status = FAILED_STATUS; } } @Override public String getName() { return "WAITING_ABORT"; } private void consumeMessage(final ServiceMessage message) { try { switch (message.getMessageType()) { case ServiceMessage.MESSAGE_TYPE_RESPONSE: { final ResponseMessage response = (ResponseMessage) message; if (response.getReturnCode() != 0) { switch (response.getResponseType()) { case RequestMessage.TYPE_HELLO: case RequestMessage.TYPE_PUT: case RequestMessage.TYPE_GET: case RequestMessage.TYPE_ABORT: case RequestMessage.TYPE_DELETE: { status = FAILED_STATUS; break; } default: { break; } } } if (response.getReturnCode() == 0) { switch (response.getResponseType()) { case RequestMessage.TYPE_HELLO: case RequestMessage.TYPE_PUT: case RequestMessage.TYPE_GET: case RequestMessage.TYPE_DELETE: { status = FAILED_STATUS; break; } case RequestMessage.TYPE_ABORT: { status = ABORT_COMPLETED_STATUS; break; } default: { break; } } } break; } case ServiceMessage.MESSAGE_TYPE_REQUEST: { status = FAILED_STATUS; break; } case ServiceMessage.MESSAGE_TYPE_EVENT: { status = FAILED_STATUS; break; } default: { break; } } } catch (final Exception e) { e.printStackTrace(); } } } private class WaitingDeleteStatus extends Status { @Override public void update() { if (session != null) { for (ServiceMessage message : messages) { consumeMessage(message); } messages.clear(); } if (isRequestTimeout()) { status = FAILED_STATUS; } } @Override public String getName() { return "WAITING_DELETE"; } private void consumeMessage(final ServiceMessage message) { try { switch (message.getMessageType()) { case ServiceMessage.MESSAGE_TYPE_RESPONSE: { final ResponseMessage response = (ResponseMessage) message; if (response.getReturnCode() != 0) { switch (response.getResponseType()) { case RequestMessage.TYPE_HELLO: case RequestMessage.TYPE_PUT: case RequestMessage.TYPE_GET: case RequestMessage.TYPE_ABORT: case RequestMessage.TYPE_DELETE: { status = FAILED_STATUS; break; } default: { break; } } } if (response.getReturnCode() == 0) { switch (response.getResponseType()) { case RequestMessage.TYPE_HELLO: case RequestMessage.TYPE_PUT: case RequestMessage.TYPE_GET: case RequestMessage.TYPE_ABORT: { status = FAILED_STATUS; break; } case RequestMessage.TYPE_DELETE: { status = DELETE_COMPLETED_STATUS; break; } default: { break; } } } break; } case ServiceMessage.MESSAGE_TYPE_REQUEST: { status = FAILED_STATUS; break; } case ServiceMessage.MESSAGE_TYPE_EVENT: { status = FAILED_STATUS; break; } default: { break; } } } catch (final Exception e) { e.printStackTrace(); } } } private class WaitingHelloStatus extends Status { @Override public void update() { if (session != null) { for (ServiceMessage message : messages) { consumeMessage(message); } messages.clear(); } if (isRequestTimeout()) { status = FAILED_STATUS; } } @Override public String getName() { return "WAITING_HELLO"; } private void consumeMessage(final ServiceMessage message) { try { switch (message.getMessageType()) { case ServiceMessage.MESSAGE_TYPE_RESPONSE: { final ResponseMessage response = (ResponseMessage) message; if (response.getReturnCode() != 0) { switch (response.getResponseType()) { case RequestMessage.TYPE_PUT: case RequestMessage.TYPE_GET: case RequestMessage.TYPE_ABORT: case RequestMessage.TYPE_DELETE: { status = FAILED_STATUS; break; } case RequestMessage.TYPE_HELLO: { processFailHelloResponse(response); status = FAILED_STATUS; break; } default: { break; } } } if (response.getReturnCode() == 0) { switch (response.getResponseType()) { case RequestMessage.TYPE_GET: case RequestMessage.TYPE_PUT: case RequestMessage.TYPE_ABORT: case RequestMessage.TYPE_DELETE: { status = FAILED_STATUS; break; } case RequestMessage.TYPE_HELLO: { processHelloResponse(response); status = HELLO_COMPLETED_STATUS; break; } default: { break; } } } break; } case ServiceMessage.MESSAGE_TYPE_REQUEST: { status = FAILED_STATUS; break; } case ServiceMessage.MESSAGE_TYPE_EVENT: { status = FAILED_STATUS; break; } default: { break; } } } catch (final Exception e) { e.printStackTrace(); } } } private class WaitingGetStatus extends Status { @Override public void update() { if (session != null) { for (ServiceMessage message : messages) { consumeMessage(message); } messages.clear(); } if (isRequestTimeout()) { status = FAILED_STATUS; } } @Override public String getName() { return "WAITING_GET"; } private void consumeMessage(final ServiceMessage message) { try { switch (message.getMessageType()) { case ServiceMessage.MESSAGE_TYPE_RESPONSE: { final ResponseMessage response = (ResponseMessage) message; if (response.getReturnCode() != 0) { switch (response.getResponseType()) { case RequestMessage.TYPE_HELLO: case RequestMessage.TYPE_PUT: case RequestMessage.TYPE_GET: case RequestMessage.TYPE_ABORT: case RequestMessage.TYPE_DELETE: { status = FAILED_STATUS; break; } default: { break; } } } if (response.getReturnCode() == 0) { switch (response.getResponseType()) { case RequestMessage.TYPE_HELLO: case RequestMessage.TYPE_PUT: case RequestMessage.TYPE_ABORT: case RequestMessage.TYPE_DELETE: { status = FAILED_STATUS; break; } case RequestMessage.TYPE_GET: { processGetResponse(response); status = GET_COMPLETED_STATUS; break; } default: { break; } } } break; } case ServiceMessage.MESSAGE_TYPE_REQUEST: { status = FAILED_STATUS; break; } case ServiceMessage.MESSAGE_TYPE_EVENT: { processEvent(message); break; } default: { break; } } } catch (final Exception e) { e.printStackTrace(); } } } private class WaitingPutStatus extends Status { @Override public void update() { if (session != null) { for (ServiceMessage message : messages) { consumeMessage(message); } messages.clear(); } if (isRequestTimeout()) { status = FAILED_STATUS; } } @Override public String getName() { return "WAITING_PUT"; } private void consumeMessage(final ServiceMessage message) { try { switch (message.getMessageType()) { case ServiceMessage.MESSAGE_TYPE_RESPONSE: { final ResponseMessage response = (ResponseMessage) message; if (response.getReturnCode() != 0) { switch (response.getResponseType()) { case RequestMessage.TYPE_HELLO: case RequestMessage.TYPE_PUT: case RequestMessage.TYPE_GET: case RequestMessage.TYPE_ABORT: case RequestMessage.TYPE_DELETE: { status = FAILED_STATUS; break; } default: { break; } } } if (response.getReturnCode() == 0) { switch (response.getResponseType()) { case RequestMessage.TYPE_HELLO: case RequestMessage.TYPE_GET: case RequestMessage.TYPE_ABORT: case RequestMessage.TYPE_DELETE: { status = FAILED_STATUS; break; } case RequestMessage.TYPE_PUT: { status = PUT_COMPLETED_STATUS; break; } default: { break; } } } break; } case ServiceMessage.MESSAGE_TYPE_REQUEST: { status = FAILED_STATUS; break; } case ServiceMessage.MESSAGE_TYPE_EVENT: { processEvent(message); break; } default: { break; } } } catch (final Exception e) { e.printStackTrace(); } } } private class WaitingEventStatus extends Status { @Override public void update() { if (session != null) { for (ServiceMessage message : messages) { consumeMessage(message); } messages.clear(); if (requiredStatusList.size() > 0) { updateStatus(ABORTED_STATUS, STOPPED_STATUS); } else { if (jobEventList.size() > 0) { JobEvent jobEvent = jobEventList.remove(0); switch (jobEvent.getEventType()) { case JobEvent.EVENT_TYPE_FRAME: { session.setEndpointJobCount(((JobStatus) jobEvent.getEventData()).getJobCount()); frameNumber = ((JobStatus) jobEvent.getEventData()).getFrameNumber(); status = WAITING_GET_STATUS; sendGetRequest(frameNumber); break; } case JobEvent.EVENT_TYPE_BEGIN: { session.setEndpointJobCount(((JobStatus) jobEvent.getEventData()).getJobCount()); lastFrameNumber = -1; frameNumber = -1; break; } case JobEvent.EVENT_TYPE_END: { session.setEndpointJobCount(((JobStatus) jobEvent.getEventData()).getJobCount()); status = WAITING_DELETE_STATUS; sendDeleteRequest(); break; } case JobEvent.EVENT_TYPE_DONE: { session.setEndpointJobCount(((JobStatus) jobEvent.getEventData()).getJobCount()); if ((frameNumber == lastFrameNumber) && (frameNumber >= 0)) { job.terminate(); } break; } default: { break; } } synchronized (messageMonitor) { messageDirty = true; messageMonitor.notify(); } } } } } @Override public String getName() { return "WAITING_EVENT"; } private void consumeMessage(final ServiceMessage message) { try { switch (message.getMessageType()) { case ServiceMessage.MESSAGE_TYPE_RESPONSE: { final ResponseMessage response = (ResponseMessage) message; if (response.getReturnCode() != 0) { switch (response.getResponseType()) { case RequestMessage.TYPE_HELLO: case RequestMessage.TYPE_PUT: case RequestMessage.TYPE_GET: case RequestMessage.TYPE_ABORT: case RequestMessage.TYPE_DELETE: { status = FAILED_STATUS; break; } default: { break; } } } if (response.getReturnCode() == 0) { switch (response.getResponseType()) { case RequestMessage.TYPE_HELLO: case RequestMessage.TYPE_GET: case RequestMessage.TYPE_PUT: case RequestMessage.TYPE_ABORT: case RequestMessage.TYPE_DELETE: { status = FAILED_STATUS; break; } default: { break; } } } break; } case ServiceMessage.MESSAGE_TYPE_REQUEST: { status = FAILED_STATUS; break; } case ServiceMessage.MESSAGE_TYPE_EVENT: { processEvent(message); break; } default: { break; } } } catch (final Exception e) { e.printStackTrace(); } } } private class AbortCompletedStatus extends Status { @Override public void update() { if (session != null) { status = WAITING_DELETE_STATUS; sendDeleteRequest(); } else { status = DELETE_COMPLETED_STATUS; } } @Override public String getName() { return "ABORT_COMPLETED"; } } private class DeleteCompletedStatus extends Status { @Override public void update() { status = DELETED_STATUS; synchronized (serviceMonitor) { serviceDirty = true; serviceMonitor.notify(); } synchronized (dispatchMonitor) { dispatchDirty = true; dispatchMonitor.notify(); } } @Override public String getName() { return "DELETE_COMPLETED"; } } private class HelloCompletedStatus extends Status { @Override public void update() { jobEventList.clear(); if (session != null) { status = WAITING_PUT_STATUS; sendPutRequest(); } } @Override public String getName() { return "HELLO_COMPLETED"; } } private class GetCompletedStatus extends Status { @Override public void update() { status = WAITING_EVENT_STATUS; updateStatus(ABORTED_STATUS, STOPPED_STATUS); } @Override public String getName() { return "GET_COMPLETED"; } } private class PutCompletedStatus extends Status { @Override public void update() { status = WAITING_EVENT_STATUS; updateStatus(ABORTED_STATUS, STOPPED_STATUS); } @Override public String getName() { return "PUT_COMPLETED"; } } private abstract class Status { public abstract String getName(); public abstract void update(); public void process() { if (isExpired()) { status = EXPIRED_STATUS; return; } update(); } public final void dispose() { requiredStatusList.add(DISPOSED_STATUS); synchronized (messageMonitor) { messageDirty = true; messageMonitor.notify(); } } public final void reset() { requiredStatusList.add(RESETTED_STATUS); synchronized (messageMonitor) { messageDirty = true; messageMonitor.notify(); } } public final void start() { requiredStatusList.add(STARTED_STATUS); synchronized (messageMonitor) { messageDirty = true; messageMonitor.notify(); } } public final void stop() { requiredStatusList.add(STOPPED_STATUS); synchronized (messageMonitor) { messageDirty = true; messageMonitor.notify(); } } public final void abort() { requiredStatusList.add(ABORTED_STATUS); synchronized (messageMonitor) { messageDirty = true; messageMonitor.notify(); } } } public void updateStatus(final Status... statusList) { Status newStatus = null; Iterator<Status> statusIterator = requiredStatusList.iterator(); while (statusIterator.hasNext()) { Status tmpStatus = statusIterator.next(); for (Status status : statusList) { if (status == tmpStatus) { newStatus = tmpStatus; break; } } statusIterator.remove(); if (newStatus != null) { break; } } if (newStatus != null) { status = newStatus; } } } /** * @see net.sf.jame.queue.spool.JobService#addServiceListener(net.sf.jame.queue.spool.JobServiceListener) */ public void addServiceListener(final JobServiceListener listener) { listeners.add(listener); } /** * @see net.sf.jame.queue.spool.JobService#removeServiceListener(net.sf.jame.queue.spool.JobServiceListener) */ public void removeServiceListener(final JobServiceListener listener) { listeners.remove(listener); } /** * @param message */ protected void fireStateChanged(final int status, final String message) { for (JobServiceListener listener : listeners) { listener.stateChanged(serviceId, status, message); } } private String createMessage() { return serviceName + " running (" + services.size() + " " + (services.size() == 1 ? "peer available" : "peers available") + ")"; } }