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.util.Iterator;
022
023import lucee.commons.io.SystemUtil;
024import lucee.commons.lang.StringUtil;
025import lucee.runtime.Page;
026import lucee.runtime.PageContext;
027import lucee.runtime.PageContextImpl;
028import lucee.runtime.config.ConfigImpl;
029import lucee.runtime.engine.ThreadLocalPageContext;
030import lucee.runtime.exp.ApplicationException;
031import lucee.runtime.exp.ExpressionException;
032import lucee.runtime.exp.PageException;
033import lucee.runtime.exp.SecurityException;
034import lucee.runtime.ext.tag.BodyTagImpl;
035import lucee.runtime.ext.tag.DynamicAttributes;
036import lucee.runtime.op.Caster;
037import lucee.runtime.spooler.ExecutionPlan;
038import lucee.runtime.spooler.ExecutionPlanImpl;
039import lucee.runtime.thread.ChildSpoolerTask;
040import lucee.runtime.thread.ChildThread;
041import lucee.runtime.thread.ChildThreadImpl;
042import lucee.runtime.thread.ThreadUtil;
043import lucee.runtime.thread.ThreadsImpl;
044import lucee.runtime.type.Array;
045import lucee.runtime.type.Collection;
046import lucee.runtime.type.Collection.Key;
047import lucee.runtime.type.KeyImpl;
048import lucee.runtime.type.Struct;
049import lucee.runtime.type.StructImpl;
050import lucee.runtime.type.scope.Threads;
051import lucee.runtime.type.util.ListUtil;
052
053// MUST change behavor of mltiple headers now is a array, it das so?
054
055/**
056* Lets you execute HTTP POST and GET operations on files. Using cfhttp, you can execute standard 
057*   GET operations and create a query object from a text file. POST operations lets you upload MIME file 
058*   types to a server, or post cookie, formfield, URL, file, or CGI variables directly to a specified server.
059*
060*
061*
062* 
063**/
064public final class ThreadTag extends BodyTagImpl implements DynamicAttributes {
065        
066        private static final int ACTION_JOIN = 0;
067        private static final int ACTION_RUN = 1;
068        private static final int ACTION_SLEEP = 2;
069        private static final int ACTION_TERMINATE = 3;
070        
071        private static final int TYPE_DAEMON = 0;
072        private static final int TYPE_TASK = 1;
073        private static final ExecutionPlan[] EXECUTION_PLAN = new ExecutionPlan[0];
074        
075        private int action=ACTION_RUN;
076        private long duration=-1;
077        private String name;
078        private String lcName;
079        private int priority=Thread.NORM_PRIORITY;
080        private long timeout=0;
081        private PageContext pc;
082        private int type=TYPE_DAEMON;
083        private ExecutionPlan[] plans=EXECUTION_PLAN;
084        private Struct attrs;
085        
086
087        @Override
088        public void release()   {
089                super.release();
090                action=ACTION_RUN;
091                duration=-1;
092                name=null;
093                lcName=null;
094                priority=Thread.NORM_PRIORITY;
095                type=TYPE_DAEMON;
096                plans=EXECUTION_PLAN;
097                timeout=0;
098                attrs=null;
099                pc=null;
100        }
101        
102        /**
103         * @param action the action to set
104         */
105        public void setAction(String strAction) throws ApplicationException {
106                String lcAction = strAction.trim().toLowerCase();
107                
108                if("join".equals(lcAction))                     this.action=ACTION_JOIN;
109                else if("run".equals(lcAction))                 this.action=ACTION_RUN;
110                else if("sleep".equals(lcAction))       this.action=ACTION_SLEEP;
111                else if("terminate".equals(lcAction)) this.action=ACTION_TERMINATE;
112                else 
113                        throw new ApplicationException("invalid value ["+strAction+"] for attribute action","values for attribute action are:join,run,sleep,terminate");
114        }
115
116
117        /**
118         * @param duration the duration to set
119         */
120        public void setDuration(double duration) {
121                this.duration = (long) duration;
122        }
123
124
125        /**
126         * @param name the name to set
127         */
128        public void setName(String name) {
129                if(StringUtil.isEmpty(name,true)) return;
130                this.name = name.trim();
131                this.lcName=this.name.toLowerCase();
132        }
133
134
135        /**
136         * @param strPriority the priority to set
137         */
138        public void setPriority(String strPriority) throws ApplicationException {
139                int p = ThreadUtil.toIntPriority(strPriority);
140                if(p==-1) {
141                        throw new ApplicationException("invalid value ["+strPriority+"] for attribute priority","values for attribute priority are:low,high,normal");
142                }
143                priority=p;
144        }
145        
146
147        /**
148         * @param strType the type to set
149         * @throws ApplicationException 
150         * @throws SecurityException 
151         */
152        public void setType(String strType) throws ApplicationException, SecurityException {
153                strType=strType.trim().toLowerCase();
154
155                if("task".equals(strType))      {
156                        // SNSN
157                        /*SerialNumber sn = pageContext.getConfig().getSerialNumber();
158                    if(sn.getVersion()==SerialNumber.VERSION_COMMUNITY)
159                         throw new SecurityException("no access to this functionality with the "+sn.getStringVersion()+" version of lucee");
160                    */
161                        
162                        
163                        //throw new ApplicationException("invalid value ["+strType+"] for attribute type","task is not supported at the moment");
164                        type=TYPE_TASK;
165                }
166                else if("daemon".equals(strType) || "deamon".equals(strType))   type=TYPE_DAEMON;
167                else throw new ApplicationException("invalid value ["+strType+"] for attribute type","values for attribute type are:task,daemon (default)");
168                
169        }
170        
171        public void setRetryintervall(Object obj) throws PageException {
172                setRetryinterval(obj);
173        }
174        
175        public void setRetryinterval(Object obj) throws PageException {
176                if(StringUtil.isEmpty(obj))return;
177                Array arr = Caster.toArray(obj,null);
178                if(arr==null){
179                        plans=new ExecutionPlan[]{toExecutionPlan(obj,1)};
180                }
181                else {
182                        Iterator<Object> it = arr.valueIterator();
183                        plans=new ExecutionPlan[arr.size()];
184                        int index=0;
185                        while(it.hasNext()) {
186                                plans[index++]=toExecutionPlan(it.next(),index==1?1:0);
187                        }
188                }
189                
190        }
191        
192        
193        
194
195
196        private ExecutionPlan toExecutionPlan(Object obj,int plus) throws PageException {
197
198                if(obj instanceof Struct){
199                        Struct sct=(Struct)obj;
200                        // GERT
201                        
202                        // tries
203                        Object oTries=sct.get("tries",null);
204                        if(oTries==null)throw new ExpressionException("missing key tries inside struct");
205                        int tries=Caster.toIntValue(oTries);
206                        if(tries<0)throw new ExpressionException("tries must contain a none negative value");
207                        
208                        // interval
209                        Object oInterval=sct.get("interval",null);
210                        if(oInterval==null)oInterval=sct.get("intervall",null);
211                        
212                        if(oInterval==null)throw new ExpressionException("missing key interval inside struct");
213                        int interval=toSeconds(oInterval);
214                        if(interval<0)throw new ExpressionException("interval should contain a positive value or 0");
215                        
216                        
217                        return new ExecutionPlanImpl(tries+plus,interval);
218                }
219                return new ExecutionPlanImpl(1+plus,toSeconds(obj));
220        }
221        
222        private int toSeconds(Object obj) throws PageException {
223                return (int)Caster.toTimespan(obj).getSeconds();
224        }
225        /**
226         * @param timeout the timeout to set
227         */
228        public void setTimeout(double timeout) {
229                this.timeout = (long)timeout;
230        }
231
232        @Override
233        public void setDynamicAttribute(String uri, String name, Object value) {
234                if(attrs==null)attrs=new StructImpl();
235                Key key = KeyImpl.getInstance(StringUtil.trim(name,""));
236                attrs.setEL(key,value);
237        }
238
239        @Override
240        public void setDynamicAttribute(String uri, Collection.Key name, Object value) {
241                if(attrs==null)attrs=new StructImpl();
242                Key key = KeyImpl.getInstance(StringUtil.trim(name.getString(),""));
243                attrs.setEL(key,value);
244        }
245
246        @Override
247        public int doStartTag() throws PageException    {
248                pc=pageContext;
249                switch(action) {
250                        case ACTION_JOIN:       
251                                doJoin();
252                        break;
253                        case ACTION_SLEEP:      
254                                required("thread", "sleep", "duration", duration,-1);   
255                                doSleep();
256                        break;
257                        case ACTION_TERMINATE:  
258                                required("thread", "terminate", "name", name);
259                                doTerminate();
260                        break;
261                        case ACTION_RUN:                
262                                required("thread", "run", "name", name);
263                                return EVAL_BODY_INCLUDE;
264                        
265                }
266                return SKIP_BODY;
267        }
268
269        @Override
270        public int doEndTag() throws PageException {
271                this.pc=pageContext;
272                //if(ACTION_RUN==action) doRun();
273                return EVAL_PAGE;
274        }
275        
276        public void register(Page currentPage, int threadIndex) throws PageException    {
277                if(ACTION_RUN!=action) return;
278                
279                if(((PageContextImpl)pc).getParentPageContext()!=null)
280                        throw new ApplicationException("could not create a thread within a child thread");
281                
282                try {
283                        Threads ts = pc.getThreadScope(lcName);
284                        
285                        if(type==TYPE_DAEMON){
286                                if(ts!=null)
287                                        throw new ApplicationException("could not create a thread with the name ["+name+"]. name must be unique within a request");
288                                ChildThreadImpl ct = new ChildThreadImpl((PageContextImpl) pc,currentPage,name,threadIndex,attrs,false);
289                                pc.setThreadScope(name,new ThreadsImpl(ct));
290                                ct.setPriority(priority);
291                                ct.setDaemon(false);
292                                ct.start();
293                        }
294                        else {
295                                ChildThreadImpl ct = new ChildThreadImpl((PageContextImpl) pc,currentPage,name,threadIndex,attrs,true);
296                                ct.setPriority(priority);
297                                ((ConfigImpl)pc.getConfig()).getSpoolerEngine().add(new ChildSpoolerTask(ct,plans));
298                        }
299                        
300                } 
301                catch (Throwable t) {
302                        throw Caster.toPageException(t);
303                }
304                finally {
305                        pc.reuse(this);// this method is not called from template when type is run, a call from template is to early,
306                }
307        }
308        
309        private void doSleep() throws ExpressionException {
310                if(duration>=0) {
311                        SystemUtil.sleep(duration);
312                }
313                else throw new ExpressionException("The attribute duration must be greater or equal than 0, now ["+duration+"]");
314                
315        }
316
317    private void doJoin() throws ApplicationException {
318        PageContextImpl mpc=(PageContextImpl)getMainPageContext(pc);
319                
320        String[] names;
321        if(lcName==null) {
322                names=mpc.getThreadScopeNames();
323        }
324        else names=ListUtil.listToStringArray(lcName, ',');
325        
326        ChildThread ct;
327        Threads ts;
328        long start=System.currentTimeMillis(),_timeout=timeout>0?timeout:-1;
329        
330        for(int i=0;i<names.length;i++) {
331                if(StringUtil.isEmpty(names[i],true))continue;
332                //PageContextImpl mpc=(PageContextImpl)getMainPageContext(pc);
333                ts = mpc.getThreadScope(names[i]);
334                if(ts==null)
335                        throw new ApplicationException("there is no thread running with the name ["+names[i]+"], only the following threads existing ["+ListUtil.arrayToList(mpc.getThreadScopeNames(),", ")+"]");
336                ct=ts.getChildThread();
337                
338                if(ct.isAlive()) {
339                        try {
340                                        if(_timeout!=-1)ct.join(_timeout);
341                                        else ct.join();
342                                } 
343                        catch (InterruptedException e) {}
344                }
345                if(_timeout!=-1){
346                        _timeout=_timeout-(System.currentTimeMillis()-start);
347                        if(_timeout<1) break;
348                }
349        }
350        
351    }
352        private void doTerminate() throws ApplicationException {
353                PageContextImpl mpc=(PageContextImpl)getMainPageContext(pc);
354                
355                Threads ts = mpc.getThreadScope(lcName);
356                
357                if(ts==null)
358                        throw new ApplicationException("there is no thread running with the name ["+name+"]");
359                ChildThread ct = ts.getChildThread();
360                
361                if(ct.isAlive()){
362                        ct.terminated();
363                        SystemUtil.stop(ct,ThreadLocalPageContext.getConfig(pageContext).getApplicationLogger());
364                }
365                
366        }
367
368        private PageContext getMainPageContext(PageContext pc) {
369                if(pc==null)pc=pageContext;
370                if(pc.getParentPageContext()==null) return pc;
371                return pc.getParentPageContext();
372        }
373
374        @Override
375        public void doInitBody()        {
376                
377        }
378
379        @Override
380        public int doAfterBody()        {
381                return SKIP_BODY;
382        }
383
384        /**
385         * sets if has body or not
386         * @param hasBody
387         */
388        public void hasBody(boolean hasBody) {
389            
390        }
391}