/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.ode.bpel.iapi;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;

/**
 * The BPEL scheduler.
 */
public interface Scheduler {
    void setJobProcessor(JobProcessor processor) throws ContextException;
    
    void setPolledRunnableProcesser(JobProcessor polledRunnableProcessor);
    
    /**
     * Schedule a persisted job. Persisted jobs MUST survive system failure.
     * They also must not be scheduled unless the transaction associated with
     * the calling thread commits.
     * @param jobDetail information about the job
     * @param when when the job should run (<code>null</code> means now)
     * @return unique job identifier
     */
    String schedulePersistedJob(Map<String,Object>jobDetail,Date when)
            throws ContextException ;


    /**
     * Schedule a Runnable that will be executed on a dedicated thread pool.
     * @param runnable
     * @param when
     * @return
     * @throws ContextException
     */
    String scheduleMapSerializableRunnable(MapSerializableRunnable runnable, Date when) throws ContextException;

    /**
     * Schedule a volatile (non-persisted) job. Volatile jobs should not be
     * saved in the database and should not survive system crash. Volatile
     * jobs scheduled from a transactional context should be scheduled
     * regardless of whether the transaction commits.
     *
     * @param transacted should the job be executed in a transaction?
     * @param jobDetail information about the job
     * @param when does the job should be executed?
     * @return unique (as far as the scheduler is concerned) job identifier
     */
    String scheduleVolatileJob(boolean transacted, Map<String,Object> jobDetail, Date when) throws ContextException;

    /**
     * Schedule a volatile job for right now
     * @see #scheduleVolatileJob(boolean, java.util.Map, java.util.Date)
     */
    String scheduleVolatileJob(boolean transacted, Map<String,Object> jobDetail) throws ContextException;

    /**
     * Make a good effort to cancel the job. If its already running no big
     * deal.
     * @param jobId job identifier of the job
     */
    void cancelJob(String jobId) throws ContextException;

    /**
     * Execute a {@link Callable} in a transactional context. If the callable
     * throws an exception, then the transaction will be rolled back, otherwise
     * the transaction will commit.
     *
     * @param <T> return type
     * @param transaction transaction to execute
     * @return result
     * @throws Exception
     */
    <T> T execTransaction(Callable<T> transaction)
            throws Exception, ContextException;

    /**
     * Execute a {@link Callable} in a transactional context. If the callable
     * throws an exception, then the transaction will be rolled back, otherwise
     * the transaction will commit. Also, modify the value of the timeout value 
     * that is associated with the transactions started by the current thread. 
     *
     * @param <T> return type
     * @param transaction transaction to execute
     * @param timeout, The value of the timeout in seconds. If the value is zero, the transaction service uses the default value.
     * @return result
     * @throws Exception
     */
    <T> T execTransaction(Callable<T> transaction, int timeout)
            throws Exception, ContextException;
    
    void setRollbackOnly() throws Exception;

    /**
     * Same as execTransaction but executes in a different thread to guarantee
     * isolation from the main execution thread.
     * @param transaction
     * @return
     * @throws Exception
     * @throws ContextException
     */
    <T> Future<T> execIsolatedTransaction(final Callable<T> transaction)
            throws Exception, ContextException;

    /**
     * @return true if the current thread is associated with a transaction.
     */
    boolean isTransacted();

    /**
     * Register a transaction synchronizer.
     * @param synch synchronizer
     * @throws ContextException
     */
    void registerSynchronizer(Synchronizer synch) throws ContextException;

    void start();

    void stop();

    void shutdown();

    public interface Synchronizer {
        /**
         * Called after the transaction is completed.
         * @param success indicates whether the transaction was comitted
         */
        void afterCompletion(boolean success);

        /**
         * Called before the transaction is completed.
         */
        void beforeCompletion();
    }

    /**
     * Interface implemented by the object responsible for job execution.
     * @author mszefler
     */
    public interface JobProcessor {
         /**
          * Implements execution of the job.
          * @param jobInfo the job information
          * @throws JobProcessorException
          */
        void onScheduledJob(JobInfo jobInfo) throws JobProcessorException;
    }

    /**
     * Wrapper containing information about a scheduled job.
     * @author mszefler
     */
    public static class JobInfo implements Serializable {
        private static final long serialVersionUID = 1L;
        public final String jobName;
        public final int retryCount;
        public final Map<String,Object> jobDetail;

        public JobInfo(String jobName, Map<String,Object>jobDetail, int retryCount) {
            this.jobName = jobName;
            this.jobDetail = jobDetail;
            this.retryCount = retryCount;
        }

        public String toString() {
            // Wrap in hashmap in case the underlying object has no toString method.
            return jobName + "["+retryCount +"]: " + new HashMap<Object, Object>(jobDetail);
        }
    }

    /**
     * Exception thrown by the {@link JobProcessor} to indicate failure in job
     * processing.
     * @author mszefler
     */
    public class JobProcessorException extends Exception {
        private static final long serialVersionUID = 1L;
        public final boolean retry;

        public JobProcessorException(boolean retry) {
            this.retry = retry;
        }

        public JobProcessorException(Throwable cause, boolean retry) {
            super(cause);
            this.retry = retry;
        }
    }

    public interface MapSerializableRunnable extends Runnable, Serializable {
        void storeToDetailsMap(Map<String, Object> details);
        void restoreFromDetailsMap(Map<String, Object> details);
    }
}