com.agileapes.webexport.concurrent.impl.AbstractManager.java Source code

Java tutorial

Introduction

Here is the source code for com.agileapes.webexport.concurrent.impl.AbstractManager.java

Source

/*
 * Copyright (c) 2013. AgileApes (http://www.agileapes.scom/), and
 * associated organizations.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 */

package com.agileapes.webexport.concurrent.impl;

import com.agileapes.webexport.concurrent.Manager;
import com.agileapes.webexport.concurrent.Worker;
import com.agileapes.webexport.concurrent.WorkerPreparator;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * This manager will abstract all the main tasks common to a manager process.
 * The single-run implementation and assignment of tasks is left to be implemented
 * by subclasses.
 *
 * @author Mohammad Milad Naseri (m.m.naseri@gmail.com)
 * @since 1.0 (2013/2/23, 13:36)
 */
public abstract class AbstractManager<W extends Worker>
        implements Manager<W>, InitializingBean, ApplicationContextAware {

    private AtomicBoolean running = new AtomicBoolean(true);
    private final Set<W> idle = new CopyOnWriteArraySet<W>();
    private final Set<W> working = new CopyOnWriteArraySet<W>();
    private final Set<W> interrupted = new CopyOnWriteArraySet<W>();
    private boolean autoShutdown;
    private int workers;
    public static final Logger logger = Logger.getLogger(Manager.class);
    private ApplicationContext applicationContext;

    @Override
    public void afterPropertiesSet() throws Exception {
        logger.info("Initializing workers for " + getClass().getSimpleName());
        for (int i = 0; i < workers; i++) {
            final W worker = newWorker(i);
            idle.add(worker);
            worker.start();
        }
    }

    @Override
    public void shutdown() {
        running = new AtomicBoolean(false);
    }

    public void onStartup() {
        logger.info("Starting up " + getClass().getSimpleName() + " ...");
    }

    public void onBeforeShutdown() {
        logger.info("Going to shutdown " + getClass().getSimpleName() + " ...");
    }

    public void onAfterShutdown() {
        logger.info("Shutdown for " + getClass().getSimpleName() + " completed.");
    }

    @Override
    public void run() {
        onStartup();
        try {
            synchronized (this) {
                //we will continue with the execution as long as a shutdown
                //signal has not been sent
                while (running.get()) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        //if we are interrupted we will shutdown safely
                        shutdown();
                        continue;
                    }
                    //in each pass we call the run-once execute method
                    execute();
                }
            }
        } finally {
            onBeforeShutdown();
            //after the execution, we will clean after ourselves.
            //this is essentially the shutdown part.
            while (!working.isEmpty()) {
                //we will wait for all the threads currently working to finish their
                //jobs or otherwise be interrupted
                try {
                    Thread.sleep(100);
                } catch (InterruptedException ignored) {
                }
            }
            //now all the threads must be idle
            for (W worker : idle) {
                //we send an interrupt signal to each idle thread so that
                //it will finish its business
                interrupted.add(worker);
                worker.interrupt();
                try {
                    //we wait for the worker to acknowledge our interrupt signal
                    worker.join();
                } catch (InterruptedException ignored) {
                }
            }
            onAfterShutdown();
        }
    }

    private void markDone(W worker) {
        working.remove(worker);
        idle.add(worker);
    }

    @Override
    public void done(W worker) {
        if (!working.contains(worker)) {
            throw new IllegalStateException();
        }
        markDone(worker);
        logger.info("Finished evaluating worker: " + worker.getName());
        logger.debug("Auto shutdown is " + (autoShutdown ? "" : "not ") + "enabled");
        if (isAutoShutdown()) {
            logger.debug("Number of workers active: " + working.size());
            logger.debug("Completion criteria check: " + completed());
        }
        if (isAutoShutdown() && working.isEmpty() && completed()) {
            shutdown();
        }
    }

    @Override
    public void fail(W worker) {
        done(worker);
        handleFailure(worker);
    }

    @Override
    public void interrupted(W worker) {
        if (interrupted.contains(worker)) {
            markDone(worker);
            return;
        }
        fail(worker);
    }

    protected boolean completed() {
        return true;
    }

    /**
     * This method is supposed to handle cases in which a worker failed to
     * complete its task
     * @param worker    the worker
     */
    protected void handleFailure(W worker) {
    }

    /**
     * @param index     the index of the new worker
     * @return an initialized instance of the worker thread
     */
    protected abstract W newWorker(int index);

    /**
     * This will run the run-once pass over the tasks
     */
    protected abstract void execute();

    /**
     * This method will start a task by preparing some worker and then notifying it
     * @param preparators    the preparators to prepare the worker and initialize the task
     */
    protected void start(WorkerPreparator<W>... preparators) {
        while (idle.isEmpty()) {
            //waiting for some worker to become available
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                //in case of interruption, schedule a clean shutdown
                shutdown();
                return;
            }
        }
        //this part is mainly concerned with assigning the task to the worker
        synchronized (this) {
            final W worker = idle.iterator().next();
            //noinspection SynchronizationOnLocalVariableOrMethodParameter
            synchronized (worker) {
                //we first mark the worker thread as in-use
                idle.remove(worker);
                working.add(worker);
                //then we prepare the worker
                worker.initialize();
                for (WorkerPreparator<W> preparator : preparators) {
                    preparator.prepare(worker);
                }
                //and finally we notify it that it should begin its processing
                worker.notify();
            }
        }
    }

    public void setWorkers(int workers) {
        this.workers = workers;
    }

    public void setAutoShutdown(boolean autoShutdown) {
        this.autoShutdown = autoShutdown;
    }

    public boolean isAutoShutdown() {
        return autoShutdown;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

}