/**
 *
 * Copyright 2003-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.activemq.work;

import EDU.oswego.cs.dl.util.concurrent.Executor;
import org.activemq.work.pool.NullWorkExecutorPool;
import org.activemq.work.pool.ScheduleWorkExecutor;
import org.activemq.work.pool.StartWorkExecutor;
import org.activemq.work.pool.SyncWorkExecutor;
import org.activemq.work.pool.WorkExecutor;
import org.activemq.work.pool.WorkExecutorPool;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.DisposableBean;

import javax.resource.spi.XATerminator;
import javax.resource.spi.work.ExecutionContext;
import javax.resource.spi.work.Work;
import javax.resource.spi.work.WorkCompletedException;
import javax.resource.spi.work.WorkException;
import javax.resource.spi.work.WorkListener;
import javax.resource.spi.work.WorkManager;

/**
 * WorkManager implementation which uses under the cover three WorkExecutorPool
 * - one for each synchronization policy - in order to dispatch the submitted
 * Work instances.
 * <P>
 * A WorkManager is a component of the JCA specifications, which allows a
 * Resource Adapter to submit tasks to an Application Server for execution.
 * <p/>
 * This code is based on the Geronimo WorkManager implementation but copied here
 * to avoid dependencies on Geronimo and GBeans etc.
 *
 * @version $Revision: 1.1.1.1 $ $Date: 2005/03/11 21:14:09 $
 */
public class SpringWorkManager implements WorkManager, InitializingBean, DisposableBean {

    private final static int DEFAULT_POOL_SIZE = 10;

    /**
     * Pool of threads used by this WorkManager in order to process
     * the Work instances submitted via the doWork methods.
     */
    private WorkExecutorPool syncWorkExecutorPool;

    /**
     * Pool of threads used by this WorkManager in order to process
     * the Work instances submitted via the startWork methods.
     */
    private WorkExecutorPool startWorkExecutorPool;

    /**
     * Pool of threads used by this WorkManager in order to process
     * the Work instances submitted via the scheduleWork methods.
     */
    private WorkExecutorPool scheduledWorkExecutorPool;

    private final XAWork xaServices;

    private final WorkExecutor scheduleWorkExecutor = new ScheduleWorkExecutor();
    private final WorkExecutor startWorkExecutor = new StartWorkExecutor();
    private final WorkExecutor syncWorkExecutor = new SyncWorkExecutor();

    /**
     * Create a WorkManager.
     */
    public SpringWorkManager() {
        this(DEFAULT_POOL_SIZE, null);
    }

    public SpringWorkManager(int size, XAWork xaServices) {
        this(size, size, size, xaServices);
    }

    public SpringWorkManager(int syncSize, int startSize, int schedSize, XAWork xaServices) {
        syncWorkExecutorPool = new NullWorkExecutorPool(syncSize);
        startWorkExecutorPool = new NullWorkExecutorPool(startSize);
        scheduledWorkExecutorPool = new NullWorkExecutorPool(schedSize);
        this.xaServices = xaServices;
    }

    public void afterPropertiesSet() throws Exception {        
        syncWorkExecutorPool = syncWorkExecutorPool.start();
        startWorkExecutorPool = startWorkExecutorPool.start();
        scheduledWorkExecutorPool = scheduledWorkExecutorPool.start();
    }

    public void destroy() throws Exception {
        syncWorkExecutorPool = syncWorkExecutorPool.stop();
        startWorkExecutorPool = startWorkExecutorPool.stop();
        scheduledWorkExecutorPool = scheduledWorkExecutorPool.stop();
    }

    public XATerminator getXATerminator() {
        return xaServices;
    }

    public int getSyncThreadCount() {
        return syncWorkExecutorPool.getPoolSize();
    }

    public int getSyncMaximumPoolSize() {
        return syncWorkExecutorPool.getMaximumPoolSize();
    }

    public void setSyncMaximumPoolSize(int maxSize) {
        syncWorkExecutorPool.setMaximumPoolSize(maxSize);
    }

    public int getStartThreadCount() {
        return startWorkExecutorPool.getPoolSize();
    }

    public int getStartMaximumPoolSize() {
        return startWorkExecutorPool.getMaximumPoolSize();
    }

    public void setStartMaximumPoolSize(int maxSize) {
        startWorkExecutorPool.setMaximumPoolSize(maxSize);
    }

