X

It's All About the Platform.

Java Cloud Service: Scheduling - Quartz

The Java Cloud Service (JCS) provides a platform to develop and deploy
business applications in the cloud. In Fusion Applications Cloud
deployments customers do not have the option to deploy custom
applications developed with JDeveloper to ensure the integrity and
supportability of the hosted application service. Instead the custom
applications can be deployed to the JCS and integrated to the Fusion
Application Cloud instance.

This series of articles will go
through the features of JCS, provide end-to-end examples on how to
develop and deploy applications on JCS and how to integrate them with
the Fusion Applications instance.

Previous articles covered ADF
web application; next we cover options to implement scheduling in JCS.
Sometimes there are requirements for the ability to schedule large
transactions to run at a future time or automate the running of
an application based on a defined schedule. In Fusion Applications these
types of requirements are met with Oracle Enterprise Scheduler (ESS), unfortunately ESS is not available in JCS.

In previous article we covered Timer and Worker API and Timer Service, this article covers popular scheduler library Quartz. In future articles other options will be covered.







 

Pre-requisites

Access to Cloud instance

In order to deploy the
application access to a JCS instance is needed, a free trial JCS instance can
be obtained from
Oracle Cloud site. To register you will need a credit card even if the credit card
will not be charged. To register simply click "Try it" and choose the
"Java" option. The confirmation email will contain the connection
details. See this video
for example of the registration.

Once the request is
processed you will be assigned 2 service instances; Java and Database. Applications
deployed to the JCS must use Oracle Database Cloud Service as their underlying
database. So when JCS instance is created a database instance is associated
with it using a JDBC data source.

The cloud services can
be monitored and managed through the web UI. For details refer to Getting
Started with Oracle Cloud
.

JDeveloper

JDeveloper contains
Cloud specific features related to e.g. connection and deployment. To use these
features download the JDeveloper from JDeveloper
download site
by clicking the “Download JDeveloper 11.1.1.7.1 for ADF
deployment on Oracle Cloud” link, this version of JDeveloper will have the JCS
integration features that will be used in this article. For versions that do not include the Cloud
integration features the Oracle Java Cloud Service SDK
or the JCS Java
Console can be used for deployment.

For details on
installing and configuring the JDeveloper refer to the installation
guide.

For details on SDK refer to Using the Command-Line Interface to Monitor Oracle Java Cloud Service and Using the Command-Line Interface to Manage Oracle Java Cloud Service.

Implementation

In this example we create an application that consists of the following:

  • Scheduling is implemented using Quartz
  • The monitoring UI is implemented with ADF using a programmatic view object

Create Application

First create an empty application with "Fusion Web Application (ADF)" in this sample I named the application "JcsDemoQuartz". For details on how to create an application refer to this blog post.

Also since in this example security is not implemented navigate to the web.xml and disable authentication by adding the "<login-config/>" element.

Also set the web context root for "ViewController" project by navigating "Project Properties > Java EE Web Context Root" to "quartz".

Install Quartz

First download the Quartz libraries. The installation consists of:

  • Creating the data model
  • Configuring the data source
  • Including the libraries to the application

Create Quartz Data Model

First create the data model in the database, the package downloaded contains a script to create the data model, in my case:

quartz-2.2.1/docs/dbTables/tables_oracle.sql

Run the script against the target database.

Add Quartz Libraries to the Application

Since in this sample the Quartz libraries are accessed from both model and view layer, a separate project is created to encapsulate the Quartz related logic into a separate project.  Create a new "Generic Project" in the application and name it "Quartz"

Copy the jar files to a suitable location e.g. under "lib", navigate "Project Properties > Libraries and Classpath > Add JAR/Directory" and include all the Quartz jar files.

Next include the Quartz project as dependency for both "ViewController" and "Model" projects; navigate "Project Properties > Dependencies > Edit" and check the "Build Output" under the "Quartz" project.

Configure Quartz Datasource

By default all Jobs and Triggers are stored in RAM and therefore do not
persist between program executions. There is also support for storing
the definitions in relational database using JDBC, to do this we need to
configure the data store. Data source is configured by introducing "quartz.properties" file or alternatively doing the same configuration run time when the scheduler is initialized. In this sample the goal is to de-couple the configuration from the code, such that change in the configuration does not require deploying the application again, so the configuration is based on a file uploaded to JCS. Following is an example of the quartz.properties file to be used in JCS, change the value of "jndiURL" to match the name of the data source in the JCS instance: 

