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.io.IOException;
022import java.util.ArrayList;
023
024import lucee.commons.io.log.LogAndSource;
025import lucee.commons.io.res.Resource;
026import lucee.commons.lang.StringUtil;
027import lucee.loader.engine.CFMLEngine;
028import lucee.runtime.config.Config;
029import lucee.runtime.engine.CFMLEngineImpl;
030import lucee.runtime.exp.PageException;
031import lucee.runtime.net.proxy.ProxyData;
032import lucee.runtime.net.proxy.ProxyDataImpl;
033import lucee.runtime.op.Caster;
034
035import org.w3c.dom.Attr;
036import org.w3c.dom.Document;
037import org.w3c.dom.Element;
038import org.w3c.dom.NamedNodeMap;
039import org.w3c.dom.Node;
040import org.w3c.dom.NodeList;
041import org.xml.sax.SAXException;
042
043/**
044 * scheduler class to execute the scheduled tasks
045 */
046public final class SchedulerImpl implements Scheduler {
047
048    private ScheduleTaskImpl[] tasks;
049    private Resource schedulerFile;
050    private Document doc;
051    private StorageUtil su=new StorageUtil();
052        private String charset;
053        private final Config config;
054        //private String md5;
055
056        private CFMLEngineImpl engine;
057        
058    /**
059     * constructor of the sheduler
060     * @param config 
061     * @param schedulerDir schedule file
062     * @param log
063     * @throws IOException
064     * @throws SAXException
065     * @throws PageException
066     */
067    public SchedulerImpl(CFMLEngine engine,Config config, Resource schedulerDir, String charset) throws SAXException, IOException, PageException {
068        this.engine=(CFMLEngineImpl) engine;
069        this.charset=charset;
070        this.config=config;
071        
072        initFile(schedulerDir);
073        doc=su.loadDocument(schedulerFile);
074        tasks=readInAllTasks();
075        init();
076    }
077    
078
079    /**
080     * creates a empty Scheduler, used for event gateway context
081     * @param engine
082     * @param config
083     * @param log
084     * @throws SAXException
085     * @throws IOException
086     * @throws PageException
087     */
088    public SchedulerImpl(CFMLEngine engine,String xml,Config config) {
089        this.engine=(CFMLEngineImpl) engine;
090        this.config=config;
091        try {
092                        doc=su.loadDocument(xml);
093                } catch (Exception e) {}
094        tasks=new ScheduleTaskImpl[0];
095        init();
096    }
097    
098    
099    
100    
101        private void initFile(Resource schedulerDir) throws IOException {
102                this.schedulerFile=schedulerDir.getRealResource("scheduler.xml");
103                if(!schedulerFile.exists()) su.loadFile(schedulerFile,"/resource/schedule/default.xml");
104                //this.log=log;  
105        }
106        
107    /**
108     * initialize all tasks
109     */
110    private void init() {
111        for(int i=0;i<tasks.length;i++) {
112            init(tasks[i]);
113        }
114    }
115
116        private void init(ScheduleTask task) {
117                new ScheduledTaskThread(engine,this,config,task,charset).start();
118        }
119
120        /**
121         * read in all schedule tasks
122         * @return all schedule tasks
123         * @throws PageException
124     */
125    private ScheduleTaskImpl[] readInAllTasks() throws PageException {
126        Element root = doc.getDocumentElement();
127        NodeList children = root.getChildNodes();
128        ArrayList<ScheduleTaskImpl> list=new ArrayList<ScheduleTaskImpl>();
129        
130        int len=children.getLength();
131        for(int i=0;i<len;i++) {
132            Node n=children.item(i);
133            if(n instanceof Element && n.getNodeName().equals("task")) {
134                list.add(readInTask((Element)n));
135            } 
136        }
137        return list.toArray(new ScheduleTaskImpl[list.size()]);
138    }
139    
140    
141
142    /**
143     * read in a single task element
144     * @param el
145     * @return matching task to Element
146     * @throws PageException
147     */
148    private ScheduleTaskImpl readInTask(Element el) throws PageException {
149        long timeout=su.toLong(el,"timeout");
150        if(timeout>0 && timeout<1000)timeout*=1000;
151        if(timeout<0)timeout=600000;
152        try {
153            ScheduleTaskImpl  st = new ScheduleTaskImpl(
154                    su.toString(el,"name").trim(),
155                    su.toResource(config,el,"file"),
156                    su.toDate(config,el,"startDate"),
157                    su.toTime(config,el,"startTime"),
158                    su.toDate(config,el,"endDate"),
159                    su.toTime(config,el,"endTime"),
160                    su.toString(el,"url"),
161                    su.toInt(el,"port",-1),
162                    su.toString(el,"interval"),
163                    timeout,
164                    su.toCredentials(el,"username","password"),
165                    ProxyDataImpl.getInstance(
166                                su.toString(el,"proxyHost"),
167                                su.toInt(el,"proxyPort",80),
168                                su.toString(el,"proxyUser"),
169                                su.toString(el,"proxyPassword")),
170                    su.toBoolean(el,"resolveUrl"),
171                    su.toBoolean(el,"publish"),
172                    su.toBoolean(el,"hidden",false),
173                    su.toBoolean(el,"readonly",false),
174                    su.toBoolean(el,"paused",false),
175                    su.toBoolean(el,"autoDelete",false));
176            return st;
177        } catch (Exception e) {e.printStackTrace();
178            throw Caster.toPageException(e);
179        }
180    }
181    
182
183        private void addTask(ScheduleTaskImpl task) {   
184                for(int i=0;i<tasks.length;i++){
185                        if(!tasks[i].getTask().equals(task.getTask())) continue;
186                        if(!tasks[i].md5().equals(task.md5())) {
187                                tasks[i].setValid(false);
188                                tasks[i]=task;
189                                init(task);
190                        }
191                        return;
192                }
193                
194                ScheduleTaskImpl[] tmp = new ScheduleTaskImpl[tasks.length+1];
195                for(int i=0;i<tasks.length;i++){
196                        tmp[i]=tasks[i];
197                }
198                tmp[tasks.length]=task;
199                tasks=tmp;
200                init(task);
201        }
202    
203    /**
204     * sets all attributes in XML Element from Schedule Task
205     * @param el
206     * @param task
207     */
208    private void setAttributes(Element el,ScheduleTask task) {
209        if(el==null) return;
210        NamedNodeMap atts = el.getAttributes();
211        
212        for(int i=atts.getLength()-1;i>=0;i--) {
213            Attr att=(Attr) atts.item(i);
214            el.removeAttribute(att.getName());
215        }
216        
217        su.setString(el,"name",task.getTask());
218        su.setFile(el,"file",task.getResource());
219        su.setDateTime(el,"startDate",task.getStartDate());
220        su.setDateTime(el,"startTime",task.getStartTime());
221        su.setDateTime(el,"endDate",task.getEndDate());
222        su.setDateTime(el,"endTime",task.getEndTime());
223        su.setString(el,"url",task.getUrl().toExternalForm());
224        su.setInt(el,"port",task.getUrl().getPort());
225        su.setString(el,"interval",task.getIntervalAsString());
226        su.setInt(el,"timeout",(int)task.getTimeout());
227        su.setCredentials(el,"username","password",task.getCredentials());
228        ProxyData pd = task.getProxyData();
229        su.setString(el,"proxyHost",StringUtil.emptyIfNull(pd==null?"":pd.getServer()));
230        su.setString(el,"proxyUser",StringUtil.emptyIfNull(pd==null?"":pd.getUsername()));
231        su.setString(el,"proxyPassword",StringUtil.emptyIfNull(pd==null?"":pd.getPassword()));
232        su.setInt(el,"proxyPort",pd==null?0:pd.getPort());
233        su.setBoolean(el,"resolveUrl",task.isResolveURL());  
234        su.setBoolean(el,"publish",task.isPublish());   
235        su.setBoolean(el,"hidden",((ScheduleTaskImpl)task).isHidden());  
236        su.setBoolean(el,"readonly",((ScheduleTaskImpl)task).isReadonly());  
237        su.setBoolean(el,"autoDelete",((ScheduleTaskImpl)task).isAutoDelete());  
238    }
239    
240    /**
241     * translate a schedule task object to a XML Element
242     * @param task schedule task to translate
243     * @return XML Element
244     */
245    private Element toElement(ScheduleTask task) {
246        Element el = doc.createElement("task");
247        setAttributes(el,task);   
248        return el;
249    }
250
251        @Override
252        public ScheduleTask getScheduleTask(String name) throws ScheduleException {
253            for(int i=0;i<tasks.length;i++) {
254                if(tasks[i].getTask().equalsIgnoreCase(name)) return tasks[i];
255            }
256            throw new ScheduleException("schedule task with name "+name+" doesn't exist");
257        }
258        
259        @Override
260        public ScheduleTask getScheduleTask(String name, ScheduleTask defaultValue) {
261            for(int i=0;i<tasks.length;i++) {
262                if(tasks[i]!=null && tasks[i].getTask().equalsIgnoreCase(name)) return tasks[i];
263            }
264            return defaultValue;
265        }
266
267        @Override
268        public ScheduleTask[] getAllScheduleTasks() {
269                ArrayList<ScheduleTask> list=new ArrayList<ScheduleTask>();
270                for(int i=0;i<tasks.length;i++) {
271                if(!tasks[i].isHidden()) list.add(tasks[i]);
272            }
273            return list.toArray(new ScheduleTask[list.size()]);
274        }
275        
276        @Override
277        public void addScheduleTask(ScheduleTask task, boolean allowOverwrite) throws ScheduleException, IOException {
278            //ScheduleTask exTask = getScheduleTask(task.getTask(),null);
279            NodeList list = doc.getDocumentElement().getChildNodes();
280            Element el=su.getElement(list,"name", task.getTask());
281            
282            if(!allowOverwrite && el!=null)
283                    throw new ScheduleException("there is already a schedule task with name "+task.getTask());
284            
285            addTask((ScheduleTaskImpl)task);
286            
287            // Element update
288            if(el!=null) {
289                    setAttributes(el,task);
290            }
291            // Element insert
292            else {
293                    doc.getDocumentElement().appendChild(toElement(task));
294            }
295            
296            su.store(doc,schedulerFile);
297        }
298        
299        @Override
300        public void pauseScheduleTask(String name, boolean pause, boolean throwWhenNotExist) throws ScheduleException, IOException {
301
302            for(int i=0;i<tasks.length;i++) {
303                if(tasks[i].getTask().equalsIgnoreCase(name)) {
304                        tasks[i].setPaused(pause);
305                    
306                }
307            }
308            
309            NodeList list = doc.getDocumentElement().getChildNodes();
310            Element el=su.getElement(list,"name", name);
311            if(el!=null) {
312                el.setAttribute("paused", Caster.toString(pause));
313                //el.getParentNode().removeChild(el);
314            }
315            else if(throwWhenNotExist) throw new ScheduleException("can't "+(pause?"pause":"resume")+" schedule task ["+name+"], task doesn't exist");
316            
317            //init();
318            su.store(doc,schedulerFile);
319        }
320
321        @Override
322        public synchronized void removeScheduleTask(String name, boolean throwWhenNotExist) throws IOException, ScheduleException {
323            
324            int pos=-1;
325            for(int i=0;i<tasks.length;i++) {
326                if(tasks[i].getTask().equalsIgnoreCase(name)) {
327                        tasks[i].setValid(false);
328                    pos=i;
329                }
330            }
331            if(pos!=-1) {
332                    ScheduleTaskImpl[] newTasks=new ScheduleTaskImpl[tasks.length-1];
333                    int count=0;
334                    for(int i=0;i<tasks.length;i++) {
335                        if(i!=pos)newTasks[count++]=tasks[i];
336                        
337                    }
338                    tasks=newTasks;
339            }
340            
341            
342            NodeList list = doc.getDocumentElement().getChildNodes();
343            Element el=su.getElement(list,"name", name);
344            if(el!=null) {
345                el.getParentNode().removeChild(el);
346            }
347            else if(throwWhenNotExist) throw new ScheduleException("can't delete schedule task ["+name+"], task doesn't exist");
348            
349            //init();
350            su.store(doc,schedulerFile);
351        }
352        
353        public synchronized void removeIfNoLonerValid(ScheduleTask task) throws IOException {
354                ScheduleTaskImpl sti=(ScheduleTaskImpl) task;
355                if(sti.isValid() || !sti.isAutoDelete()) return;
356                
357                
358                try {
359                        removeScheduleTask(task.getTask(), false);
360                } catch (ScheduleException e) {}
361        }
362        
363
364        @Override
365        public synchronized void runScheduleTask(String name, boolean throwWhenNotExist) throws IOException, ScheduleException {
366            ScheduleTask task = getScheduleTask(name);
367            
368            if(task!=null) {
369                execute(task);
370            }
371            else if(throwWhenNotExist) throw new ScheduleException("can't run schedule task ["+name+"], task doesn't exist");
372            
373            
374            su.store(doc,schedulerFile);
375        }
376    
377    public void execute(ScheduleTask task) {
378        new ExecutionThread(config,task,charset).start();
379    } 
380
381    @Override
382    public LogAndSource getLogger() {
383        throw new RuntimeException("this method is no longer supported, call instead Config.getLogger");
384    }
385}