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