<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<chapter id="scheduled-jobs">
  <title>TorqueBox Scheduled Jobs</title>

  <section id="what-are-scheduled-jobs">
    <title>What Are Scheduled Jobs?</title>

    <para>Scheduled jobs are simply components that execute on a
    possibly-recurring schedule instead of in response to user interaction.
    Scheduled jobs fire asynchronously, outside of the normal web-browser
    thread-of-control. Scheduled jobs have full access to the entire Ruby
    environment. This allows them to interact with database models and other
    application functionality.</para>
  </section>

  <section id="ruby-job-classes">
    <title>Ruby Job Classes</title>

    <para>Each scheduled job maps to exactly one Ruby class. The path and
    filename should match the class name of the job contained in the
    file.</para>

    <informaltable>
      <tgroup cols="2">
        <thead>
          <row>
            <entry align="center">File name</entry>

            <entry align="center">Class name</entry>
          </row>
        </thead>

        <tbody>
          <row>
            <entry><filename>mail_notifier.rb</filename></entry>

            <entry><type>MailNotifier</type></entry>
          </row>

          <row>
            <entry><filename>mail/notifier.rb</filename></entry>

            <entry><type>Mail::Notifier</type></entry>
          </row>
        </tbody>
      </tgroup>
    </informaltable>

    <para><example>
        <title>Skeleton scheduled job class
        (<filename>mail/notifier.rb</filename>)</title>

        <para><programlisting>module Mail
  class Notifier

    # implementation goes here

  end
end</programlisting></para>
      </example>Each job class should implement a no-argument
    <methodname>run()</methodname> method to perform the work when
    fired. The class may optionally implement a one-argument
    <methodname>on_error(exception)</methodname> method to handle
    any errors raised during the job's execution.</para>

    <para><example>
        <title>Scheduled job implementation
        (<filename>mail/notifier.rb</filename>)</title>

        <para><programlisting>module Mail
  class Notifier

    # optional, only needed if you pass config options to the job
    def initialize(options = {})
      @options = options
    end

    def run()
      # perform work here
    end

    def on_error(exception)
      # Optionally implement this method to interrogate any exceptions
      # raised inside the job's run method.
    end

  end
end</programlisting></para>
      </example></para>

    <para>From within the class's <methodname>run()</methodname> method, the
    full application environment is available.</para>
  </section>

  <section id="scheduling-jobs">
    <title>Scheduling Jobs</title>

    <para>The job schedule defines the time(s) that a job should execute. This
    may be defined to be single point in time, or more often, as recurring
    event. The job schedule is defined in your deployment descriptor.</para>

    <section id="jobs-format">
      <title>Configuration Format</title>

      <para>Within the internal <filename>torquebox.yml</filename>
      descriptor (or through an external
      <filename><replaceable>*</replaceable>-knob.yml</filename>
      descriptor), scheduled jobs are configured using the
      <parameter>jobs:</parameter> section, in which a block of
      information is provided for each job. The block starts with an
      arbitrary name for the job. Each block must also define the job
      class and the schedule specification. Optionally a
      <parameter>timeout</parameter>, a <parameter>description</parameter>, and a
      <parameter>config</parameter> may be provided. Providing a 
      <parameter>timeout</parameter> will cause the job to be interrupted
      if it runs beyond the timeout period (see <xref linkend="jobs-timeout" />).
      If you provide
      a <parameter>config</parameter>, its value will be passed to the
      initialize method of the job class.</para>

      <para>If you are using the DSL (via <filename>torquebox.rb</filename>) in
      your internal descriptor, each job is defined using the <methodname>job</methodname>
      directive, with very similar options to the YAML syntax described above. 
      The DSL does not require a name for each job, unless you intend to share
      a job class across multiple jobs.</para>

      <para><example>
          <title>Example deployment descriptor</title>

          <para>Using the YAML syntax:<programlisting>application:
  ..
