org.paxle.core.threading.AWorker.java Source code

Java tutorial

Introduction

Here is the source code for org.paxle.core.threading.AWorker.java

Source

/**
 * This file is part of the Paxle project.
 * Visit http://www.paxle.net for more information.
 * Copyright 2007-2010 the original author or authors.
 *
 * Licensed under the terms of the Common Public License 1.0 ("CPL 1.0").
 * Any use, reproduction or distribution of this program constitutes the recipient's acceptance of this agreement.
 * The full license text is available under http://www.opensource.org/licenses/cpl1.0.txt
 * or in the file LICENSE.txt in the root directory of the Paxle distribution.
 *
 * Unless required by applicable law or agreed to in writing, this software is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 */

package org.paxle.core.threading;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.paxle.core.doc.ICommand;
import org.paxle.core.queue.ICommandFilterQueue;
import org.paxle.core.queue.ICommandFilteringContext;
import org.paxle.core.queue.IInputQueue;
import org.paxle.core.queue.IOutputQueue;

import edu.umd.cs.findbugs.annotations.OverrideMustInvoke;

/**
 * An abstract class of a {@link IWorker worker-thread}. Components such as
 * <ul>
 *    <li>Core-Crawler</li>
 *    <li>Core-Parser</li>
 *    <li>Indexer</li>
 * </ul>
 * extend this class and provide a component-specific implementation of the
 * {@link #execute(ICommand)} method.
 * 
 * {@link ICommand commands} are assigned to this component by a {@link IMaster master}-thread.
 * 
 */
public abstract class AWorker<Data> extends Thread implements IWorker<Data> {

    private Log logger = LogFactory.getLog(this.getClass());

    /**
     * The crawler thread pool
     */
    private IPool<Data> myPool = null;

    /**
     * The output-queue where the modified command should
     * be enqueued.
     */
    private IOutputQueue<Data> outQueue = null;

    /**
     * The input-queue where incoming commands are fetched
     * from.
     */
    private IInputQueue<Data> inQueue = null;

    /**
     * The next {@link ICommand command} that must be processed by the worker  
     */
    private Data command = null;

    /**
     * if <code>true</code> the worker was destroyed using {@link IPool#invalidateWorker(IWorker)}
     */
    public boolean destroyed = false;

    /**
     * if <code>true</code> the thread was already started with {@link Thread#start()}
     */
    protected boolean running = false;

    /**
     * if <code>true</code> the thread was terminated with {@link IWorker#terminate()}
     */
    protected boolean stopped = false;

    /**
     * if <code>true</code> the execution of the {@link #command current-command} has finished
     */
    protected boolean done = false;

    /**
     * Setter methods to set the pool
     */
    public void setPool(IPool<Data> pool) {
        this.myPool = pool;
    }

    /**
     * Setter method to set the output-queue
     */
    public void setOutQueue(IOutputQueue<Data> outQueue) {
        this.outQueue = outQueue;
    }

    /**
     * @see IWorker#setInQueue(IInputQueue)
     */
    public void setInQueue(IInputQueue<Data> inQueue) {
        this.inQueue = inQueue;
    }

