content: /*
   * This library is part of OpenCms -
   * the Open Source Content Management System
   *
   * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com)
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   * Lesser General Public License for more details.
   *
   * For further information about Alkacon Software GmbH & Co. KG, please see the
   * company website: https://www.alkacon.com
   *
   * For further information about OpenCms, please see the
   * project website: https://www.opencms.org
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   *
   *
   * This library is based to some extend on code from the
   * OpenSymphony Quartz project. Original copyright notice:
   *
   * Copyright James House (c) 2001-2005
   *
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met: 1.
   * Redistributions of source code must retain the above copyright notice, this
   * list of conditions and the following disclaimer. 2. Redistributions in
   * binary form must reproduce the above copyright notice, this list of
   * conditions and the following disclaimer in the documentation and/or other
   * materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
   * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  package org.opencms.scheduler;
  
  import org.opencms.main.CmsLog;
  
  import org.apache.commons.logging.Log;
  
  import org.quartz.SchedulerConfigException;
  import org.quartz.spi.ThreadPool;
  
  /**
   * Simple thread pool used for the Quartz scheduler in OpenCms.<p>
   *
   * @since 6.0.0
   */
  public class CmsSchedulerThreadPool implements ThreadPool {
  
      /** The log object for this class. */
      private static final Log LOG = CmsLog.getLog(CmsSchedulerThreadPool.class);
  
      /** The current thread count. */
      private int m_currentThreadCount;
  
      /** The inheri group. */
      private boolean m_inheritGroup;
  
      /** The inherit loader. */
      private boolean m_inheritLoader;
  
      /** The initial thread count. */
      private int m_initialThreadCount;
  
      /** Flag indicating if the system is shutting down. */
      private boolean m_isShutdown;
  
      /** Flag indicating whether to create thread deamons. */
      private boolean m_makeThreadsDaemons;
  
      /** The max thread count. */
      private int m_maxThreadCount;
  
      /** The next runnable. */
      private Runnable m_nextRunnable;
  
      /** The next runnable lock. */
      private Object m_nextRunnableLock;
  
      /** The thread group. */
      private ThreadGroup m_threadGroup;
  
      /** The thread name prefix. */
      private String m_threadNamePrefix;
  
      /** The thread priority. */
      private int m_threadPriority;
  
      /** The threads. */
      private CmsSchedulerThread[] m_workers;
      
      private String schedulerInstanceName, schedulerInstanceId;
  
      /**
       * Create a new <code>CmsSchedulerThreadPool</code> with default values.
       *
       * This will create a pool with 0 initial and 10 maximum threads running
       * in normal priority.<p>
       *
       * @see #CmsSchedulerThreadPool(int, int, int)
       */
      public CmsSchedulerThreadPool() {
  
          this(0, 10, Thread.NORM_PRIORITY);
      }
  
      /**
       * Create a new <code>CmsSchedulerThreadPool</code> with the specified number
       * of threads that have the given priority.
       *
       * The OpenCms scheduler thread pool will initially start with provided number of
       * active scheduler threads.
       * When a thread is requested by the scheduler, and no "free" threads are available,
       * a new thread will be added to the pool and used for execution. The pool
       * will be allowed to grow until it has reached the configured number
       * of maximum threads.<p>
       *
       * @param initialThreadCount the initial number of threads for the pool
       * @param maxThreadCount maximum number of threads the pool is allowed to grow
       * @param threadPriority the thread priority for the scheduler threads
       *
       * @see java.lang.Thread
       */
      public CmsSchedulerThreadPool(int initialThreadCount, int maxThreadCount, int threadPriority) {
  
          m_inheritGroup = true;
          m_inheritLoader = true;
          m_nextRunnableLock = new Object();
          m_threadNamePrefix = "OpenCms: Scheduler Thread ";
          m_makeThreadsDaemons = true;
          m_initialThreadCount = initialThreadCount;
          m_currentThreadCount = 0;
          m_maxThreadCount = maxThreadCount;
          m_threadPriority = threadPriority;
      }
  
      /**
       * @see org.quartz.spi.ThreadPool
       *
       * @return if the pool should be blocked for available threads
       */
      public int blockForAvailableThreads() {
  
          // Waiting will be done in runInThread so we always return 1
          return 1;
      }
  
      /**
       * @see org.quartz.spi.ThreadPool#getPoolSize()
       */
      public int getPoolSize() {
  
          return m_currentThreadCount;
      }
  
      /**
       * Returns the thread priority of the threads in the scheduler pool.<p>
       *
       * @return the thread priority of the threads in the scheduler pool
       */
      public int getThreadPriority() {
  
          return m_threadPriority;
      }
  
      /**
       * @see org.quartz.spi.ThreadPool#initialize()
       */
      public void initialize() throws SchedulerConfigException {
  
          if ((m_maxThreadCount <= 0) || (m_maxThreadCount > 200)) {
              throw new SchedulerConfigException(Messages.get().getBundle().key(Messages.ERR_MAX_THREAD_COUNT_BOUNDS_0));
          }
          if ((m_initialThreadCount < 0) || (m_initialThreadCount > m_maxThreadCount)) {
              throw new SchedulerConfigException(Messages.get().getBundle().key(Messages.ERR_INIT_THREAD_COUNT_BOUNDS_0));
          }
          if ((m_threadPriority <= 0) || (m_threadPriority > 9)) {
              throw new SchedulerConfigException(
                  Messages.get().getBundle().key(Messages.ERR_SCHEDULER_PRIORITY_BOUNDS_0));
          }
  
          if (m_inheritGroup) {
              m_threadGroup = Thread.currentThread().getThreadGroup();
          } else {
              // follow the threadGroup tree to the root thread group
              m_threadGroup = Thread.currentThread().getThreadGroup();
              ThreadGroup parent = m_threadGroup;
              while (!parent.getName().equals("main")) {
                  m_threadGroup = parent;
                  parent = m_threadGroup.getParent();
              }
              m_threadGroup = new ThreadGroup(parent, this.getClass().getName());
          }
  
          if (m_inheritLoader) {
              LOG.debug(
                  Messages.get().getBundle().key(
                      Messages.LOG_USING_THREAD_CLASSLOADER_1,
                      Thread.currentThread().getName()));
          }
  
          // create the worker threads and start them
          m_workers = new CmsSchedulerThread[m_maxThreadCount];
          for (int i = 0; i < m_initialThreadCount; ++i) {
              growThreadPool();
          }
      }
  
      /**
       * Run the given <code>Runnable</code> object in the next available
       * <code>Thread</code>.<p>
       *
       * If while waiting the thread pool is asked to
       * shut down, the Runnable is executed immediately within a new additional
       * thread.<p>
       *
       * @param runnable the <code>Runnable</code> to run
       * @return true if the <code>Runnable</code> was run
       */
      public boolean runInThread(Runnable runnable) {
  
          if (runnable == null) {
              return false;
          }
  
          if (m_isShutdown) {
              LOG.debug(Messages.get().getBundle().key(Messages.LOG_THREAD_POOL_UNAVAILABLE_0));
              return false;
          }
  
          boolean hasNextRunnable;
          synchronized (m_nextRunnableLock) {
              // must synchronize here to avoid potential double checked locking
              hasNextRunnable = (m_nextRunnable != null);
          }
  
          if (hasNextRunnable || (m_currentThreadCount == 0)) {
              // try to grow the thread pool since other runnables are already waiting
              growThreadPool();
          }
  
          synchronized (m_nextRunnableLock) {
  
              // wait until a worker thread has taken the previous Runnable
              // or until the thread pool is asked to shutdown
              while ((m_nextRunnable != null) && !m_isShutdown) {
                  try {
                      m_nextRunnableLock.wait(1000);
                  } catch (InterruptedException e) {
                      // can be ignores
                  }
              }
  
              // during normal operation, not shutdown, set the nextRunnable
              // and notify the worker threads waiting (getNextRunnable())
              if (!m_isShutdown) {
                  m_nextRunnable = runnable;
                  m_nextRunnableLock.notifyAll();
              }
          }
  
          // if the thread pool is going down, execute the Runnable
          // within a new additional worker thread (no thread from the pool)
          // note: the synchronized section should be as short (time) as
          // possible as starting a new thread is not a quick action
          if (m_isShutdown) {
              CmsSchedulerThread thread = new CmsSchedulerThread(
                  this,
                  m_threadGroup,
                  m_threadNamePrefix + "(final)",
                  m_threadPriority,
                  false,
                  runnable);
              thread.start();
          }
  
          return true;
      }
  
      /**
       * Terminate any worker threads in this thread group.<p>
       *
       * Jobs currently in progress will be allowed to complete.<p>
       */
      public void shutdown() {
  
          shutdown(true);
      }
  
      /**
       * Terminate all threads in this thread group.<p>
       *
       * @param waitForJobsToComplete if true,, all current jobs will be allowed to complete
       */
      public void shutdown(boolean waitForJobsToComplete) {
  
          m_isShutdown = true;
  
          // signal each scheduler thread to shut down
          for (int i = 0; i < m_currentThreadCount; i++) {
              if (m_workers[i] != null) {
                  m_workers[i].shutdown();
              }
          }
  
          // give waiting (wait(1000)) worker threads a chance to shut down
          // active worker threads will shut down after finishing their
          // current job
          synchronized (m_nextRunnableLock) {
              m_nextRunnableLock.notifyAll();
          }
  
          if (waitForJobsToComplete) {
              // wait until all worker threads are shut down
              int alive = m_currentThreadCount;
              while (alive > 0) {
                  alive = 0;
                  for (int i = 0; i < m_currentThreadCount; i++) {
                      if (m_workers[i].isAlive()) {
                          try {
                              if (LOG.isDebugEnabled()) {
                                  LOG.debug(
                                      Messages.get().getBundle().key(Messages.LOG_THREAD_POOL_WAITING_1, Integer.valueOf(i)));
                              }
  
                              // note: with waiting infinite - join(0) - the application
                              // may appear to 'hang'
                              // waiting for a finite time however requires an additional loop (alive)
                              alive++;
                              m_workers[i].join(200);
                          } catch (InterruptedException e) {
                              // can be ignored
                          }
                      }
                  }
              }
  
              int activeCount = m_threadGroup.activeCount();
              if ((activeCount > 0) && LOG.isInfoEnabled()) {
                  LOG.info(
                      Messages.get().getBundle().key(Messages.LOG_THREAD_POOL_STILL_ACTIVE_1, Integer.valueOf(activeCount)));
              }
              if (LOG.isDebugEnabled()) {
                  LOG.debug(Messages.get().getBundle().key(Messages.LOG_THREAD_POOL_SHUTDOWN_0));
              }
          }
      }
  
      /**
       * Dequeue the next pending <code>Runnable</code>.<p>
       *
       * @return the next pending <code>Runnable</code>
       * @throws InterruptedException if something goes wrong
       */
      protected Runnable getNextRunnable() throws InterruptedException {
  
          Runnable toRun = null;
  
          // Wait for new Runnable (see runInThread()) and notify runInThread()
          // in case the next Runnable is already waiting.
          synchronized (m_nextRunnableLock) {
              if (m_nextRunnable == null) {
                  m_nextRunnableLock.wait(1000);
              }
  
              if (m_nextRunnable != null) {
                  toRun = m_nextRunnable;
                  m_nextRunnable = null;
                  m_nextRunnableLock.notifyAll();
              }
          }
  
          return toRun;
      }
  
      /**
       * Grows the thread pool by one new thread if the maximum pool size
       * has not been reached.<p>
       */
      private void growThreadPool() {
  
          if (m_currentThreadCount < m_maxThreadCount) {
              // if maximum number is not reached grow the thread pool
              synchronized (m_nextRunnableLock) {
                  m_workers[m_currentThreadCount] = new CmsSchedulerThread(
                      this,
                      m_threadGroup,
                      m_threadNamePrefix + m_currentThreadCount,
                      m_threadPriority,
                      m_makeThreadsDaemons);
                  m_workers[m_currentThreadCount].start();
                  if (m_inheritLoader) {
                      m_workers[m_currentThreadCount].setContextClassLoader(
                          Thread.currentThread().getContextClassLoader());
                  }
                  // increas the current size
                  m_currentThreadCount++;
                  // notify the waiting threads
                  m_nextRunnableLock.notifyAll();
              }
          }
      }
  
      public void setInstanceId(String schedInstId) {
  
          schedulerInstanceId = schedInstId;
      }
  
      public void setInstanceName(String schedName) {
  
          schedulerInstanceName = schedName;
      }
  }