org.quartz.scheduler.instanceName = JcsDemoQuartzScheduler
org.quartz.scheduler.skipUpdateCheck = true
org.quartz.threadPool.threadCount = 3
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate
org.quartz.jobStore.useProperties=false
org.quartz.jobStore.dataSource=myDS
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.isClustered=true
org.quartz.dataSource.myDS.jndiURL=<database>
org.quartz.dataSource.myDS.java.naming.factory.initial=weblogic.jndi.WLInitialContextFactory

For details of the configuration refer to the Quartz Scheduler Configuration Guide.

In JCS access to the local file system is restricted so if included in the project directly an error occurs:

Policy POLICY-ID-429 violated.File operation not permitted at /domains/wlsaas/quartz.properties
java.security.AccessControlException: File operation not permitted at /domains/wlsaas/quartz.properties

In order for the Quartz to be able to access the configuration the file must be uploaded into the accessible location in a separate step. First start the file-system access shell:

java -jar javacloud.jar -fs -u user -id identityDomain -si serviceInstance -dc dataCenter 

and upload the file with:

put quartz.properties

Create Quartz Scheduler Singleton

Next we create the logic to interact with the Quartz library.

ScheduledJob

This class represents a scheduled object in Quartz framework. The
object is used by the model and view logic to encapsulate details of a
scheduled job during processing. Create a Java file with name
"ScheduledJob" and package "oracle.demo":

package oracle.demo;
import java.util.Date;
/**
 * This class represents a scheduled object in Quartz framework.
 * The object is used by the ScheduledJobsVO to encapsulate details of a 
 * scheduled job during processing.
 */
public class ScheduledJob {
    /*
     * Variables to store the information for a scheduled job. 
     */
    private String jobName = null;
    private String groupName = null;
    private Date nextExecution = null;
    private String jobClass = null;
    /**
     * Default constructor
     * @param jobName Name of the scheduled job
     * @param groupName Group to which the scheduled job belongs to
     * @param nextExecution The time of next execution for the job
     * @param jobClass Name of the class executed for the job
     */
    public ScheduledJob(String jobName, String groupName,
                        Date nextExecution, String jobClass) {
        super();
        this.jobName = jobName;
        this.groupName = groupName;
        this.nextExecution = nextExecution;
        this.jobClass = jobClass;
    }
    /**
     * Setter for a variable.
     * @param jobName value for the variable
     */
    public void setJobName(String jobName) {
        this.jobName = jobName;
    }
    /**
     * Getter for a variable.
     * @return value of the variable
     */
    public String getJobName() {
        return jobName;
    }
    /**
     * Setter for a variable.
     * @param groupName value for the variable
     */
    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }
    /**
     * Getter for a variable.
     * @return value of the variable
     */
    public String getGroupName() {
        return groupName;
    }
    /**
     * Setter for a variable.
     * @param nextExecution value for the variable
     */
    public void setNextExecution(Date nextExecution) {
        this.nextExecution = nextExecution;
    }
    /**
     * Getter for a variable.
     * @return value of the variable
     */
    public Date getNextExecution() {
        return nextExecution;
    }
    /**
     * Setter for a variable.
     * @param jobClass value for the variable
     */
    public void setJobClass(String jobClass) {
        this.jobClass = jobClass;
    }
    /**
     * Getter for a variable.
     * @return value of the variable
     */
    public String getJobClass() {
        return jobClass;
    }
    @Override
    public String toString() {
        String nextExecutionFormatted = (null == nextExecution ? "null" : String.format("%tc", nextExecution));
        return jobName  + " " + groupName + " " + nextExecutionFormatted + " " + jobClass;
    }    
}

 

Scheduler Jobs

Next we create a class that does the actual processing. Create a Java file with name "TestJob" and package  "oracle.demo":

package oracle.demo;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class TestJob implements Job {
    public void execute(JobExecutionContext context) throws JobExecutionException {
        Date date = new Date();
        System.out.println("Quartz TestJob.execute on " + date);
    }
} 
Also create another similar job with e.g. name "TestJob2".




 

QuartzSchedulerSingleton

Singleton pattern
is used; create new java class with name "QuartzSchedulerSingleton" and
include the following logic. For details of the logic refer to the
comments in code:

package oracle.demo;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.GroupMatcher;
/**
 * Logic to interact with the Quartz library.
 * Implemented using the singleton pattern. Logic is accessed from both model
 * and view layers
 */
