/// <summary>
        /// Returns (currently not used): 1 if scheduler should retry later; 0 otherwise
        /// </summary>
        /// <param name="heContext"></param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        public static int Execute(HeContext heContext, int timeout)
        {
            string   schedule;
            int      cyclicJobId;
            bool     isShared;
            DateTime dbNow, newNextRun;

            int eSpaceId = Global.eSpaceId;
            int tenantId = heContext.AppInfo.Tenant.Id;

            string   errorLogId = "";
            DateTime dbStartTime, lastRun, nextRun, previousNextRun, isRunnSince;
            DateTime localStartedDateTime = DateTime.Now;             // used to measure the timer duration
            DateTime newIsRunningSince, currentIsRunningSince;
            int      duration = 0;
            string   isRunningBy;
            int      numberOfTries      = 0;
            int      maxNumberOfRetries = RuntimePlatformSettings.Timers.NumberOfRetries.GetValue();

            // ExceptionLogged signals that this type of exception has been logged
            bool ExceptionLogged = false;

            try {
                try {
                    using (Transaction mainTrans = DatabaseAccess.ForRuntimeDatabase.GetRequestTransaction()) {
                        dbStartTime = DBRuntimePlatform.Instance.GetDbDatetime(mainTrans);
                        cyclicJobId = DBRuntimePlatform.Instance.GetCyclicJobId(mainTrans, eSpaceId, tenantId, TimerKey, out isShared);
                    }
                    using (Transaction privTrans = DatabaseAccess.ForRuntimeDatabase.GetCommitableTransaction()) {
                        try {
                            bool ok = DBRuntimePlatform.Instance.GetCyclicJobForUpdate(privTrans, cyclicJobId, isShared, out isRunnSince,
                                                                                       out schedule, out lastRun, out previousNextRun, out isRunningBy);
                            if (!ok)
                            {
                                throw new DataBaseException("Unable to get data for Bootstrap Timer (" + cyclicJobId + ") in Execute method.");
                            }

                            // return if not yet time to execute
                            if (dbStartTime.CompareTo(previousNextRun) < 0)
                            {
                                return(0);
                            }

                            // some node is executing it
                            if (isRunnSince != BuiltInFunction.NullDate())
                            {
                                // In normal cases, it will return from execution because
                                // some other node is executing this timer
                                if (dbStartTime.CompareTo(isRunnSince.AddSeconds(1.2 * timeout)) < 0)
                                {
                                    return(0);
                                }

                                // recover mechanism: if it is executing for too long
                                // maybe there was a problem with the node, but the database
                                // was left with data stating it is still executing.

                                TimeSpan ts;
                                string   recoverDetail = "";
                                try {
                                    string isRunnBy = "?";
                                    ts            = (dbStartTime - isRunnSince);
                                    recoverDetail = "Marked as running since " + isRunnSince.ToString() + " (" + ts.TotalMinutes.ToString() + " minutes) by hubnode '" + isRunnBy + "'. Timeout defined is " + (timeout / 60.0).ToString() + " minutes";
                                } catch {}
                                ErrorLog.LogApplicationError("Recovering timer Bootstrap execution. It is marked as running for too long", recoverDetail, heContext, "");
                            }

                            // lets update db, stating that I'm executing the job
                            numberOfTries = DBRuntimePlatform.Instance.GetNumberOfTries(privTrans, cyclicJobId, isShared);
                            if (numberOfTries >= maxNumberOfRetries)
                            {
                                DBRuntimePlatform.Instance.GetTimerScheduleNextRunAndDbDate(privTrans, cyclicJobId, isShared, out schedule, out dbNow, out newNextRun, out currentIsRunningSince);
                                RuntimeScheduler.NextRun(schedule, dbNow, out nextRun);
                                DBRuntimePlatform.Instance.SetTimerNextRun(privTrans, cyclicJobId, isShared, nextRun);
                                errorLogId = ErrorLog.StaticWrite(DateTime.Now, heContext.Session.SessionID, heContext.AppInfo.eSpaceId, heContext.AppInfo.Tenant.Id, heContext.Session.UserId, String.Format("Timer Bootstrap reached maximum number of retries ({0}) [Calculating next run]", maxNumberOfRetries), "", "");
                                duration   = (int)((TimeSpan)(DateTime.Now - localStartedDateTime)).TotalSeconds;

                                // lets update the rest of the cyclic job stuff...
                                DBRuntimePlatform.Instance.SetTimerLastRun(privTrans, cyclicJobId, isShared, dbStartTime, duration);

                                CyclicJobLog jobLog = new CyclicJobLog();
                                jobLog.Write(dbStartTime,
                                             duration,
                                             TimerKey,
                                             heContext.AppInfo.eSpaceId,
                                             heContext.AppInfo.Tenant.Id,
                                             RuntimeEnvironment.MachineName,
                                             errorLogId,
                                             previousNextRun,
                                             nextRun,
                                             heContext.AppInfo.eSpaceName,
                                             heContext.AppInfo.ApplicationName,
                                             heContext.AppInfo.ApplicationUIDAsKey,
                                             TimerName);
                                return(0);
                            }
                            DBRuntimePlatform.Instance.SetTimerRunningBy(privTrans, cyclicJobId, isShared, RuntimeEnvironment.MachineName, out newIsRunningSince);
                            DBRuntimePlatform.Instance.IncrementNumberOfTries(privTrans, cyclicJobId, isShared);
                        } finally {
                            privTrans.Commit();
                            // NOTE: Must release and reget the session... the timer action might commit or something!
                        }
                    }
                } catch (Exception e) {
                    duration        = (int)((TimeSpan)(DateTime.Now - localStartedDateTime)).TotalSeconds;
                    errorLogId      = ErrorLog.LogApplicationError("Timer Bootstrap error (before executing action 'BootstrapContacts'). Timer duration = " + duration + " secs: " + e.Message + " [Will retry later]", e.ToString(), heContext, "");
                    ExceptionLogged = true;
                    return(1);                    // assume some error in update, so it will retry later...
                }
                // from tests in framework 1.0, it seems this ScriptTimeout is not working as expected...
                // but it seems in framework 1.1 it works fine (if not in debug mode)
                heContext.Context.Server.ScriptTimeout = timeout;

                try {
                    Actions.ActionBootstrapContacts(heContext);
                } catch (Exception e) {
                    // error in timer action: we must rollback all pending transactions
                    DatabaseAccess.RollbackAllTransactions();
                    using (Transaction mainTrans = DatabaseAccess.ForRuntimeDatabase.GetRequestTransaction()) {
                        if (!ExceptionLogged)                           // if already logged, don't log the same error
                        {
                            duration = (int)((TimeSpan)(DateTime.Now - localStartedDateTime)).TotalSeconds;

                            currentIsRunningSince = DBRuntimePlatform.Instance.GetTimerRunningSince(mainTrans, cyclicJobId, isShared);
                            if (currentIsRunningSince != newIsRunningSince)
                            {
                                ErrorLog.LogApplicationError("Timer 'Bootstrap' Running Since Date changed unexpectedly. Expected '" + BuiltInFunction.DateTimeToText(newIsRunningSince) + "' but was '" + BuiltInFunction.DateTimeToText(currentIsRunningSince) + "'.", "", heContext, "");
                            }

                            DBRuntimePlatform.Instance.ClearTimerRunningBy(mainTrans, cyclicJobId, isShared);
                            ExceptionLogged = true;
                            errorLogId      = ErrorLog.LogApplicationError("Timer Bootstrap error (inside action 'BootstrapContacts'). Timer duration = " + duration + " secs:" + e.Message + String.Format(" [retry {0} of {1} scheduled]", numberOfTries + 1, maxNumberOfRetries), e.ToString(), heContext, "");
                            CyclicJobLog jobLog = new CyclicJobLog();
                            jobLog.Write(
                                dbStartTime,
                                duration,
                                TimerKey,
                                heContext.AppInfo.eSpaceId,
                                heContext.AppInfo.Tenant.Id,
                                RuntimeEnvironment.MachineName,
                                errorLogId,
                                previousNextRun,
                                previousNextRun,
                                heContext.AppInfo.eSpaceName,
                                heContext.AppInfo.ApplicationName,
                                heContext.AppInfo.ApplicationUIDAsKey,
                                TimerName);
                            return(0);
                        }
                    }
                }

                using (Transaction mainTrans = DatabaseAccess.ForRuntimeDatabase.GetRequestTransaction()) {
                    try {
                        // calculate next run based on current date in db server and also
                        // on scheduler (that may have been changed inside the user action
                        // for example)

                        DBRuntimePlatform.Instance.GetTimerScheduleNextRunAndDbDate(mainTrans, cyclicJobId, isShared, out schedule, out dbNow, out newNextRun, out currentIsRunningSince);

                        RuntimeScheduler.NextRun(schedule, dbNow, out nextRun);

                        if (newNextRun == previousNextRun)
                        {
                            DBRuntimePlatform.Instance.SetTimerNextRun(mainTrans, cyclicJobId, isShared, nextRun);
                        }
                        else
                        {
                            nextRun = newNextRun;
                        }

                        duration = (int)((TimeSpan)(DateTime.Now - localStartedDateTime)).TotalSeconds;

                        if (currentIsRunningSince != newIsRunningSince)
                        {
                            ErrorLog.LogApplicationError("Timer 'Bootstrap' Running Since Date changed unexpectedly. Expected '" + BuiltInFunction.DateTimeToText(newIsRunningSince) + "' but was '" + BuiltInFunction.DateTimeToText(currentIsRunningSince) + "'.", "", heContext, "");
                        }
                        // lets update the rest of the cyclic job stuff...
                        DBRuntimePlatform.Instance.SetTimerLastRun(mainTrans, cyclicJobId, isShared, dbStartTime, duration);
                        DBRuntimePlatform.Instance.ResetNumberOfTries(mainTrans, cyclicJobId, isShared);

                        CyclicJobLog jobLog = new CyclicJobLog();
                        jobLog.Write(
                            dbStartTime,
                            duration,
                            TimerKey,
                            heContext.AppInfo.eSpaceId,
                            heContext.AppInfo.Tenant.Id,
                            RuntimeEnvironment.MachineName,
                            errorLogId,
                            previousNextRun,
                            nextRun,
                            heContext.AppInfo.eSpaceName,
                            heContext.AppInfo.ApplicationName,
                            heContext.AppInfo.ApplicationUIDAsKey,
                            TimerName);
                        return(0);
                    } catch (Exception e) {
                        if (!ExceptionLogged)                           // if already logged, don't log the same error
                        {
                            duration        = (int)((TimeSpan)(DateTime.Now - localStartedDateTime)).TotalSeconds;
                            errorLogId      = ErrorLog.LogApplicationError("Timer Bootstrap error (after executing action 'BootstrapContacts'). Timer duration = " + duration + " secs: " + e.Message + " [Will retry later]", e.ToString(), heContext, "");
                            ExceptionLogged = true;
                        }
                        return(1);                        // assume some error in update, so it will retry later...
                    }
                }
            } catch (Exception e) {
                if (!ExceptionLogged)                   // if already logged, don't log the same error
                {
                    duration        = (int)((TimeSpan)(DateTime.Now - localStartedDateTime)).TotalSeconds;
                    errorLogId      = ErrorLog.LogApplicationError("Timer Bootstrap error. Timer duration = " + duration + " secs: " + e.Message + " [Will retry later]", e.ToString(), heContext, "");
                    ExceptionLogged = true;
                }
                return(1);
            }
        }