001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.schedule;
020
021import java.util.Calendar;
022import java.util.TimeZone;
023
024import lucee.commons.date.DateTimeUtil;
025import lucee.commons.date.JREDateTimeUtil;
026import lucee.commons.io.log.Log;
027import lucee.commons.lang.ExceptionUtil;
028import lucee.runtime.config.Config;
029import lucee.runtime.config.ConfigImpl;
030import lucee.runtime.engine.CFMLEngineImpl;
031import lucee.runtime.engine.ThreadLocalPageContext;
032import lucee.runtime.type.dt.DateTimeImpl;
033
034public class ScheduledTaskThread extends Thread {
035
036
037        private static final long DAY=24*3600000;
038        //private Calendar calendar;
039        
040        private long startDate;
041        private long startTime;
042        private long endDate;
043        private long endTime;
044        private int intervall;
045        private int amount;
046        
047        private DateTimeUtil util;
048
049        private int cIntervall;
050        
051        private Config config;
052        private ScheduleTask task;
053        private String charset;
054        private final CFMLEngineImpl engine;
055        private TimeZone timeZone;
056        private SchedulerImpl scheduler;
057
058
059
060        
061        public ScheduledTaskThread(CFMLEngineImpl engine,SchedulerImpl scheduler, Config config, ScheduleTask task, String charset) {
062                util = DateTimeUtil.getInstance();
063                this.engine=engine;
064                this.scheduler=scheduler;
065                this.config=config;
066                this.task=task;
067                this.charset=charset;
068                timeZone=ThreadLocalPageContext.getTimeZone(config);
069                
070                this.startDate=util.getMilliSecondsAdMidnight(timeZone,task.getStartDate().getTime());
071                this.startTime=util.getMilliSecondsInDay(timeZone, task.getStartTime().getTime());
072                this.endDate=task.getEndDate()==null?Long.MAX_VALUE:util.getMilliSecondsAdMidnight(timeZone,task.getEndDate().getTime());
073                this.endTime=task.getEndTime()==null?DAY:util.getMilliSecondsInDay(timeZone, task.getEndTime().getTime());
074
075                
076                this.intervall=task.getInterval();
077                if(intervall>=10){
078                        amount=intervall;
079                        intervall=ScheduleTaskImpl.INTERVAL_EVEREY;
080                }
081                else amount=1;
082
083                cIntervall = toCalndarIntervall(intervall);
084        }
085
086
087        public void run(){
088                try{
089                _run();
090                }
091                finally{
092                        task.setValid(false);
093                        try {
094                                scheduler.removeIfNoLonerValid(task);
095                        } catch (Throwable t) {
096                                ExceptionUtil.rethrowIfNecessary(t);
097                                t.printStackTrace();
098                        }
099                }
100                
101        }
102        public void _run(){
103                
104                                
105                // check values
106                if(startDate>endDate) {
107                        log(Log.LEVEL_ERROR,"This task can not be executed because the task definition is invalid; enddate is before startdate");
108                        return;
109                }
110                if(intervall==ScheduleTaskImpl.INTERVAL_EVEREY && startTime>endTime) {
111                        log(Log.LEVEL_ERROR,"This task can not be executed because the task definition is invalid; endtime is before starttime");
112                        return;
113                }
114                
115                
116                long today = System.currentTimeMillis();
117                long execution ;
118                boolean isOnce=intervall==ScheduleTask.INTERVAL_ONCE;
119                if(isOnce){
120                        if(startDate+startTime<today) return;
121                        execution=startDate+startTime;
122                }
123                else execution = calculateNextExecution(today,false);
124                //long sleep=execution-today;
125                
126                log(Log.LEVEL_INFO,"first execution runs at "+new DateTimeImpl(execution,false).castToString(timeZone));
127
128                while(true){
129                        sleepEL(execution,today);
130                        
131                        if(!engine.isRunning()) break;
132                        
133                        today=System.currentTimeMillis();
134                        long todayTime=util.getMilliSecondsInDay(null,today);
135                        long todayDate=today-todayTime;
136                        
137                        if(!task.isValid()) break;
138                        if(!task.isPaused()){
139                                if(endDate<todayDate && endTime<todayTime) {
140                                        break;
141                                }
142                                execute();
143                        }
144                        if(isOnce)break;
145                        today=System.currentTimeMillis();
146                        execution=calculateNextExecution(today,true);
147
148                        if (!task.isPaused())
149                                log(Log.LEVEL_INFO,"next execution runs at "+new DateTimeImpl(execution,false).castToString(timeZone)+":"+today+":"+execution);
150                        //sleep=execution-today;
151                }
152        }
153        
154        
155        
156        
157        private void log(int level, String msg) {
158                String logName="schedule task:"+task.getTask();
159                ((ConfigImpl)config).getLog("scheduler").log(level,logName, msg);
160        }
161
162
163        private void sleepEL(long when, long now) {
164                long millis = when-now;
165                
166                try {
167                        while(true){
168                                sleep(millis);
169                                millis=when-System.currentTimeMillis();
170                                if(millis<=0)break;
171                                millis=10;
172                        }
173                } 
174                catch (InterruptedException e) {
175                        e.printStackTrace();
176                }
177
178        }
179
180        private void execute() {
181                if(config!=null)new ExecutionThread(config,task,charset).start();
182        }
183
184        private long calculateNextExecution(long now, boolean notNow) {
185                long nowTime=util.getMilliSecondsInDay(timeZone,now);
186                long nowDate=now-nowTime;
187                
188                
189                // when second or date intervall switch to current date
190                if(startDate<nowDate && (cIntervall==Calendar.SECOND || cIntervall==Calendar.DATE))
191                        startDate=nowDate;
192                
193                // init calendar
194                Calendar calendar = JREDateTimeUtil.getThreadCalendar(timeZone);
195                calendar.setTimeInMillis(startDate+startTime);
196                
197                long time;
198                while(true) {
199                        time=getMilliSecondsInDay(calendar);
200                        if(now<=calendar.getTimeInMillis() && time>=startTime) {
201                                // this is used because when cames back sometme to early
202                                if(notNow && (calendar.getTimeInMillis()-now)<1000);
203                                else if(intervall==ScheduleTaskImpl.INTERVAL_EVEREY && time>endTime)
204                                        now=nowDate+DAY;
205                                else 
206                                        break;
207                        }
208                        calendar.add(cIntervall, amount);
209                }
210                return calendar.getTimeInMillis();
211        }
212
213        private static int toCalndarIntervall(int intervall) {
214                switch(intervall){
215                case ScheduleTask.INTERVAL_DAY:return Calendar.DATE;
216                case ScheduleTask.INTERVAL_MONTH:return Calendar.MONTH;
217                case ScheduleTask.INTERVAL_WEEK:return Calendar.WEEK_OF_YEAR;
218                case ScheduleTask.INTERVAL_ONCE:return -1;
219                
220                }
221                return Calendar.SECOND;
222        }
223        
224        public static long getMilliSecondsInDay(Calendar c) {
225                return  (c.get(Calendar.HOUR_OF_DAY)*3600000)+
226                    (c.get(Calendar.MINUTE)*60000)+
227                    (c.get(Calendar.SECOND)*1000)+
228                    (c.get(Calendar.MILLISECOND));
229        
230    }
231        
232
233        public Config getConfig() {
234                return config;
235        }
236
237
238        public ScheduleTask getTask() {
239                return task;
240        }
241}