    @Override
    public void run() {
        this.running = true;

        try {
            if (this.outQueue == null)
                throw new IllegalArgumentException("Output-Queue was not set properly.");

            // The thread keeps running.
            while (!this.stopped && !this.destroyed && !this.isInterrupted()) {
                if (this.done) {
                    if (this.myPool != null && !this.myPool.closed()) {
                        synchronized (this) {
                            // return thread back into pool
                            this.myPool.returnWorker(this);

                            // We are waiting for a new task now.
                            if (!this.stopped && !this.destroyed && !this.isInterrupted()) {
                                this.wait();
                            }
                        }
                    } else {
                        this.stopped = true;
                    }
                } else {
                    try {
                        // if we are in trigger mode we fetch the next command on our own
                        if (this.command == null && this.inQueue != null) {
                            this.command = this.dequeue();

                            /*
                             * If the command is null here, then it was rejected by one of the
                             * input-queue filters during dequeueing. 
                             */
                            if (this.command == null) {
                                this.logger.debug("Command was null. Maybe command was consumed by a queue filter");
                                continue;
                            }
                        } else {
                            // set threadname
                            this.setWorkerName();
                        }

                        // executing the new Command
                        this.execute(this.command);
                    } finally {
                        // write the modified command object to the out-queue
                        if (this.command != null)
                            this.outQueue.enqueue(this.command);

                        // signal that we have finished execution
                        this.done = true;

                        // free memory
                        this.reset();

                        // reset threadname
                        this.setWorkerName();
                    }
                }
            }
            this.logger.debug("Worker thread stopped from outside.");
        } catch (InterruptedException ex) {
            this.logger.debug("Worker thread interrupted from outside.");
        } catch (Throwable ex) {
            this.logger.error(String.format("Unexpected '%s' while processing command '%s'.",
                    ex.getClass().getName(), this.command), ex);
        } finally {
            if (this.myPool != null && !this.destroyed && !this.stopped)
                this.myPool.invalidateWorker(this);
        }
    }

    private Data dequeue() throws InterruptedException {
        if (this.inQueue instanceof ICommandFilterQueue) {
            ICommandFilteringContext<ICommand> filteringContext = null;
            try {
                // first step: getting the filtering context and pre-dequeue command
                filteringContext = ((ICommandFilterQueue<ICommand>) this.inQueue).getFilteringContext();
                this.command = (Data) filteringContext.getPreFilteredCmd();
            } finally {
                // notify the master that we have fetched the command
                synchronized (this) {
                    this.notify();
                }
            }

            // changing thread name
            this.setWorkerName(filteringContext.getLocation().toString());

            // second step: do the real filtering process
            return (Data) filteringContext.dequeue();
        } else {
            try {
                // fetch the next command
                return this.inQueue.dequeue();
            } finally {
                // notify the master that we have fetched the command
                synchronized (this) {
                    this.notify();
                }
            }
        }
    }

    /**
     * @see IWorker#assign(ICommand)
     */
    public void assign(Data cmd) {
        synchronized (this) {

            this.command = cmd;
            this.done = false;

            if (!this.running) {
                // if the thread is not running until yet, we need to start it now
                this.start();
            } else {
                // inform the thread about the new command
                this.notifyAll();
            }
        }
    }

    /**
     * @throws InterruptedException 
     * @see IWorker#trigger()
     */
    public void trigger() throws InterruptedException {
        if (this.inQueue == null) {
            throw new IllegalStateException("No inputqueue set on this object.");
        }

        synchronized (this) {
            this.done = false;

            /*
             * Start or notify the thread
             */
            if (!this.running) {
                // if the thread is not running until yet, we need to start it now
                this.start();
            } else {
                // inform the thread about the new command
                this.notifyAll();
            }

            // wait until the worker has dequeued the command
            this.wait(60000);
        }
    }

    /**
     * @see IWorker#getAssigned()
     */
    public Data getAssigned() {
        return this.command;
    }

    protected void setWorkerName() {
        this.setWorkerName((this.command == null) ? "" : "_" + this.command);
    }

    protected void setWorkerName(String postFix) {
        String className = this.getClass().getSimpleName();
        super.setName(String.format("%s_%s", className, postFix));
    }

    /**
     * This method must be extended by a concrete worker class
     * to free all command specific data (if any)
     */
    @OverrideMustInvoke
    protected void reset() {
        this.command = null;
    }

    /**
     * This method must be implemented by the concrete worker class
     * and contains all operations needed for command processing
     * @param cmd the command to execute
     */
    protected abstract void execute(Data cmd);

    /**
     * @see IWorker#terminate()
     */
    public void terminate() {
        if (this.stopped && !this.isAlive()) {
            // thread already stopped
            return;
        }

        // signal termination to the worker thread
        this.stopped = true;
        this.interrupt();

        // wait for the thread to finish
        // XXX: should we use a timeout here?
        try {
            this.join();
        } catch (InterruptedException e) {
            /* ignore this */}
    }

    /**
     * @see IWorker#destroy()
     */
    @Override
    public void destroy() {
        this.destroyed = true;
    }
}