jobs:
  mail.notifier:
    job: Mail::Notifier
    cron: '0 */5 * * * ?'
    timeout: 50000 ms
    description: Deliver queued mail notifications
    config: 
      foo: bar</programlisting></para>

          <para>And via the DSL:<programlisting>TorqueBox.configure do
  ...
  job Mail::Notifier do
    name 'mail.notifier' # optional, unless the job class is used by multiple jobs
    cron '0 */5 * * * ?'
    timeout '5s'
    description 'Deliver queued mail notifications' # optional
    config do
      foo 'bar'
    end
  end
end</programlisting></para>
        </example></para>

      <para>The cron attribute should contain a typical crontab-like entry. It
      is composed of 7 fields (6 are required).</para>

      <para><informaltable>
          <tgroup cols="7">
            <thead>
              <row>
                <entry align="center">Seconds</entry>

                <entry align="center">Minutes</entry>

                <entry align="center">Hours</entry>

                <entry align="center">Day of Month</entry>

                <entry align="center">Month</entry>

                <entry align="center">Day of Week</entry>

                <entry align="center">Year</entry>
              </row>
            </thead>

            <tbody>
              <row>
                <entry>0-59</entry>

                <entry>0-59</entry>

                <entry>0-23</entry>

                <entry>1-31</entry>

                <entry>1-12 or JAN-DEC</entry>

                <entry>1-7 or SUN-SAT</entry>

                <entry>1970-2099 (optional)</entry>
              </row>
            </tbody>
          </tgroup>
        </informaltable></para>

      <para>For several fields, you may denote subdivision by using the
      forward-slash (<code>/</code>) character. To execute a job every 5
      minutes, <code>*/5</code> in the minutes field would specify this
      condition.</para>

      <para>Spans may be indicated using the dash (<code>-</code>) character.
      To execute a job Monday through Friday, <code>MON-FRI</code> should be
      used in the day-of-week field.</para>

      <para>Multiple values may be separated using the comma (<code>,</code>)
      character. The specification of <code>1,15</code> in the day-of-month
      field would result in the job firing on the 1st and 15th of each
      month.</para>

      <para>Either day-of-month or day-of-week must be specified using the
      <code>?</code> character, since specifying both is contradictory.</para>

    </section>
    <section id="jobs-timeout">
      <title>Timing Out Jobs</title>

      <para>To keep jobs from running too long, you can set a 
      <parameter>timeout</parameter> setting for the job. The format of 
      the timeout is a integer followed by an optional time unit.
      The available time units are:</para>

      <itemizedlist spacing="compact">
        <listitem>
          <para><literal>ms</literal> - milliseconds</para> 
        </listitem>

        <listitem>
          <para><literal>s</literal> - seconds</para> 
        </listitem>

        <listitem>
          <para><literal>m</literal> - minutes</para> 
        </listitem>

        <listitem>
          <para><literal>h</literal> - hours</para> 
        </listitem>

    </itemizedlist>

    <para>If no unit is provided, seconds are assumed.</para>

    <para>In addition to specifying a timeout parameter, you will also have
    to implement a <methodname>on_timeout</methodname> method on your job class 
    that will be called when the timeout occurs. This method is responsible
    for actually shutting down the job - TorqueBox will not kill the job
    when the timeout occurs. One approach would be for your job to periodically
    check a flag while processing, with <methodname>on_timeout</methodname> setting
    that flag when called.</para>

<para><example>
        <title>Job with timeout
        (<filename>mail/notifier.rb</filename>)</title>

        <para><programlisting>module Mail
  class Notifier

    # optional, only needed if you pass config options to the job
    def initialize(options = {})
      @options = options
      @timeout = false
    end

    def run()
      notification_list.each do |n|
        raise 'Timeout!' if @timeout
        n.notify
      end
    end

    def on_timeout
      @timeout = true
    end

  end
