org.carewebframework.ui.thread.ZKThread.java Source code

Java tutorial

Introduction

Here is the source code for org.carewebframework.ui.thread.ZKThread.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
 * If a copy of the MPL was not distributed with this file, You can obtain one at
 * http://mozilla.org/MPL/2.0/.
 * 
 * This Source Code Form is also subject to the terms of the Health-Related Additional
 * Disclaimer of Warranty and Limitation of Liability available at
 * http://www.carewebframework.org/licensing/disclaimer.
 */
package org.carewebframework.ui.thread;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.carewebframework.api.thread.ThreadUtil;
import org.carewebframework.ui.FrameworkWebSupport;

import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;

/**
 * Used to run long operations in the background. Uses ZK events to notify the requester of
 * completion.
 * <p>
 * <i>Note: reference {@link #getException()} to evaluate any exception the ZKRunnable target may
 * have thrown. You may also invoke {@link #rethrow()}. One of these methods should be referenced to
 * ensure that the task successfully completed.</i>
 * </p>
 */
public class ZKThread {

    private static final Log log = LogFactory.getLog(ZKThread.class);

    /**
     * Adds an abort method to the traditional Runnable interface. The target is responsible for
     * implementing this method so as to terminate its operation ASAP when this is called.
     */
    public interface ZKRunnable {

        /**
         * Run method
         * 
         * @param thread The thread.
         * @throws Exception Unspecified exception.
         */
        void run(ZKThread thread) throws Exception;

        /**
         * Auto generated method comment
         */
        void abort();
    }

    /**
     * Subclasses the Thread class, notifying the requester when the target operation completes.
     */
    private class ThreadEx extends Thread implements EventListener<Event> {

        /**
         * Executes the target operation with timings.
         */
        @Override
        public void run() {
            associateThread(desktop, requestAttributes);
            watch.start();

            try {
                target.run(ZKThread.this);
            } catch (final Throwable e) {
                exception = e;
            }
            watch.stop();
            disassociateThread();
            done();
        }

        /**
         * This entry point receives the notification on the desktop's event thread that the
         * background thread has completed. Posts an event (on the desktop's event thread) to the
         * requester that the operation has completed. The data associated with the event is a
         * reference to this ZKThread object and may be interrogated for the outcome of the
         * operation.
         */
        @Override
        public void onEvent(Event event) {
            Events.postEvent(event);
        }

        /**
         * Invoked by the background thread when it has completed the target operation, even if
         * aborted. This schedules a notification on the desktop's event thread where the requester
         * is notified of the completion.
         */
        private void done() {
            try {
                Executions.schedule(desktop, this, event);
            } catch (final Exception e) {
                log.error(e);
            }
        }

    }

    private boolean aborted;

    private Throwable exception;

    private final StopWatch watch = new StopWatch();

    private final ZKRunnable target;

    private final Event event;

    private final Desktop desktop;

    private final ThreadEx thread;

    private final RequestAttributes requestAttributes;

    private final Map<String, Object> attribute = new HashMap<String, Object>();

    /**
     * Associates a desktop and request attributes with a background thread and notifies any thread
     * listeners.
     * 
     * @param desktop The desktop to associate.
     * @param requestAttributes The Spring request attributes to associate.
     */
    public static void associateThread(final Desktop desktop, final RequestAttributes requestAttributes) {
        RequestContextHolder.setRequestAttributes(requestAttributes, true);
        FrameworkWebSupport.associateDesktop(desktop);
        ThreadListenerRegistry.notifyListeners(true);
    }

    /**
     * Disassociates a desktop and request attributes from a background thread and notifies any
     * thread listeners.
     */
    public static void disassociateThread() {
        ThreadListenerRegistry.notifyListeners(false);
        FrameworkWebSupport.associateDesktop(null);
        RequestContextHolder.setRequestAttributes(null);
    }

    /**
     * Creates a thread for executing a background operation.
     * 
     * @param target Target operation.
     * @param requester ZK component requesting the operation.
     * @param eventName Name of the event used to notify requester of completion. When fired, the
     *            data associated with the event will be a reference to this instance and may be
     *            interrogated to determine the outcome of the operation.
     */
    public ZKThread(final ZKRunnable target, final Component requester, final String eventName) {
        super();
        this.target = target;
        this.event = new Event(eventName, requester, this);
        this.desktop = requester.getDesktop();
        this.requestAttributes = RequestContextHolder.getRequestAttributes();
        this.thread = new ThreadEx();
    }

    /**
     * Starts the background thread.
     */
    public void start() {
        final String targetName = target.getClass().getName();
        log.debug("Executing ZKThread [target=" + targetName + "]");
        ThreadUtil.startThread(this.thread);
    }

    /**
     * Request that the thread abort and notify the target.
     */
    public void abort() {
        aborted = true;

        if (thread.isAlive()) {
            target.abort();
        }
    }

    /**
     * Returns true if the thread execution was aborted.
     * 
     * @return boolean true if aborted
     */
    public boolean isAborted() {
        return aborted;
    }

    /**
     * Returns the execution time in milliseconds of the thread.
     * 
     * @return long elapsed
     */
    public long getElapsed() {
        return watch.getTime();
    }

    /**
     * Returns the named attribute associated with this thread object.
     * 
     * @param name Name of the attribute.
     * @return Value of the attribute, or null if not found.
     */
    public Object getAttribute(final String name) {
        synchronized (attribute) {
            return attribute.get(name);
        }
    }

    /**
     * Sets the named attribute to the specified value.
     * 
     * @param name Name of the attribute.
     * @param value Value to associate with the attribute.
     */
    public void setAttribute(final String name, final Object value) {
        synchronized (attribute) {
            attribute.put(name, value);
        }
    }

    /**
     * Returns the exception thrown by the background thread, or null if there was none.
     * 
     * @return Throwable
     */
    public Throwable getException() {
        return exception;
    }

    /**
     * Throws the saved exception in the current thread. If there is no saved exception, no action
     * is taken.
     * 
     * @throws Throwable when exception was thrown via ZKRunnable target
     */
    public void rethrow() throws Throwable {
        if (exception != null) {
            throw exception;
        }
    }

}