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.tag;
020
021import java.net.URL;
022
023import lucee.commons.io.res.Resource;
024import lucee.commons.io.res.util.ResourceUtil;
025import lucee.commons.lang.StringUtil;
026import lucee.commons.net.HTTPUtil;
027import lucee.commons.security.Credentials;
028import lucee.commons.security.CredentialsImpl;
029import lucee.runtime.exp.ApplicationException;
030import lucee.runtime.exp.DatabaseException;
031import lucee.runtime.exp.PageException;
032import lucee.runtime.ext.tag.TagImpl;
033import lucee.runtime.net.proxy.ProxyData;
034import lucee.runtime.net.proxy.ProxyDataImpl;
035import lucee.runtime.op.Caster;
036import lucee.runtime.op.date.DateCaster;
037import lucee.runtime.schedule.ScheduleTask;
038import lucee.runtime.schedule.ScheduleTaskImpl;
039import lucee.runtime.schedule.Scheduler;
040import lucee.runtime.schedule.SchedulerImpl;
041import lucee.runtime.type.QueryImpl;
042import lucee.runtime.type.dt.Date;
043import lucee.runtime.type.dt.DateImpl;
044import lucee.runtime.type.dt.Time;
045import lucee.runtime.type.util.KeyConstants;
046/**
047* Provides a programmatic interface to the Lucee scheduling engine. You can run a specified 
048*   page at scheduled intervals with the option to write out static HTML pages. This lets you offer users 
049*   access to pages that publish data, such as reports, without forcing users to wait while a database transaction 
050*   is performed in order to populate the data on the page. 
051**/
052public final class Schedule extends TagImpl {
053
054    private static final short ACTION_RUN=1;
055    private static final short ACTION_UPDATE=2;
056    private static final short ACTION_DELETE=3;
057    private static final short ACTION_LIST=4;
058    private static final short ACTION_PAUSE=5;
059    private static final short ACTION_RESUME=6;
060    
061
062        /** Password if URL is protected. */
063        private String password="";
064
065        /** Required when action ='update'. The date when scheduling of the task should start. */
066        private Date startdate;
067
068        /** Specifies whether to resolve links in the result page to absolute references. */
069        private boolean resolveurl;
070
071        /**  */
072        private short action;
073
074        /** Host name or IP address of a proxy server. */
075        private String proxyserver;
076
077        /** The date when the scheduled task ends. */
078        private Date enddate;
079
080        /** Required with publish ='Yes' A valid filename for the published file. */
081        private String strFile;
082
083        /** Required when creating tasks with action = 'update'. Enter a value in seconds. The time when 
084        **      scheduling of the task starts. */
085        private Time starttime;
086
087        /** The port number on the proxy server from which the task is being requested. Default is 80. When 
088        **              used with resolveURL, the URLs of retrieved documents that specify a port number are automatically 
089        **              resolved to preserve links in the retrieved document. */
090        private int proxyport=80;
091
092        /** The port number on the server from which the task is being scheduled. Default is 80. When used 
093        **              with resolveURL, the URLs of retrieved documents that specify a port number are automatically resolved 
094        **              to preserve links in the retrieved document. */
095        private int port=-1;
096
097        /** The time when the scheduled task ends. Enter a value in seconds. */
098        private Time endtime;
099
100
101        /** Required when creating tasks with action = 'update'. Interval at which task should be scheduled. 
102        **              Can be set in seconds or as Once, Daily, Weekly, and Monthly. The default interval is one hour. The 
103        **              minimum interval is one minute. */
104        private String interval;
105
106        /** Specifies whether the result should be saved to a file. */
107        private boolean publish;
108
109        /** Customizes the requestTimeOut for the task operation. Can be used to extend the default timeout 
110        **              for operations that require more time to execute. */
111        private long requesttimeout=-1;
112
113        /** Username if URL is protected. */
114        private String username;
115
116        /** Required when action = 'update'. The URL to be executed. */
117        private String url;
118
119        /** Required with publish ='Yes' The path location for the published file. */
120        private String strPath;
121
122        /** The name of the task to delete, update, or run. */
123        private String task;
124    private Scheduler scheduler;
125
126    private String proxyuser;
127    private String proxypassword="";
128    
129    private String returnvariable="cfschedule";
130        private boolean hidden;
131        private boolean readonly;
132        private String serverPassword=null;
133        private boolean paused;
134        private boolean autoDelete;
135
136        public void setAutodelete(boolean autoDelete) {
137                this.autoDelete=autoDelete;
138        }
139        
140        /**
141         * @param readonly the readonly to set
142         */
143        public void setReadonly(boolean readonly) {
144                this.readonly = readonly;
145        }
146
147        /**
148         * @param hidden the hidden to set
149         */
150        public void setHidden(boolean hidden) {
151                this.hidden = hidden;
152        }
153        
154    /**
155     * @param returnvariable The returnvariable to set.
156     */
157    public void setReturnvariable(String returnvariable) {
158        this.returnvariable = returnvariable;
159    }
160
161    /**
162     * @param proxypassword The proxypassword to set.
163     */
164    public void setProxypassword(String proxypassword) {
165        this.proxypassword = proxypassword;
166    }
167    /**
168     * @param proxyuser The proxyuser to set.
169     */
170    public void setProxyuser(String proxyuser) {
171        this.proxyuser = proxyuser;
172    }
173    
174    public void setPaused(boolean paused) {
175        this.paused = paused;
176    }
177
178        /** set the value password
179        *  Password if URL is protected.
180        * @param password value to set
181        **/
182        public void setPassword(String password)        {
183                this.password=password;
184        }
185
186        /** set the value startdate
187        *  Required when action ='update'. The date when scheduling of the task should start.
188        * @param objStartDate value to set
189         * @throws PageException
190        **/
191        public void setStartdate(Object objStartDate) throws PageException      {
192                if(StringUtil.isEmpty(objStartDate)) return;
193                this.startdate=new DateImpl(DateCaster.toDateAdvanced(objStartDate,pageContext.getTimeZone()));
194        }
195
196        /** set the value resolveurl
197        *  Specifies whether to resolve links in the result page to absolute references.
198        * @param resolveurl value to set
199        **/
200        public void setResolveurl(boolean resolveurl)   {
201                this.resolveurl=resolveurl;
202        }
203        
204        public void setServerpassword(String serverPassword)    {
205            this.serverPassword=serverPassword;
206        }
207
208        /** set the value action
209        *  
210        * @param action value to set
211         * @throws ApplicationException
212        **/
213        public void setAction(String action) throws ApplicationException        {
214                if(StringUtil.isEmpty(action)) return;
215                action=action.toLowerCase().trim();
216            if(action.equals("run"))this.action=ACTION_RUN;
217            else if(action.equals("delete"))this.action=ACTION_DELETE;
218            else if(action.equals("update"))this.action=ACTION_UPDATE;
219            else if(action.equals("list"))this.action=ACTION_LIST;
220            else if(action.equals("lists"))this.action=ACTION_LIST;
221            else if(action.equals("pause"))this.action=ACTION_PAUSE;
222            else if(action.equals("resume"))this.action=ACTION_RESUME;
223            else throw new ApplicationException("attribute action with value ["+action+"] of tag schedule is invalid","valid attributes are [delete,run,update,list,resume,pause]");
224        }
225
226        /** set the value proxyserver
227        *  Host name or IP address of a proxy server.
228        * @param proxyserver value to set
229        **/
230        public void setProxyserver(String proxyserver)  {
231                this.proxyserver=proxyserver;
232        }
233
234        /** set the value enddate
235        *  The date when the scheduled task ends.
236        * @param enddate value to set
237         * @throws PageException
238        **/
239        public void setEnddate(Object enddate) throws PageException     {
240            if(StringUtil.isEmpty(enddate)) return;
241                this.enddate=new DateImpl(DateCaster.toDateAdvanced(enddate,pageContext.getTimeZone()));
242        }
243
244        /** set the value file
245        *  Required with publish ='Yes' A valid filename for the published file.
246        * @param file value to set
247        **/
248        public void setFile(String file)        {
249                this.strFile=file;
250        }
251
252        /** set the value starttime
253        *  Required when creating tasks with action = 'update'. Enter a value in seconds. The time when 
254        *       scheduling of the task starts.
255        * @param starttime value to set
256         * @throws PageException
257        **/
258        public void setStarttime(Object starttime) throws PageException {
259                if(StringUtil.isEmpty(starttime)) return;
260                this.starttime=DateCaster.toTime(pageContext.getTimeZone(),starttime);
261        }
262
263        /** set the value proxyport
264        *  The port number on the proxy server from which the task is being requested. Default is 80. When 
265        *               used with resolveURL, the URLs of retrieved documents that specify a port number are automatically 
266        *               resolved to preserve links in the retrieved document.
267        * @param proxyport value to set
268         * @throws PageException 
269        **/
270        public void setProxyport(Object oProxyport) throws PageException        {
271                if(StringUtil.isEmpty(oProxyport)) return;
272                this.proxyport=Caster.toIntValue(oProxyport);
273        }
274
275        /** set the value port
276        *  The port number on the server from which the task is being scheduled. Default is 80. When used 
277        *               with resolveURL, the URLs of retrieved documents that specify a port number are automatically resolved 
278        *               to preserve links in the retrieved document.
279        * @param port value to set
280         * @throws PageException 
281        **/
282        public void setPort(Object oPort) throws PageException  {
283                if(StringUtil.isEmpty(oPort)) return;
284                this.port=Caster.toIntValue(oPort);
285        }
286
287        /** set the value endtime
288        *  The time when the scheduled task ends. Enter a value in seconds.
289        * @param endtime value to set
290         * @throws PageException
291        **/
292        public void setEndtime(Object endtime) throws PageException     {
293                if(StringUtil.isEmpty(endtime)) return;
294                this.endtime=DateCaster.toTime(pageContext.getTimeZone(),endtime);
295        }
296
297        /** set the value operation
298        *  The type of operation the scheduler performs when executing this task.
299        * @param operation value to set
300         * @throws ApplicationException
301        **/
302        public void setOperation(String operation) throws ApplicationException  {
303                if(StringUtil.isEmpty(operation)) return;
304                operation=operation.toLowerCase().trim();
305                if(!operation.equals("httprequest"))
306                    throw new ApplicationException("attribute operation must have the value [HTTPRequest]");
307        }
308
309        /** set the value interval
310        *  Required when creating tasks with action = 'update'. Interval at which task should be scheduled. 
311        *               Can be set in seconds or as Once, Daily, Weekly, and Monthly. The default interval is one hour. The 
312        *               minimum interval is one minute.
313        * @param interval value to set
314        **/
315        public void setInterval(String interval)        {
316                if(StringUtil.isEmpty(interval)) return;
317                interval=interval.trim().toLowerCase();
318                if(interval.equals("week")) this.interval="weekly";
319                else if(interval.equals("day")) this.interval="daily";
320                else if(interval.equals("month")) this.interval="monthly";
321                else if(interval.equals("year")) this.interval="yearly";
322            this.interval=interval;
323        }
324
325        /** set the value publish
326        *  Specifies whether the result should be saved to a file.
327        * @param publish value to set
328        **/
329        public void setPublish(boolean publish) {
330                this.publish=publish;
331        }
332
333        /** set the value requesttimeout
334        *  Customizes the requestTimeOut for the task operation. Can be used to extend the default timeout 
335        *               for operations that require more time to execute.
336        * @param requesttimeout value to set
337        **/
338        public void setRequesttimeout(Object oRequesttimeout) throws PageException      {
339                if(StringUtil.isEmpty(oRequesttimeout)) return;
340                this.requesttimeout=Caster.toLongValue(oRequesttimeout)*1000L;
341        }
342
343        /** set the value username
344        *  Username if URL is protected.
345        * @param username value to set
346        **/
347        public void setUsername(String username)        {
348                this.username=username;
349        }
350
351        /** set the value url
352        *  Required when action = 'update'. The URL to be executed.
353        * @param url value to set
354        **/
355        public void setUrl(String url)  {
356                this.url=url;
357        }
358
359        /** set the value path
360        *  Required with publish ='Yes' The path location for the published file.
361        * @param path value to set
362        **/
363        public void setPath(String path)        {
364                this.strPath=path;
365        }
366
367        /** set the value task
368        *  The name of the task to delete, update, or run.
369        * @param task value to set
370        **/
371        public void setTask(String task)        {
372                this.task=task;
373        }
374
375
376        @Override
377        public int doStartTag() throws PageException    {
378            scheduler=pageContext.getConfig().getScheduler();
379            
380            if(action!=ACTION_LIST && task==null) {
381                throw new ApplicationException("attribute task is required for tag schedule when action is not list");
382            }
383            
384            
385            switch(action) {
386                case ACTION_DELETE: doDelete(); break;
387                case ACTION_RUN: doRun();       break;
388                case ACTION_UPDATE: doUpdate(); break;
389                case ACTION_LIST: doList();     break;
390                case ACTION_PAUSE: doPause(true);       break;
391                case ACTION_RESUME: doPause(false);     break;
392            }
393                return SKIP_BODY;
394        }
395
396        /**
397         * @throws PageException
398     */
399    private void doUpdate() throws PageException {
400        String message="missing attribute for tag schedule with action update";
401        String detail="required attributes are [startDate, startTime, URL, interval, operation]";
402        
403        Resource file=null;
404        //if(publish) {
405        if(!StringUtil.isEmpty(strFile) && !StringUtil.isEmpty(strPath)) {
406                file=ResourceUtil.toResourceNotExisting(pageContext, strPath);
407                file=file.getRealResource(strFile);
408        }
409        else if(!StringUtil.isEmpty(strFile)) {
410            file=ResourceUtil.toResourceNotExisting(pageContext, strFile);
411        }
412        else if(!StringUtil.isEmpty(strPath)) {
413            file=ResourceUtil.toResourceNotExisting(pageContext, strPath);
414        }
415        if(file!=null)pageContext.getConfig().getSecurityManager().checkFileLocation(pageContext.getConfig(),file,serverPassword);
416        
417        // missing attributes
418        if(startdate==null || starttime==null || url==null || interval==null) 
419            throw new ApplicationException(message,detail);
420        
421        // timeout
422        if(requesttimeout<0)requesttimeout=pageContext.getRequestTimeout();
423        
424        // username/password
425        Credentials cr=null;
426        if(username!=null) cr=CredentialsImpl.toCredentials(username,password);
427        
428       try {
429           
430           ScheduleTask st=new ScheduleTaskImpl(
431                   task,
432                   file,
433                   startdate,
434                   starttime,
435                   enddate, 
436                   endtime,
437                   url,
438                   port,
439                   interval,
440                   requesttimeout,
441                   cr,
442                   ProxyDataImpl.getInstance(proxyserver,proxyport,proxyuser,proxypassword),
443                   resolveurl,
444                   publish,
445                   hidden,
446                   readonly,
447                   paused,
448                   autoDelete);
449        scheduler.addScheduleTask(st,true);
450    } catch (Exception e) {
451        throw Caster.toPageException(e);
452    } 
453        
454        
455        
456        
457        
458        //
459    }
460
461    /**
462     * @throws PageException
463     */
464    private void doRun() throws PageException {
465        try {
466            scheduler.runScheduleTask(task,true);
467        } catch (Exception e) {
468            throw Caster.toPageException(e);
469        }
470    }
471
472    /**
473     * @throws PageException
474     */
475    private void doDelete() throws PageException {
476        try {
477            scheduler.removeScheduleTask(task,false);
478        } catch (Exception e) {
479            throw Caster.toPageException(e);
480        }
481    }
482
483    /**
484     * @throws PageException
485     * 
486     */
487    private void doList() throws PageException  {
488        //if(tr ue) throw new PageRuntimeException("qqq");
489        ScheduleTask[] tasks = scheduler.getAllScheduleTasks(); 
490        final String v="VARCHAR";
491        String[] cols = new String[]{"task","path","file","startdate","starttime","enddate","endtime",
492                "url","port","interval","timeout","username","password","proxyserver",
493                "proxyport","proxyuser","proxypassword","resolveurl","publish","valid","paused","autoDelete"};
494        String[] types = new String[]{v,v,v,"DATE","OTHER","DATE","OTHER",
495                v,v,v,v,v,v,v,
496                v,v,v,v,"BOOLEAN",v,"BOOLEAN","BOOLEAN"};
497        lucee.runtime.type.Query query=new QueryImpl(cols,types,tasks.length,"query"
498        );
499        try {
500                for(int i=0;i<tasks.length;i++) {
501                    int row=i+1;
502                    ScheduleTask task=tasks[i];
503                    query.setAt(KeyConstants._task,row,task.getTask());
504                    if(task.getResource()!=null) {
505                        query.setAt(KeyConstants._path,row,task.getResource().getParent());
506                        query.setAt(KeyConstants._file,row,task.getResource().getName());
507                    }
508                    query.setAt("publish",row,Caster.toBoolean(task.isPublish()));
509                    query.setAt("startdate",row,task.getStartDate());
510                    query.setAt("starttime",row,task.getStartTime());
511                    query.setAt("enddate",row,task.getEndDate());
512                    query.setAt("endtime",row,task.getEndTime());
513                    query.setAt(KeyConstants._url,row,printUrl(task.getUrl()));
514                    query.setAt(KeyConstants._port,row,Caster.toString(HTTPUtil.getPort(task.getUrl())));
515                    query.setAt("interval",row,task.getStringInterval());
516                    query.setAt("timeout",row,Caster.toString(task.getTimeout()/1000));
517                    query.setAt("valid",row,Caster.toString(task.isValid()));
518                    if(task.hasCredentials()) {
519                        query.setAt("username",row,task.getCredentials().getUsername());
520                        query.setAt("password",row,task.getCredentials().getPassword());
521                    }
522                    ProxyData pd = task.getProxyData();
523                    if(ProxyDataImpl.isValid(pd)){
524                            query.setAt("proxyserver",row,pd.getServer());
525                            if(pd.getPort()>0)query.setAt("proxyport",row,Caster.toString(pd.getPort()));
526                            if(ProxyDataImpl.hasCredentials(pd)) {
527                                query.setAt("proxyuser",row,pd.getUsername());
528                                query.setAt("proxypassword",row,pd.getPassword());
529                            }
530                    }
531                    query.setAt("resolveurl",row,Caster.toString(task.isResolveURL()));
532
533                    query.setAt("paused",row,Caster.toBoolean(task.isPaused()));
534                    query.setAt("autoDelete",row,Caster.toBoolean(((ScheduleTaskImpl)task).isAutoDelete()));
535                    
536                    
537                    
538                }
539                pageContext.setVariable(returnvariable,query);
540        } 
541        catch (DatabaseException e) {}
542        
543        
544    }
545    
546        private void doPause(boolean pause) throws PageException {
547                try {
548                        ((SchedulerImpl)scheduler).pauseScheduleTask(task,pause,true);
549        } catch (Exception e) {
550            throw Caster.toPageException(e);
551        }
552                
553        }
554
555    private String printUrl(URL url) {
556        
557        String qs=url.getQuery();
558        if(qs==null) qs="";
559        else if(qs.length()>0)qs="?"+qs;
560        
561        String str=url.getProtocol()+"://"+url.getHost()+url.getPath()+qs;
562        return str;
563    }
564    @Override
565        public int doEndTag()   {
566                return EVAL_PAGE;
567        }
568
569        @Override
570        public void release()   {
571                super.release();
572                readonly=false;
573                strPath=null;
574                strFile=null;
575                starttime=null;
576                startdate=null;
577                endtime=null;
578                enddate=null;
579                url=null;
580                port=-1;
581                interval=null;
582                requesttimeout=-1;
583                username=null;
584                password="";
585                proxyserver=null;
586                proxyport=80;
587                proxyuser=null;
588                proxypassword="";
589                resolveurl=false;
590                publish=false;
591                returnvariable="cfschedule";
592                task=null;
593                hidden=false;
594                serverPassword=null;
595                paused=false;
596                autoDelete=false;
597        }
598
599}