end</programlisting></para>
      </example></para>

    </section>

    <section id="jobs-concurrency">
      <title>Job Concurrency</title>

      <para>Quartz manages its own thread pool for running jobs. By default,
      this pool contains three threads. If you have more than three jobs executing
      at the same time, you may want to increase this pool size. You can do so via
      the <parameter>concurrency</parameter> setting.</para>

      <para><example>
          <title>Setting job concurrency</title>

          <para>Using the YAML syntax:<programlisting>application:
  ..
jobs:
  concurrency: 10
  mail.notifier:
    job: Mail::Notifier
    cron: '0 */5 * * * ?'</programlisting></para>

          <para>And via the DSL:<programlisting>TorqueBox.configure do
  ...
  options_for :jobs, :concurrency => 10

  job Mail::Notifier do
    name 'mail.notifier' # optional, unless the job class is used by multiple jobs
    cron '0 */5 * * * ?'
  end
end</programlisting></para>
        </example></para>
        
        <para>Note that if you are using a bounded runtime pool for the jobs
        subsystem that is smaller than the concurrency setting, your
        available concurrency will be limited to the pool size. See <xref
        linkend="pooling" /> for more details.</para>
    </section>

  </section>

  <section id="clustered-jobs">
    <title>Clustered Jobs</title>

    <section id="ha-singleton-jobs">
      <title>High Availability Singleton Jobs</title>

      <para>TorqueBox supports highly-available singleton jobs. By
      default, a job only runs on one node in the cluster and if that
      node goes down or the job fails to run to completion, it is
      automatically scheduled on a new node.</para>

      <para>To use high availability singleton jobs, you must
      start TorqueBox with a clustered configuration. For example:</para>

      <para><screen> <prompt>$</prompt> <command>$JBOSS_HOME/bin/standalone.sh --server-config=standalone-ha.xml</command></screen></para>

      <para>
        Alternatively, use the <command>torquebox</command> command:
        <screen> <prompt>$</prompt> <command>torquebox run --clustered</command></screen>
      </para>

      <para>HA jobs are configured using the <code>singleton</code>
      key in the job specification in your deployment descriptor. Its
      default value is <code>true</code> so you must manually
      configure it with a value of <code>false</code> for the job to
      run on every node in the cluster.</para>

      <para><example>
          <title>Example deployment descriptor</title>

          <para>Using the YAML syntax:<programlisting>application:
  ..
jobs:
  mail.notifier:
    job:         Mail::Notifier
    cron:        '0 */5 * * * ?'
    description: Deliver queued mail notifications
    singleton: true
    config: 
      foo: bar</programlisting></para>

         <para>And via the DSL:<programlisting>TorqueBox.configure do
  ...
  job Mail::Notifier do
    name 'mail.notifier' # optional, unless the job class is used by multiple jobs
    cron '0 */5 * * * ?'
    description 'Deliver queued mail notifications' # optional
    singleton true
    config do
      foo 'bar'
    end
  end
end</programlisting>This is the same
        deployment descriptor from the example above.
        Including the <code>singleton</code> attribute with a value
        of <code>true</code> is redundant of course, since jobs will
        only run on a single node when clustered, by default.</para>
      </example></para>
    </section>

    <section id="jobs-running-on-every-node">
      <title>Jobs Running on Every Node</title>

      <para>To configure a job to run on every node in a cluster, set
      <code>singleton</code> to <code>false</code> in the deployment
      descriptor.</para>
    </section>

  </section>

  <section id="resouce-injection-with-jobs">
    <title>Resource Injection with Jobs</title>

    <para>If a job requires access to other resources, such as messaging
    topics and queues, or Java CDI components these should be injected using
    the resource injection facilities provided by TorqueBox (see <xref
    linkend="resource-injection" />).</para>

    <para>In order for resource injection to function with scheduled jobs,
    they must reside either at the root of your application directory (typical
    for simple Rack applications), or underneath an
    <filename>app/jobs/</filename> directory. If you place the job anywhere
    else, it may still function, but resources injection will not be
    available.</para>
  </section>
</chapter>
