Skip to content

Commit c4279f5

Browse files
committed
update quartz doc
1 parent e9ce88e commit c4279f5

File tree

2 files changed

+317
-0
lines changed

2 files changed

+317
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
<!--
2+
{
3+
"title": "Creating Component-Based Jobs with Quartz Scheduler",
4+
"id": "component-jobs-quartz-scheduler",
5+
"related": ["scheduler-quartz", "clustering-quartz-scheduler"],
6+
"categories": ["extensions", "recipes"],
7+
"description": "How to create and configure component-based jobs with the Quartz Scheduler extension",
8+
"keywords": [
9+
"scheduler",
10+
"quartz",
11+
"component",
12+
"cfc",
13+
"jobs",
14+
"listeners"
15+
]
16+
}
17+
-->
18+
19+
# Creating Component-Based Jobs with Quartz Scheduler
20+
21+
This recipe provides detailed instructions for creating and configuring component-based jobs with the Quartz Scheduler extension for Lucee.
22+
23+
For a general overview of the Quartz Scheduler extension, see the [Quartz Scheduler documentation](https://github.com/lucee/lucee-docs/blob/master/docs/recipes/scheduler-quartz.md).
24+
25+
## Overview
26+
27+
Component-based jobs are a powerful feature of the Quartz Scheduler extension that allow you to execute CFML components (CFCs) as scheduled tasks. This approach provides several advantages over URL-based jobs:
28+
29+
- **Full CFML Capabilities**: Leverage the full power of CFML in your scheduled tasks
30+
- **Object-Oriented Design**: Organize your scheduled tasks using proper OO principles
31+
- **Dependency Injection**: Pass configuration parameters to your components
32+
- **Better Testing**: Create testable, reusable components
33+
34+
## Component Mappings
35+
36+
Before creating component-based jobs, it's important to understand how Lucee locates your components using component mappings.
37+
38+
### Default Component Mappings
39+
40+
Every Lucee installation comes with the following default mapping configuration:
41+
42+
```json
43+
{
44+
"componentMappings": [
45+
{
46+
"physical": "{lucee-config}/components/",
47+
"virtual": "/7c0791ef8c6ceb3efef56e85a04ae393",
48+
"archive": "",
49+
"primary": "physical",
50+
"inspectTemplate": "always"
51+
}
52+
]
53+
}
54+
```
55+
56+
This mapping establishes a location where Lucee looks for components, similar to classpath in Java.
57+
58+
### Configuring Custom Mappings
59+
60+
You can extend the component mappings by:
61+
62+
1. **Editing the Configuration File**:
63+
Edit `lucee-server/context/.CFConfig.json` to add your own mappings
64+
65+
2. **Using the Lucee Administrator**:
66+
Navigate to Server/Web Admin > Archives & Resources > Component Mappings
67+
68+
When a component is referenced in a Quartz Scheduler job configuration, Lucee will search for it in these configured mappings.
69+
70+
## Creating a Component-Based Job
71+
72+
### Step 1: Create the Component
73+
74+
Create a CFC with an `execute()` method that contains your job logic. Optionally, include an `init()` method to receive configuration parameters.
75+
76+
```cfml
77+
// path: {lucee-config}/components/jobs/DatabaseCleanupJob.cfc
78+
component {
79+
80+
// Properties
81+
property name="tableName" type="string";
82+
property name="retentionDays" type="numeric";
83+
property name="logName" type="string" default="scheduler";
84+
85+
// Constructor - receives job parameters
86+
public void function init(
87+
required string tableName,
88+
numeric retentionDays=30,
89+
string logName="scheduler"
90+
) {
91+
variables.tableName = arguments.tableName;
92+
variables.retentionDays = arguments.retentionDays;
93+
variables.logName = arguments.logName;
94+
95+
log log=variables.logName type="info" text="DatabaseCleanupJob initialized for table: #variables.tableName#";
96+
}
97+
98+
// Required execute method - called when the job runs
99+
public void function execute() {
100+
try {
101+
log log=variables.logName type="info" text="Starting cleanup for table: #variables.tableName#";
102+
103+
// Sample cleanup logic
104+
var cutoffDate = dateAdd("d", -variables.retentionDays, now());
105+
var result = queryExecute(
106+
"DELETE FROM #variables.tableName# WHERE created_date < :cutoffDate",
107+
{cutoffDate: {value: cutoffDate, cfsqltype: "CF_SQL_TIMESTAMP"}},
108+
{datasource: "myDatasource"}
109+
);
110+
111+
log log=variables.logName type="info" text="Cleanup complete. Removed #result.recordCount# records from #variables.tableName#";
112+
}
113+
catch(any e) {
114+
log log=variables.logName type="error" text="Error in DatabaseCleanupJob: #e.message#" exception=e;
115+
rethrow;
116+
}
117+
}
118+
}
119+
```
120+
121+
### Step 2: Place the Component in a Mapped Location
122+
123+
Either:
124+
1. Save your component in the default component directory: `{lucee-config}/components/jobs/DatabaseCleanupJob.cfc`
125+
2. Create a custom mapping that points to your component's location
126+
127+
### Step 3: Configure the Job in Quartz Scheduler
128+
129+
Add the component job to your Quartz Scheduler configuration:
130+
131+
```json
132+
{
133+
"jobs": [
134+
{
135+
"label": "Database Cleanup - User Logs",
136+
"component": "jobs.DatabaseCleanupJob",
137+
"cron": "0 0 3 * * ?", // Run at 3 AM daily
138+
"pause": false,
139+
"mode": "transient",
140+
"tableName": "user_logs",
141+
"retentionDays": 90
142+
}
143+
]
144+
}
145+
```
146+
147+
## Component Modes
148+
149+
Quartz Scheduler supports two modes for component jobs:
150+
151+
1. **Transient Mode** (default):
152+
- Creates a new instance of the component for each execution
153+
- Useful for jobs that don't need to maintain state between executions
154+
- Configuration: `"mode": "transient"`
155+
156+
2. **Singleton Mode**:
157+
- Creates a single instance that's reused across all executions
158+
- Useful for jobs that maintain state or have expensive initialization
159+
- Configuration: `"mode": "singleton"`
160+
161+
Example of singleton mode:
162+
163+
```json
164+
{
165+
"label": "Incremental Data Processor",
166+
"component": "jobs.DataProcessor",
167+
"cron": "0 */15 * * * ?", // Every 15 minutes
168+
"mode": "singleton",
169+
"batchSize": 100
170+
}
171+
```
172+
173+
## Creating a Job Listener
174+
175+
Job listeners allow you to monitor and respond to job execution events. They can be used for logging, notifications, or to implement more complex job coordination.
176+
177+
### Step 1: Create the Listener Component
178+
179+
Create a CFC that implements the necessary listener methods:
180+
181+
```cfml
182+
// path: {lucee-config}/components/listeners/JobMonitorListener.cfc
183+
component {
184+
185+
// Properties
186+
property name="name" type="string";
187+
property name="stream" type="string";
188+
property name="logFile" type="string";
189+
190+
// Constructor - receives listener parameters
191+
public void function init(struct listenerData) {
192+
variables.name = "JobMonitorListener";
193+
variables.stream = listenerData.stream ?: "err";
194+
variables.logFile = listenerData.logFile ?: "";
195+
196+
// Initialize any resources
197+
if (len(variables.logFile)) {
198+
// Ensure log directory exists
199+
var logDir = getDirectoryFromPath(variables.logFile);
200+
if (!directoryExists(logDir)) {
201+
directoryCreate(logDir);
202+
}
203+
}
204+
}
205+
206+
// Required method - returns the name of the listener
207+
public string function getName() {
208+
return variables.name;
209+
}
210+
211+
// Called before a job executes
212+
public void function jobToBeExecuted(jobExecutionContext) {
213+
var jobDetail = jobExecutionContext.getJobDetail();
214+
var jobDataMap = jobDetail.getJobDataMap();
215+
var jobName = jobDataMap.get("label") ?: jobDetail.getKey().toString();
216+
217+
var message = "#now()# - Job starting: #jobName#";
218+
writeToLog(message);
219+
}
220+
221+
// Called after a job executes
222+
public void function jobWasExecuted(jobExecutionContext, jobException) {
223+
var jobDetail = jobExecutionContext.getJobDetail();
224+
var jobDataMap = jobDetail.getJobDataMap();
225+
var jobName = jobDataMap.get("label") ?: jobDetail.getKey().toString();
226+
227+
if (isNull(jobException)) {
228+
var message = "#now()# - Job completed successfully: #jobName#";
229+
} else {
230+
var message = "#now()# - Job failed: #jobName# - Error: #jobException.getMessage()#";
231+
}
232+
233+
writeToLog(message);
234+
}
235+
236+
// Called when a job is vetoed
237+
public void function jobExecutionVetoed(jobExecutionContext) {
238+
var jobDetail = jobExecutionContext.getJobDetail();
239+
var jobDataMap = jobDetail.getJobDataMap();
240+
var jobName = jobDataMap.get("label") ?: jobDetail.getKey().toString();
241+
242+
var message = "#now()# - Job execution vetoed: #jobName#";
243+
writeToLog(message);
244+
}
245+
246+
// Helper function to write to log
247+
private void function writeToLog(required string message) {
248+
// Write to console
249+
if (variables.stream == "out") {
250+
systemOutput(message, true, true);
251+
} else {
252+
systemOutput(message, true, false);
253+
}
254+
255+
// Write to log file if configured
256+
if (len(variables.logFile)) {
257+
fileAppend(variables.logFile, message & chr(13) & chr(10));
258+
}
259+
}
260+
}
261+
```
262+
263+
### Step 2: Place the Listener in a Mapped Location
264+
265+
Save your listener component in a location accessible via component mapping, such as:
266+
`{lucee-config}/components/listeners/JobMonitorListener.cfc`
267+
268+
### Step 3: Configure the Listener in Quartz Scheduler
269+
270+
Add the listener to your Quartz Scheduler configuration:
271+
272+
```json
273+
{
274+
"listeners": [
275+
{
276+
"component": "listeners.JobMonitorListener",
277+
"stream": "err",
278+
"logFile": "{lucee-config}/logs/quartz-jobs.log"
279+
}
280+
]
281+
}
282+
```
283+
284+
## Best Practices
285+
286+
1. **Organize Your Components**:
287+
- Create a clear structure for your job components (e.g., by function or application area)
288+
- Use namespaces to avoid conflicts (e.g., `myapp.jobs.DataCleanup`)
289+
290+
2. **Handle Exceptions Properly**:
291+
- Always implement error handling in your `execute()` method
292+
- Log detailed error information to help with troubleshooting
293+
294+
3. **Keep Jobs Focused**:
295+
- Each job component should have a single responsibility
296+
- For complex operations, consider creating helper components
297+
298+
4. **Use Dependency Injection**:
299+
- Pass configuration values through the job configuration
300+
- Avoid hardcoding values in your components
301+
302+
5. **Include Logging**:
303+
- Add detailed logging to track job execution
304+
- Use listeners for centralized monitoring
305+
306+
## Conclusion
307+
308+
Component-based jobs in Quartz Scheduler provide a powerful way to organize and implement your scheduled tasks in Lucee. By understanding component mappings and following these patterns, you can create maintainable, testable job components that leverage the full power of CFML.
309+
310+
Remember to place your components in locations accessible via component mappings and to configure your jobs properly in the Quartz Scheduler configuration.

docs/recipes/scheduler-quartz.md

+7
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,13 @@ When clustering is enabled, the storage backend becomes the source of truth. The
288288

289289
For detailed instructions and best practices on setting up clustering with Quartz Scheduler, see the dedicated [Clustering with Quartz Scheduler](recipes/clustering-quartz-scheduler.md) recipe.
290290

291+
## Related Recipes
292+
293+
For more detailed information on specific aspects of the Quartz Scheduler extension, refer to these dedicated recipes:
294+
295+
- [Clustering with Quartz Scheduler](https://github.com/lucee/lucee-docs/blob/master/docs/recipes/scheduler-quartz-clustering.md): Detailed instructions for setting up and configuring clustering
296+
- [Creating Component-Based Jobs with Quartz Scheduler](https://github.com/lucee/lucee-docs/blob/master/docs/recipes/scheduler-quartz-component-jobs.md): Guide to creating and configuring component-based jobs
297+
291298
## Full Configuration Example
292299

293300
```json

0 commit comments

Comments
 (0)