Skip to content

Commit

Permalink
PRESIDECMS-2972 refactor cron functionality into a shared lib
Browse files Browse the repository at this point in the history
and use in various places that were relying on jars that no
longer exist in the project.
  • Loading branch information
DominicWatson committed Nov 18, 2024
1 parent 4564f16 commit 312f3fb
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 161 deletions.
3 changes: 2 additions & 1 deletion system/handlers/admin/TaskManager.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
component extends="preside.system.base.AdminHandler" {

property name="taskManagerService" inject="taskManagerService";
property name="cronUtil" inject="cronUtil";
property name="i18n" inject="i18n";
property name="logRendererUtil" inject="logRendererUtil";
property name="taskHistoryDao" inject="presidecms:object:taskmanager_task_history";
Expand Down Expand Up @@ -106,7 +107,7 @@ component extends="preside.system.base.AdminHandler" {
var formName = "taskmanager.task_configuration";
var formData = event.getCollectionForForm( formName );
var validationResult = validateForm( formName, formData );
var crontabError = taskManagerService.getValidationErrorMessageForPotentiallyBadCrontabExpression( formData.crontab_definition ?: "" );
var crontabError = cronUtil.validateExpression( formData.crontab_definition ?: "" );

if ( Len( Trim( crontabError ) ) ) {
validationResult.addError( fieldName="crontab_definition", message=crontabError );
Expand Down
9 changes: 5 additions & 4 deletions system/handlers/admin/datamanager/saved_export.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ component {
property name="customizationService" inject="dataManagerCustomizationService";
property name="datamanagerService" inject="datamanagerService";
property name="messageBox" inject="messagebox@cbmessagebox";
property name="taskManagerService" inject="taskManagerService";
property name="cronUtil" inject="cronUtil";
property name="i18n" inject="i18n";
property name="dataExportTemplateService" inject="dataExportTemplateService";
property name="formsService" inject="FormsService";

Expand All @@ -34,7 +35,7 @@ component {

for ( var record in records ) {
querySetCell( records, "label", _decorateLabelForListing( record.id ), queryCurrentRow( records ) );
querySetCell( records, "schedule", scheduledExportService.cronExpressionToHuman( record.schedule ), queryCurrentRow( records ) );
querySetCell( records, "schedule", cronUtil.describeCronTabExression( record.schedule, i18n.getFwLanguageCode() ), queryCurrentRow( records ) );
}
}

Expand Down Expand Up @@ -84,7 +85,7 @@ component {
var formData = args.formData ?: {};

if ( len( formData.schedule ?: "" ) && formData.schedule != "disabled" ) {
var scheduleValidationMessage = taskManagerService.getValidationErrorMessageForPotentiallyBadCrontabExpression( formData.schedule );
var scheduleValidationMessage = cronUtil.validateExpression( formData.schedule );

if ( len( trim( scheduleValidationMessage ) ) ) {
args.validationResult.addError( fieldName="schedule", message=scheduleValidationMessage );
Expand Down Expand Up @@ -129,7 +130,7 @@ component {
if ( !isEmpty( args.record.schedule ?: "" ) && ( args.record.schedule neq "disabled" ) ) {
args.exportSchedule = {
raw = args.record.schedule
, readable = scheduledExportService.cronExpressionToHuman( args.record.schedule )
, readable = cronUtil.describeCronTabExression( args.record.schedule, i18n.getFwLanguageCode() )
};

if ( !isEmptyString( args.exportSchedule.readable ?: "" ) ) {
Expand Down
5 changes: 3 additions & 2 deletions system/handlers/formcontrols/CronPicker.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
* @feature presideForms
*/
component {
property name="scheduledExportService" inject="ScheduledExportService";
property name="cronUtil" inject="cronUtil";
property name="i18n" inject="i18n";

private string function index( event, rc, prc, args={} ) {
var inputName = args.name ?: "";
Expand Down Expand Up @@ -35,7 +36,7 @@ component {
var expression = rc.expression ?: "";

if ( !isEmpty( expression ) ) {
return scheduledExportService.cronExpressionToHuman( expression );
return cronUtil.describeCronTabExression( expression, i18n.getFWLanguageCode() );
}
return "";
}
Expand Down
55 changes: 15 additions & 40 deletions system/services/dataExport/ScheduledExportService.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
*/
component {
/**
* @threadUtil.inject threadUtil
* @threadUtil.inject threadUtil
* @cronUtil.inject cronUtil
*/
public any function init(
required any threadUtil
, required any cronUtil
) {
_setThreadUtil( arguments.threadUtil );
_setCronUtil( arguments.cronUtil );

_setMachineId();
_setRunningExports({});
Expand Down Expand Up @@ -102,31 +105,13 @@ component {
var detail = getExportDetail( arguments.recordId );

if ( !isEmpty( detail ) and !isEmpty( detail.schedule ?: "" ) ) {
$getPresideObject( "saved_export" ).updateData( id=arguments.recordId, data={ next_run=getNextRunDate( detail.schedule ) } );
$getPresideObject( "saved_export" ).updateData( id=arguments.recordId, data={ next_run=_getCronUtil().getNextRunDate( detail.schedule ) } );
}
} catch (any e) {
$raiseError( e );
}
}

public string function getNextRunDate( required string schedule, date lastRun=now() ) {
if ( !Len( Trim( arguments.schedule ) ) || arguments.schedule == "disabled" ) {
return "";
}

var cronTabExpression = _getCrontabExpressionObject( arguments.schedule );
var lastRunJodaTime = _createJodaTimeObject( arguments.lastRun );

return cronTabExpression.nextTimeAfter( lastRunJodaTime ).toDate();
}

public string function cronExpressionToHuman( required string expression ) {
if ( arguments.expression == "disabled" ) {
return "Disabled";
}
return CreateObject( "java", "net.redhogs.cronparser.CronExpressionDescriptor", _getLib() ).getDescription( arguments.expression );
}

public void function sendScheduledExports() {
var nonRunningExports = $getPresideObject( "saved_export" ).selectData(
selectFields = [ "id" ]
Expand Down Expand Up @@ -209,7 +194,7 @@ component {
id = arguments.exportId
, data = {
is_running = true
, next_run = ( ( exportDetail.schedule ?: "" ) eq "disabled" ) ? "" : getNextRunDate( exportDetail.schedule ?: "" )
, next_run = ( ( exportDetail.schedule ?: "" ) eq "disabled" ) ? "" : _getCronUtil().getNextRunDate( exportDetail.schedule ?: "" )
, running_thread = arguments.threadId
, running_machine = _getMachineId()
}
Expand All @@ -233,7 +218,7 @@ component {
, data = {
is_running = false
, last_ran = now()
, next_run = ( ( exportRecord.schedule ?: "" ) eq "disabled" ) ? "" : getNextRunDate( exportRecord.schedule ?: "" )
, next_run = ( ( exportRecord.schedule ?: "" ) eq "disabled" ) ? "" : _getCronUtil().getNextRunDate( exportRecord.schedule ?: "" )
, was_last_run_success = false
, last_run_time_taken = ""
, running_thread = ""
Expand Down Expand Up @@ -264,7 +249,7 @@ component {
, data = {
is_running = false
, last_ran = now()
, next_run = ( ( exportRecord.schedule ?: "" ) eq "disabled" ) ? "" : getNextRunDate( exportRecord.schedule ?: "" )
, next_run = ( ( exportRecord.schedule ?: "" ) eq "disabled" ) ? "" : _getCronUtil().getNextRunDate( exportRecord.schedule ?: "" )
, was_last_run_success = arguments.success
, last_run_time_taken = arguments.timeTaken
, running_thread = ""
Expand Down Expand Up @@ -340,23 +325,6 @@ component {
}

// PRIVATE HELPERS
private array function _getLib() {
return [
"/preside/system/services/taskmanager/lib/cron-parser-2.6-SNAPSHOT.jar"
, "/preside/system/services/taskmanager/lib/commons-lang3-3.3.2.jar"
, "/preside/system/services/taskmanager/lib/joda-time-2.9.4.jar"
, "/preside/system/services/taskmanager/lib/cron-1.0.jar"
];
}

private any function _createJodaTimeObject( required date cfmlDateTime ) {
return CreateObject( "java", "org.joda.time.DateTime", _getLib() ).init( cfmlDateTime );
}

private any function _getCrontabExpressionObject( required string expression ) {
return CreateObject( "java", "fc.cron.CronExpression", _getLib() ).init( arguments.expression );
}

private boolean function _exportIsRunningOnLocalMachine( required any task ){
var runningTasks = _getRunningExports();
var threadRef = runningTasks[ task.running_thread ].thread ?: NullValue();
Expand Down Expand Up @@ -396,4 +364,11 @@ component {
private void function _setThreadUtil( required any threadUtil ) {
_threadUtil = arguments.threadUtil;
}

private any function _getCronUtil() {
return _cronUtil;
}
private void function _setCronUtil( required any cronUtil ) {
_cronUtil = arguments.cronUtil;
}
}
44 changes: 13 additions & 31 deletions system/services/systemAlerts/SystemAlertsService.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ component {

// CONSTRUCTOR
/**
* @validAlertLevels.inject coldbox:setting:enum.systemAlertLevel
* @validAlertLevels.inject coldbox:setting:enum.systemAlertLevel
* @cronUtil.inject cronUtil
*
*/
public any function init( required array validAlertLevels ) {
public any function init( required array validAlertLevels, required any cronUtil ) {
_setValidAlertLevels( arguments.validAlertLevels );
_setCronUtil( arguments.cronUtil );

return this;
}
Expand Down Expand Up @@ -470,38 +472,11 @@ component {
}

private string function _getNextRunDate( required string schedule, date lastRun=now() ) {
var cronTabExpression = _getCrontabExpressionObject( arguments.schedule );
var lastRunJodaTime = _createJodaTimeObject( arguments.lastRun );

return cronTabExpression.nextTimeAfter( lastRunJodaTime ).toDate();
return _getCronUtil().getNextRunDate( arguments.schedule, arguments.lastRun );
}

private boolean function _crontabExpressionIsValid( required string crontabExpression ) {
try {
_getCrontabExpressionObject( arguments.cronTabExpression );
} catch ( any e ) {
$raiseError( e );
return false;
}

return true;
}

private any function _getCrontabExpressionObject( required string expression ) {
return CreateObject( "java", "fc.cron.CronExpression", _getSchedulingLib() ).init( arguments.expression );
}

private any function _createJodaTimeObject( required date cfmlDateTime ) {
return CreateObject( "java", "org.joda.time.DateTime", _getSchedulingLib() ).init( cfmlDateTime );
}

private array function _getSchedulingLib() {
return [
"/preside/system/services/taskmanager/lib/cron-parser-2.6-SNAPSHOT.jar"
, "/preside/system/services/taskmanager/lib/commons-lang3-3.3.2.jar"
, "/preside/system/services/taskmanager/lib/joda-time-2.9.4.jar"
, "/preside/system/services/taskmanager/lib/cron-1.0.jar"
];
return Len( _getCronUtil().validateExpression( arguments.crontabExpression ) ) > 0;
}

// GETTERS AND SETTERS
Expand Down Expand Up @@ -533,4 +508,11 @@ component {
variables._validAlertLevels = arguments.validAlertLevels;
}

private any function _getCronUtil() {
return _cronUtil;
}
private void function _setCronUtil( required any cronUtil ) {
_cronUtil = arguments.cronUtil;
}

}
99 changes: 99 additions & 0 deletions system/services/taskmanager/CronUtil.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* Service providing cron parsing and other cron related utility functions
*
* @singleton true
* @presideService true
*/
component displayName="Cron util" {

// CONSTRUCTOR
public any function init() {
_setupCronParser();
_setupTimezoneOffset();

return this;
}

// PUBLIC API METHODS
public string function validateExpression( required string crontabExpression ) {
try {
_getCrontabExpressionObject( arguments.cronTabExpression );
} catch ( any e ) {
return e.message;
}

return "";
}

public string function getNextRunDate( required string crontabExpression, date lastRun=Now() ) {
var cronTabExpression = _getCrontabExpressionObject( arguments.crontabExpression );
var executionTimeObj = CreateObject( "java", "com.cronutils.model.time.ExecutionTime", _getLib() ).forCron( cronTabExpression );

return executionTimeObj.nextExecution( _createJavaZonedTimeObject( arguments.lastRun ) ).get().toString();
}

public string function describeCronTabExression( required string crontabExpression, required string locale ) {
if ( arguments.crontabExpression == "disabled" ) {
return "disabled";
}

var locale = CreateObject( "java", "java.util.Locale" ).of( UCase( ListFirst( arguments.locale, "-" ) ) );
var cronObj = _getCrontabExpressionObject( arguments.crontabExpression );
var descriptor = CreateObject( "java", "com.cronutils.descriptor.CronDescriptor", _getLib() ).instance( locale );

return descriptor.describe( cronObj );
}


// PRIVATE HELPERS
private any function _createJavaZonedTimeObject( required date cfmlDateTime ) {
var formatted = DateFormat( arguments.cfmlDateTime, "yyyy-mm-dd" ) & "T"
& TimeFormat( arguments.cfmlDateTime, "HH:mm:ss" ) & variables._timezoneOffset;

return CreateObject( "java", "java.time.ZonedDateTime" ).parse( formatted );
}

private any function _getCrontabExpressionObject( required string expression ) {
return variables._cronParser.parse( _convertToValidQuartzCron( arguments.expression ) );
}

private array function _getLib() {
return [ "/preside/system/services/taskmanager/lib/cron-utils-9.2.1.jar" ];
}

private string function _convertToValidQuartzCron( expression ) {
var expressions = ListToArray( arguments.expression, " " );

// quartz does not allow both day of month and day of week
// replace one if both used with ?
if ( ArrayLen( expressions ) >= 6 ) {
if ( expressions[ 4 ] == "*" && expressions[ 6 ] != "?" ) {
expressions[ 4 ] = "?";
} else if ( expressions[ 4 ] != "?" ) {
expressions[ 6 ] = "?"
}
}

return ArrayToList( expressions, " " );
}

private void function _setupCronParser() {
var cronTypes = CreateObject( "java", "com.cronutils.model.CronType", _getLib() );
var defBuilder = CreateObject( "java", "com.cronutils.model.definition.CronDefinitionBuilder", _getLib() );
var def = defBuilder.instanceDefinitionFor( cronTypes.QUARTZ );

variables._cronParser = CreateObject( "java", "com.cronutils.parser.CronParser", _getLib() ).init( def );
}

private void function _setupTimezoneOffset() {
var tzInfo = GetTimeZoneInfo();
var hours = NumberFormat( tzInfo.utcHourOffset, "00" );
var mins = NumberFormat( tzInfo.utcMinuteOffset, "00" );

variables._timezoneOffset = "#hours#:#mins#";
if ( Left( variables._timezoneOffset, "1" ) != "-" ) {
variables._timezoneOffset = "+" & variables._timezoneOffset;
}
}

}
Loading

0 comments on commit 312f3fb

Please sign in to comment.