    public int getScheduledThreadCount() {
        return scheduledWorkExecutorPool.getPoolSize();
    }

    public int getScheduledMaximumPoolSize() {
        return scheduledWorkExecutorPool.getMaximumPoolSize();
    }

    public void setScheduledMaximumPoolSize(int maxSize) {
        scheduledWorkExecutorPool.setMaximumPoolSize(maxSize);
    }

    /* (non-Javadoc)
     * @see javax.resource.spi.work.WorkManager#doWork(javax.resource.spi.work.Work)
     */
    public void doWork(Work work) throws WorkException {
        executeWork(new WorkerContext(work), syncWorkExecutor, syncWorkExecutorPool);
    }

    /* (non-Javadoc)
     * @see javax.resource.spi.work.WorkManager#doWork(javax.resource.spi.work.Work, long, javax.resource.spi.work.ExecutionContext, javax.resource.spi.work.WorkListener)
     */
    public void doWork(Work work,
                       long startTimeout,
                       ExecutionContext execContext,
                       WorkListener workListener)
            throws WorkException {
        WorkerContext workWrapper =
                new WorkerContext(work, startTimeout, execContext, xaServices, workListener);
        workWrapper.setThreadPriority(Thread.currentThread().getPriority());
        executeWork(workWrapper, syncWorkExecutor, syncWorkExecutorPool);
    }

    /* (non-Javadoc)
     * @see javax.resource.spi.work.WorkManager#startWork(javax.resource.spi.work.Work)
     */
    public long startWork(Work work) throws WorkException {
        WorkerContext workWrapper = new WorkerContext(work);
        workWrapper.setThreadPriority(Thread.currentThread().getPriority());
        executeWork(workWrapper, startWorkExecutor, startWorkExecutorPool);
        return System.currentTimeMillis() - workWrapper.getAcceptedTime();
    }

    /* (non-Javadoc)
     * @see javax.resource.spi.work.WorkManager#startWork(javax.resource.spi.work.Work, long, javax.resource.spi.work.ExecutionContext, javax.resource.spi.work.WorkListener)
     */
    public long startWork(Work work,
                          long startTimeout,
                          ExecutionContext execContext,
                          WorkListener workListener)
            throws WorkException {
        WorkerContext workWrapper =
                new WorkerContext(work, startTimeout, execContext, xaServices, workListener);
        workWrapper.setThreadPriority(Thread.currentThread().getPriority());
        executeWork(workWrapper, startWorkExecutor, startWorkExecutorPool);
        return System.currentTimeMillis() - workWrapper.getAcceptedTime();
    }

    /* (non-Javadoc)
     * @see javax.resource.spi.work.WorkManager#scheduleWork(javax.resource.spi.work.Work)
     */
    public void scheduleWork(Work work) throws WorkException {
        WorkerContext workWrapper = new WorkerContext(work);
        workWrapper.setThreadPriority(Thread.currentThread().getPriority());
        executeWork(workWrapper, scheduleWorkExecutor, scheduledWorkExecutorPool);
    }

    /* (non-Javadoc)
     * @see javax.resource.spi.work.WorkManager#scheduleWork(javax.resource.spi.work.Work, long, javax.resource.spi.work.ExecutionContext, javax.resource.spi.work.WorkListener)
     */
    public void scheduleWork(Work work,
                             long startTimeout,
                             ExecutionContext execContext,
                             WorkListener workListener)
            throws WorkException {
        WorkerContext workWrapper =
                new WorkerContext(work, startTimeout, execContext, xaServices, workListener);
        workWrapper.setThreadPriority(Thread.currentThread().getPriority());
        executeWork(workWrapper, scheduleWorkExecutor, scheduledWorkExecutorPool);
    }

    /**
     * Execute the specified Work.
     *
     * @param work Work to be executed.
     * @throws WorkException Indicates that the Work execution has been
     *                       unsuccessful.
     */
    private void executeWork(WorkerContext work, WorkExecutor workExecutor, Executor pooledExecutor) throws WorkException {
        work.workAccepted(this);
        try {
            workExecutor.doExecute(work, pooledExecutor);
            WorkException exception = work.getWorkException();
            if (null != exception) {
                throw exception;
            }
        }
        catch (InterruptedException e) {
            WorkCompletedException wcj = new WorkCompletedException("The execution has been interrupted.", e);
            wcj.setErrorCode(WorkException.INTERNAL);
            throw wcj;
        }
    }

}
