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 }