public class QuartzSchedulerSingleton {
    // Singleton static instance for the class
    private static QuartzSchedulerSingleton _instance = null;
    /*
     * Static instance of the scheduler factory initialized based on configuration
     * file uploaded to JCS. Instance is shared for all code in the sample.
     */
    private static StdSchedulerFactory _schedulerFactory = null;
    // Static instance for the shared scheduler. Created and started when singleton is created.
    private static Scheduler _scheduler = null;
    /**
     * Default constructor
     * Defined as protected to prevent instantiation
     */
    protected QuartzSchedulerSingleton() {
        System.out.println("QuartzSchedulerSingleton.constructor Start ");
        try {
            /*
             * Obtain instance of the Scheduler Factory and initialize it based
             * on the configuration file uploaded to JCS. In this sample the 
             * file location is hard coded as the location accessible in JCS 
             * is fixed, however this could be based on for example a system
             * property. For this to work the file must exist in the given 
             * location. See the configuration steps in the blog post for steps
             * to upload it.
             * Alternatively the properties object could be populated RT, however
             * that would mean that if something changes the application would
             * need to be re-deployed.
             */
            _schedulerFactory = new StdSchedulerFactory();
            _schedulerFactory.initialize("/customer/scratch/quartz.properties");
            _scheduler = _schedulerFactory.getScheduler();
            // Scheduler needs to be started for it to execute jobs 
            _scheduler.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        System.out.println("QuartzSchedulerSingleton.constructor End ");
    }
    /**
     * Get the instance of the singleton
     * @return instance of the singleton
     */
    public static QuartzSchedulerSingleton getInstance() {
        if (_instance == null) {
            System.out.println("QuartzSchedulerSingleton.getInstance create new ");
            _instance = new QuartzSchedulerSingleton();
        }
        return _instance;
    }
    /**
     * Get the scheduler initialized based on the properties uploaded to JCS.
     * @return initialized scheduler
     */
    public Scheduler getScheduler() {
        return _scheduler;
    }
    /**
     * Gets a list of scheduled jobs
     * @return list of scheduler jobs
     */
    public List<ScheduledJob> getScheduledJobs() {
        System.out.println("QuartzSchedulerSingleton.getScheduledJobs Start ");
        List<ScheduledJob> result = new ArrayList<ScheduledJob>();
        try {            
            // Loop through the groups and all jobs for each group
            for (String groupName : _scheduler.getJobGroupNames()) {
                for (JobKey jobKey :
                     _scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
                    String jobName = jobKey.getName();
                    String jobGroup = jobKey.getGroup();
                    // Get the trigger for the job to determine next execution
                    List<Trigger> triggers =
                        (List<Trigger>)_scheduler.getTriggersOfJob(jobKey);
                    Date nextFireTime = triggers.get(0).getNextFireTime();
                    // Get the JobDetail to obtain the name of the class for the job
                    JobDetail jobDetail = _scheduler.getJobDetail(jobKey);
                    // Add a POJO object to the list containing the details of the scheduled jobs
                    ScheduledJob temp =
                        new ScheduledJob(jobName, jobGroup, nextFireTime,
                                         jobDetail.getJobClass().getName());
                    System.out.println("QuartzSchedulerSingleton.getScheduledJobs job : "+temp);
                    result.add(temp);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        System.out.println("QuartzSchedulerSingleton.getScheduledJobs result "+result);
        return result;
    }
    /**
     * Delete scheduled job
     * @param jobName name of the job to be deleted
     * @param groupName group name of the job to be deleted
     */
    public void deleteScheduledJob(String jobName, String groupName) {
        System.out.println("QuartzSchedulerSingleton.deleteScheduledJob Start; "+jobName + " " + groupName);
        try {
            // Construct the key for the job to be deleted
            JobKey jobKey = new JobKey(jobName, groupName);
            // Delete the job
            _scheduler.deleteJob(jobKey);  
        } catch (Exception e) {
            throw new RuntimeException(e);
        }         
        System.out.println("QuartzSchedulerSingleton.deleteScheduledJob End ");
    }
    /**
     * Schedule a job
     * @param jobName name of the job to be scheduled
     * @param jobClassName class of the job to be schedule
     * @param cronSchedule the cron based schedule for the job
     */
    public void scheduleJob(String jobName, String jobClassName, String cronSchedule) {
        System.out.println("QuartzSchedulerSingleton.scheduleJob Start; "+jobName + " " + jobClassName + " " + cronSchedule);
        try {
            Class jobClass = Class.forName(jobClassName);
            // Create the job
            JobDetail job =
                JobBuilder.newJob(jobClass).withIdentity(jobName).build();
            // Create a new cron based schedule 
            Trigger trigger =
                TriggerBuilder.newTrigger().withIdentity(jobName).withSchedule(CronScheduleBuilder.cronSchedule(cronSchedule)).build();
            _scheduler.scheduleJob(job,trigger);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        System.out.println("QuartzSchedulerSingleton.scheduleJob End ");
    }
}
 

Create ADFbc Objects

To implement UI to monitor and manage the scheduled jobs we will create some ADFBc objects accessing the Quarts data using APIs.

ScheduledJobsVO

This is a programmatic view object is used to query the scheduled jobs using the Quartz APIs. Create the programmatic VO by navigating "File > New > Business Tier > ADF Business Components > View Object". Use name "ScheduledJobsVO", package "oracle.demo.model" and choose "Rows populated programmatically, not based on a query". On the following screen add the following transient attributes with following properties:

  • JobName; String, primary key, always updatable
  • GroupName; String, primary key, always updatable
  • NextExecution; Timestamp, always updatable. On control hints set "Format Type" to "Simple Date" and "Format" to "dd-MMM-yyyy hh:mm"
  • JobClass; String, always updatable

Rows for the VO are populated programmatically so on the "Java" step check the "generate the View Object Class" checkbox and click "Finish". Next we implement the logic to obtain data from Quartz to be displayed on the UI. Open the file ScheduledJobsVOImpl.java; in executeQueryForCollection we use the Quartz APIs to populate an iterator with data for scheduled jobs. In createRowFromResultSet we create programmatically create a row for an entry in the iterator. For details of the implementation refer to the comments in code:

package oracle.demo.model;
import java.sql.ResultSet;
import java.util.List;
import java.util.ListIterator;
import oracle.demo.QuartzSchedulerSingleton;
import oracle.demo.ScheduledJob;
import oracle.jbo.Row;
import oracle.jbo.domain.Timestamp;
import oracle.jbo.server.ViewObjectImpl;
import oracle.jbo.server.ViewRowImpl;
import oracle.jbo.server.ViewRowSetImpl;
// ---------------------------------------------------------------------
// ---    File generated by Oracle ADF Business Components Design Time.
// ---    Tue Jun 02 15:10:50 EEST 2015
// ---    Custom code may be added to this class.
// ---    Warning: Do not modify method signatures of generated methods.
// ---------------------------------------------------------------------
public class ScheduledJobsVOImpl extends ViewObjectImpl {
    /**
     * This is the default constructor (do not remove).
     */
    public ScheduledJobsVOImpl() {
    }
    /**
     * Overridden framework method.
     *
     * Wipe out all traces of a built-in query for this VO
     */
    protected void create() {
        getViewDef().setQuery(null);
        getViewDef().setSelectClause(null);
        setQuery(null);
    }
    @Override
    public void executeQuery() {
        getViewDef().setQuery(null);
        getViewDef().setSelectClause(null);
        setQuery(null);
        this.reset();
        this.clearCache();
        super.executeQuery();
    }
    /**
     * executeQueryForCollection - overridden for custom java data source support.
     */
    protected void executeQueryForCollection(Object qc, Object[] params,
                                             int noUserParams) {
        /*
         * Here we populate an iterator with data obtained using Quartz APIs.
         * This iterator will be used in createRowFromResultSet to 
         * programmatically create rows for the VO.
         */
        QuartzSchedulerSingleton quartzSchedulerSingleton = QuartzSchedulerSingleton.getInstance();
        List<ScheduledJob> scheduledJobs = quartzSchedulerSingleton.getScheduledJobs();
        if (!scheduledJobs.isEmpty()) {
            // Store the iterator of the results on the collection such that it
            // can be accessed from createRowFromResultSet
            ListIterator<ScheduledJob> scheduledJobsIterator =
                scheduledJobs.listIterator();
            setUserDataForCollection(qc, scheduledJobsIterator);
            super.executeQueryForCollection(qc, params, noUserParams);
        }
    }
    /**
     * hasNextForCollection - overridden for custom java data source support.
     */
    protected boolean hasNextForCollection(Object qc) {
        boolean result = false;
        ListIterator<ScheduledJob> scheduledJobs =
            (ListIterator<ScheduledJob>)getUserDataForCollection(qc);
        // Check if the iterator has more elements, if not indicate that the processing is complete
        result = scheduledJobs.hasNext();
        if (!result) {
            setFetchCompleteForCollection(qc, true);
        }
        return result;
    }
    /**
     * createRowFromResultSet - overridden for custom java data source support.
     */
    protected ViewRowImpl createRowFromResultSet(Object qc,
                                                 ResultSet resultSet) {
        // Get the iterator for scheduled jobs, this was populated in executeQueryForCollection
        ListIterator<ScheduledJob> scheduledJobs =
            (ListIterator<ScheduledJob>)getUserDataForCollection(qc);
        // Create new row for current entry in the collection
        ViewRowImpl row = createNewRowForCollection(qc);
        try {
            // Populate the data of the row from the data obtained from Quartz APIs
            ScheduledJob scheduledJob = scheduledJobs.next();
            row.setAttribute("JobName", scheduledJob.getJobName());
            row.setAttribute("GroupName", scheduledJob.getGroupName());
            row.setAttribute("NextExecution", new Timestamp(scheduledJob.getNextExecution()));
            row.setAttribute("JobClass", scheduledJob.getJobClass());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return row;
    }
    /**
     * getQueryHitCount - overridden for custom java data source support.
     * This is not implemented at the moment
     */
    public long getQueryHitCount(ViewRowSetImpl viewRowSet) {
        // Not implemented at the moment
        return 0;
    }
    public void removeCurrentRow() {
        Row row = this.getCurrentRow();
        String jobName = (String)row.getAttribute("JobName");
        String groupName = (String)row.getAttribute("GroupName");
        QuartzSchedulerSingleton quartzSchedulerSingleton = QuartzSchedulerSingleton.getInstance();
        quartzSchedulerSingleton.deleteScheduledJob(jobName, groupName);
        System.out.println("removeCurrentRow "+ jobName + "" + groupName);
        super.removeCurrentRow();
    }
}

QuartzAM

 

Next we create application module to access the VO from the UI. Create the AM by navigating "File > New > Business
Tier > ADF Business Components > Application Module". Use name
"QuartzAM", package "oracle.demo.model". On the "Data Model" screen add the ScheduledJobsVO to the AM. 

Also navigate to "Configurations ->
QuartzAMLocal -> Edit" and choose "JDBC DataSource" as the connection
type and "database" (or whatever the data source name is in your JCS
instance) for the "Datasource name".

Create UI

The UI will use the ADFbc components to display and manage the Quartz jobs.

Managed Bean

Next we create a managed bean which will include the following key logic. For "ViewController" project create a Java file; use name "JcsDemoQuartzBean" and package "oracle.demo":

  • _jobClass: Class variable used to store value obtained from the user through the UI, defaulting to TestJob
  • _cronScheduler: Class variable used to store value obtained from the user through the UI, defaulting to expression for scheduling execution every 30 seconds
  • scheduleJob: Called from the UI when the "ScheduleJob" button is pressed. The logic will use the singleton logic to schedule a job using "class reference" and the "scheduler expression" entered by the user on the UI

For details refer to the comments in code:

package oracle.demo;
import java.util.UUID;
import javax.faces.event.ActionEvent;
import oracle.adf.model.BindingContext;
import oracle.binding.BindingContainer;
import oracle.binding.OperationBinding;
/**
 * Managed bean to handle actions on the UI
 */
public class JcsDemoQuartzBean {
    public JcsDemoQuartzBean() {
        super();
    }
    // Job class obtained from the user through the UI, defaulting to TestJob
    private String _jobClass = "oracle.demo.TestJob";
    // Cron expression obtained from the user through the UI, defaulting to every 30 seconds
    private String _cronScheduler = "0/30 * * * * ?";
    /**
     * Called from the UI when the "ScheduleJob" button is pressed. The logic
     * will use the "class reference" and the "scheduler expression" entered
     * by the user on the UI to schedule a new job.
     * @param actionEvent not used
     */
    public void scheduleJob(ActionEvent actionEvent) {
        System.out.println("JcsDemoQuartzBean.scheduleJob Start ");
        try {
            // Random string is used as the name of the job to keep the names unique
            String jobName = UUID.randomUUID().toString();
            QuartzSchedulerSingleton quartzSchedulerSingleton = QuartzSchedulerSingleton.getInstance();
            quartzSchedulerSingleton.scheduleJob(jobName, this.getJobClass(), this.getCronScheduler());
            BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();  
            // Execute query on the VO to reflect the change on the UI
            OperationBinding method = bindings.getOperationBinding("Execute");  
            method.execute();  
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        System.out.println("JcsDemoQuartzBean.scheduleJob End ");
    }
    /**
     * Setter for the job class. Referred from the input field on the UI to
     * contain the name of the job to be scheduled.
     * @param jobClass Name of the job to be scheduled
     */
    public void setJobClass(String jobClass) {
        this._jobClass = jobClass;
    }
    /**
     * Getter for the job class. Returns the name of the job to be scheduled.
     * @return name of the job to be scheduled
     */
    public String getJobClass() {
        return _jobClass;
    }
    /**
     * Setter for the cron schedule expression. Referred from the input field
     * on the UI to contain the cron schedule expression for the job to be scheduled.
     * @param cronScheduler Cron schedule expression for the job to be scheduled
     */
    public void setCronScheduler(String cronScheduler) {
        this._cronScheduler = cronScheduler;
    }
    /**
     * Getter for the cron schedule expression. Returns the cron schedule
     * expression for the job to be scheduled.
     * @return cron schedule expression for the job to be scheduled
     */
    public String getCronScheduler() {
        return _cronScheduler;
    }
}

Register the java class as managed bean. Open adfc-config.xml, navigate to "Overview > Managed Beans" and create a new managed bean with:

  • Name: JcsDemoQuartzBean
  • Class: oracle.demo.JcsDemoQuartzBean

Create Page


Finally we create the page; navigate "New > Web Tier > JSF > JSF Page", enter name JcsDemoQuartz.jspx, check the "Create as XML Document (*.jspx)" and "Blank Page". Build the UI as follows:
  • Create "Input Text" component and bind the value to #{JcsDemoQuartzBean.jobClass}", this allows the user to enter the name of the class to be scheduled
  • Create "Input Text" component and bind the value to #{JcsDemoQuartzBean.cronScheduler}", this allows the user to enter the cron expression to schedule the job with
  • Create "Button" component and bind the "ActionListener" value to "#{JcsDemoQuartzBean.scheduleJob}"
  • Navigate to "Data Controls > QuartzAMDataControl > ScheduleJobsVO1", drag and drop it as a read only table to the UI. Set "Row Selection" to "Single Row"
  • Navigate to "Data Controls > QuartzAMDataControl > ScheduleJobsVO1
    > Operations > Execute", drag and drop it as button to the UI
  • Navigate to "Data Controls > QuartzAMDataControl > ScheduleJobsVO1 > Operations > Delete", and add as column to the read only table

The UI definitions should look like the following:

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
  <jsp:directive.page contentType="text/html;charset=UTF-8"/>
  <f:view>
    <af:document id="d1" title="Quartz Test">
      <af:messages id="m1"/>
      <af:form id="f1">
        <af:inputText label="Class reference" id="it1"
                      value="#{JcsDemoQuartzBean.jobClass}"/>
        <af:inputText label="Scheduler expression" id="it2"
                      value="#{JcsDemoQuartzBean.cronScheduler}"/>
        <af:commandButton text="ScheduleJob" id="cb1"
                          actionListener="#{JcsDemoQuartzBean.scheduleJob}"/>
        <af:commandButton actionListener="#{bindings.Execute.execute}"
                          text="Refresh Table"
                          disabled="#{!bindings.Execute.enabled}" id="cb3"/>
        <af:panelStretchLayout id="psl1" styleClass="AFStretchWidth">
          <f:facet name="center">
            <af:table value="#{bindings.ScheduledJobsVO1.collectionModel}"
                      var="row" rows="#{bindings.ScheduledJobsVO1.rangeSize}"
                      emptyText="#{bindings.ScheduledJobsVO1.viewable ? 'No data to display.' : 'Access Denied.'}"
                      fetchSize="#{bindings.ScheduledJobsVO1.rangeSize}"
                      rowBandingInterval="0"
                      selectedRowKeys="#{bindings.ScheduledJobsVO1.collectionModel.selectedRow}"
                      selectionListener="#{bindings.ScheduledJobsVO1.collectionModel.makeCurrent}"
                      rowSelection="single" id="t1" summary="Scheduled Jobs" styleClass="AFStretchWidth"
                      partialTriggers="cb2 ::cb3">
              <af:column sortProperty="#{bindings.ScheduledJobsVO1.hints.JobName.name}"
                         sortable="false"
                         headerText="#{bindings.ScheduledJobsVO1.hints.JobName.label}"
                         id="c2" rowHeader="true">
                <af:outputText value="#{row.JobName}" id="ot3"/>
              </af:column>
              <af:column sortProperty="#{bindings.ScheduledJobsVO1.hints.GroupName.name}"
                         sortable="false"
                         headerText="#{bindings.ScheduledJobsVO1.hints.GroupName.label}"
                         id="c4">
                <af:outputText value="#{row.GroupName}" id="ot4"/>
              </af:column>
              <af:column sortProperty="#{bindings.ScheduledJobsVO1.hints.NextExecution.name}"
                         sortable="false"
                         headerText="#{bindings.ScheduledJobsVO1.hints.NextExecution.label}"
                         id="c1">
                <af:outputText value="#{row.NextExecution}" id="ot2">
                  <af:convertDateTime pattern="#{bindings.ScheduledJobsVO1.hints.NextExecution.format}"/>
                </af:outputText>
              </af:column>
              <af:column sortProperty="#{bindings.ScheduledJobsVO1.hints.JobClass.name}"
                         sortable="false"
                         headerText="#{bindings.ScheduledJobsVO1.hints.JobClass.label}"
                         id="c3">
                <af:outputText value="#{row.JobClass}" id="ot1"/>
              </af:column>
              <af:column id="c5" headerText="Delete">
                <af:commandButton actionListener="#{bindings.Delete.execute}"
                                  text="Delete"
                                  disabled="#{!bindings.Delete.enabled}"
                                  id="cb2"/>
              </af:column>
            </af:table>
          </f:facet>
        </af:panelStretchLayout>
      </af:form>
    </af:document>
  </f:view>
</jsp:root>
 

For the system to automatically query the data when navigate to the page open the page definition for the file, select the "ScheduledJobsVO1Iterator", go to the properties window and set the value of property "Refresh" to "always".

 

Testing

Deploy the application to the JCS. Once deployed to the JCS we can access the application with URL:

https://<host:port>/quartz/faces/JcsDemoQuartz.jspx 

Click "ScheduleJob" which will schedule the default job to execute
every 30 seconds. Change the value of "Class Reference" to "oracle.demo.TestJob2" and click "ScheduleJob" to schedule another job:

From the JCS console you can observe the Jobs executing:

 

The scheduled jobs can then be deleted with the "Delete" button.

Summary

In this article we
learned how to
use Quartz to implement scheduling for Java Cloud Service. In future
articles various other technologies and features for scheduling in JCS will be covered.

Appendix

QuartzInitializerListener

Alternatively if access is only needed from UI the scheduler can be automatically started and configured with the application using QuartzInitializerListener. The listener can be configured in the web.xml to point to the properties file uploaded to JCS using parameter, an example configuration would be:

  <listener>
    <listener-class>org.quartz.ee.servlet.QuartzInitializerListener</listener-class>
  </listener>
  <context-param>
    <param-name>config-file</param-name>
    <param-value>/customer/scratch/quartz.properties</param-value>
  </context-param>
  <context-param>
    <param-name>shutdown-on-unload</param-name>
    <param-value>true</param-value>
  </context-param>
  <context-param>
    <param-name>start-on-load</param-name>
    <param-value>true</param-value>
  </context-param>
  <context-param>
    <param-name>start-delay-seconds</param-name>
    <param-value>10</param-value>
  </context-param>

 

 

Join the discussion

Comments ( 6 )
  • guest Friday, February 5, 2016

    Hi Jani,

    Thanks for sharing this article.

    I'm have build and deployed the application by following your steps to Oracle Java Cloud service instance. But while scheduling the Job, I'm getting the below issue, please help in resolving the issue.

    Failed to obtain DB connection from data source 'javatrialXXXXdb': java.sql.SQLException: Could not retrieve datasource via JNDI url 'jdbc/javatrialXXXXdb' javax.naming.NameNotFoundException: While trying to lookup 'jdbc.javatrialXXXXdb' didn't find subcontext 'jdbc'.

    Thanks

    Deepak C S


  • Jani Rautiainen Friday, February 5, 2016

    Looks like some of your DS reference is wrong, what did you use for:

    org.quartz.dataSource.myDS.jndiURL=<database>

    and for

    "Configurations -> QuartzAMLocal -> Edit -> JDBC DataSource -> "Datasource name"

    --

    Jani Rautiainen

    Fusion Applications Developer Relations


  • Gaurav Thursday, May 19, 2016

    Hi Jani,

    Thanks for sharing this useful article.

    I'm facing an issue while scheduling the job using quartz in jcs-sx environment.

    The quartz scheduler gets successfully instantiated and started, but when I try to invoke a job using simple trigger or through cron scheduled job, I get the below error :

    ----------------------------------------------------------------------

    [NOTIFICATION] [1] [Watch 'Error' with severity 'Notice' on server 'm0' has triggered at May 19, 2016 9:39:25 AM GMT. Notification details: [

    WatchRuleType: Log

    WatchRule: (SEVERITY = 'Error')

    WatchData: DATE = May 19, 2016 9:39:25 AM GMT SERVER = m0 MESSAGE = Policy REF-POLICY-ID-515 violated.File operation not permitted at /middleware/oracle_common/modules/oracle.jdbc_11.1.1/ojdbc6dms.jar

    java.security.AccessControlException: File operation not permitted at /middleware/oracle_common/modules/oracle.jdbc_11.1.1/ojdbc6dms.jar

    at oracle.cloud.jcs.scanning.impl.extension.fileaccess.FileValidatorBase.validatePath(FileValidatorBase.java:53)

    at oracle.cloud.jcs.scanning.impl.extension.fileaccess.FilePathValidator.createNewObject(FilePathValidator.java:46)

    at oracle.cloud.jcs.scanning.impl.extension.fileaccess.FileAccessValidator.intercept(FileAccessValidator.java:44)

    at oracle.cloud.jcs.scanning.impl.extension.CloudInvocationHandlerAdapter.invoke(CloudInvocationHandlerAdapter.java:83)

    at oracle.cloud.jcs.security.SecurityManager_TTUDT29564tsiwo.__fwd__WkqIzzL5H6wrRbWuMrfuN2I70lQ__B2z_REF_POLICY_ID_515(SecurityManager_TTUDT29564tsiwo.java:3706)

    at org.apache.axis2.deployment.RepositoryListener.loadClassPathModules(RepositoryListener.java:184)

    at org.apache.axis2.deployment.RepositoryListener.init2(RepositoryListener.java:70)

    at org.apache.axis2.deployment.RepositoryListener.<init>(RepositoryListener.java:63)

    at org.apache.axis2.deployment.DeploymentEngine.loadFromClassPath(DeploymentEngine.java:164)

    at org.apache.axis2.deployment.FileSystemConfigurator.getAxisConfiguration(FileSystemConfigurator.java:135)

    at org.apache.axis2.context.ConfigurationContextFactory.createConfigurationContext(ConfigurationContextFactory.java:68)

    at org.apache.axis2.context.ConfigurationContextFactory.createConfigurationContextFromFileSystem(ConfigurationContextFactory.java:184)

    at org.apache.axis2.client.ServiceClient.configureServiceClient(ServiceClient.java:150)

    at org.apache.axis2.client.ServiceClient.<init>(ServiceClient.java:143)

    at org.apache.axis2.client.ServiceClient.<init>(ServiceClient.java:244)

    -----------------------------------------------------------------------

    For the front-end, I've developed the UI application using ADF 11.1.1.7 version. In the model and view controller project, I've removed the reference for Oracle JDBC library which has ojdbc6dms.jar as part of it.

    But still I get an error related to "java.security.AccessControlException: File operation not permitted at /middleware/oracle_common/modules/oracle.jdbc_11.1.1/ojdbc6dms.jar"

    Any pointers to resolve this issue would be really grateful.

    Thanks,

    Gaurav


  • Jani Rautiainen Tuesday, May 24, 2016

    Can you post the question to our forum where its easier to discuss this:

    https://community.oracle.com/community/oracle-applications/fusion_applications/customizations__extensions_and_integrations/content

    The exception seems to come from the JCS security infrastructure and as such unfortunately it cannot be workaround other than by removing the offending code. For the forum post please include details of whether you are using JCS SX or JCS. Whether the sample application described here works or is this specific to your case. The security restrictions may have changed since this sample was posted and if so it may mean that the sample does not work anymore. Based on the exception this could be related to the access to "quartz.properties" ..

    --

    Jani Rautiainen

    Fusion Applications Developer Relations


  • Gaurav Tuesday, May 24, 2016

    Hi Jani,

    Thank you for responding.

    I've raised an SR with Oracle support. Upon further investigation, Cloud team has filed a bug 23324572 with the jcs-sx development team for fixing the issue.

    Thanks,

    Gaurav


  • Nilesh Junnarkar Wednesday, May 25, 2016

    It seems that the application is packaged with some middleware libraries which are being loaded in the user's context which is non-privileged .

    Please provide the whitelist log which was generated at the deployment time.

    I suspect the issue can be resolved by removing some non-required libraries from the deployed application package.


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.