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}