001 package railo.runtime.tag; 002 003 import railo.runtime.PageContext; 004 import railo.runtime.PageContextImpl; 005 import railo.runtime.config.ConfigWebImpl; 006 import railo.runtime.debug.ActiveLock; 007 import railo.runtime.exp.ApplicationException; 008 import railo.runtime.exp.LockException; 009 import railo.runtime.exp.PageException; 010 import railo.runtime.ext.tag.BodyTagTryCatchFinallyImpl; 011 import railo.runtime.lock.LockData; 012 import railo.runtime.lock.LockManager; 013 import railo.runtime.lock.LockTimeoutException; 014 import railo.runtime.op.Caster; 015 import railo.runtime.type.Struct; 016 import railo.runtime.type.StructImpl; 017 import railo.runtime.type.dt.TimeSpan; 018 import railo.runtime.type.scope.ApplicationImpl; 019 import railo.runtime.type.scope.RequestImpl; 020 import railo.runtime.type.scope.ServerImpl; 021 022 /** 023 * Provides two types of locks to ensure the integrity of shared data: Exclusive lock and Read-only 024 * lock. An exclusive lock single-threads access to the CFML constructs in its body. Single-threaded access 025 * implies that the body of the tag can be executed by at most one request at a time. A request executing 026 * inside a cflock tag has an "exclusive lock" on the tag. No other requests can start executing inside the 027 * tag while a request has an exclusive lock. CFML issues exclusive locks on a first-come, first-served 028 * basis. A read-only lock allows multiple requests to access the CFML constructs inside its body concurrently. 029 * Therefore, read-only locks should be used only when the shared data is read only and not modified. If another 030 * request already has an exclusive lock on the shared data, the request waits for the exclusive lock to be 031 * released. 032 * 033 * 034 * 035 **/ 036 public final class Lock extends BodyTagTryCatchFinallyImpl { 037 038 039 040 private static final short SCOPE_NONE=0; 041 private static final short SCOPE_SERVER=1; 042 private static final short SCOPE_APPLICATION=2; 043 private static final short SCOPE_SESSION=3; 044 private static final short SCOPE_REQUEST=4; 045 046 private String id="anonymous"; 047 048 /** Specifies the maximum amount of time, in seconds, to wait to obtain a lock. If a lock can be 049 ** obtained within the specified period, execution continues inside the body of the tag. Otherwise, the 050 ** behavior depends on the value of the throwOnTimeout attribute. */ 051 private int timeoutInMillis; 052 053 /** readOnly or Exclusive. Specifies the type of lock: read-only or exclusive. Default is Exclusive. 054 ** A read-only lock allows more than one request to read shared data. An exclusive lock allows only one 055 ** request to read or write to shared data. */ 056 private short type=LockManager.TYPE_EXCLUSIVE; 057 058 /** Specifies the scope as one of the following: Application, Server, or Session. This attribute is mutually 059 ** exclusive with the name attribute. */ 060 private short scope=SCOPE_NONE; 061 062 /** Yes or No. Specifies how timeout conditions are handled. If the value is Yes, an exception is 063 ** generated to provide notification of the timeout. If the value is No, execution continues past the 064 ** cfclock tag. Default is Yes. */ 065 private boolean throwontimeout = true; 066 067 /** Specifies the name of the lock. */ 068 private String name; 069 070 private LockManager manager; 071 private LockData data=null; 072 private long start; 073 074 @Override 075 public void release() { 076 super.release(); 077 type = LockManager.TYPE_EXCLUSIVE; 078 scope = SCOPE_NONE; 079 throwontimeout = true; 080 name = null; 081 manager=null; 082 this.data=null; 083 id="anonymous"; 084 timeoutInMillis=0; 085 } 086 087 088 /** 089 * @param id the id to set 090 */ 091 public void setId(String id) { 092 this.id = id; 093 } 094 095 /** set the value timeout 096 * Specifies the maximum amount of time, in seconds, to wait to obtain a lock. If a lock can be 097 * obtained within the specified period, execution continues inside the body of the tag. Otherwise, the 098 * behavior depends on the value of the throwOnTimeout attribute. 099 * @param timeout value to set 100 **/ 101 public void setTimeout(Object oTimeout) throws PageException { 102 if(oTimeout instanceof TimeSpan) 103 this.timeoutInMillis=(int)((TimeSpan)oTimeout).getMillis(); 104 else 105 this.timeoutInMillis = (int)(Caster.toDoubleValue(oTimeout)*1000D); 106 //print.out(Caster.toString(timeoutInMillis)); 107 } 108 public void setTimeout(double timeout) { 109 this.timeoutInMillis = (int)(timeout*1000D); 110 } 111 112 /** set the value type 113 * readOnly or Exclusive. Specifies the type of lock: read-only or exclusive. Default is Exclusive. 114 * A read-only lock allows more than one request to read shared data. An exclusive lock allows only one 115 * request to read or write to shared data. 116 * @param type value to set 117 * @throws ApplicationException 118 **/ 119 public void setType(String type) throws ApplicationException { 120 type=type.toLowerCase().trim(); 121 122 if(type.equals("exclusive")) { 123 this.type=LockManager.TYPE_EXCLUSIVE; 124 } 125 else if(type.startsWith("read")){ 126 this.type=LockManager.TYPE_READONLY; 127 } 128 else 129 throw new ApplicationException("invalid value ["+type+"] for attribute [type] from tag [lock]", 130 "valid values are [exclusive,read-only]"); 131 } 132 133 /** set the value scope 134 * Specifies the scope as one of the following: Application, Server, or Session. This attribute is mutually 135 * exclusive with the name attribute. 136 * @param scope value to set 137 * @throws ApplicationException 138 **/ 139 public void setScope(String scope) throws ApplicationException { 140 scope=scope.toLowerCase().trim(); 141 142 if(scope.equals("server"))this.scope=SCOPE_SERVER; 143 else if(scope.equals("application"))this.scope=SCOPE_APPLICATION; 144 else if(scope.equals("session"))this.scope=SCOPE_SESSION; 145 else if(scope.equals("request"))this.scope=SCOPE_REQUEST; 146 else 147 throw new ApplicationException("invalid value ["+scope+"] for attribute [scope] from tag [lock]", 148 "valid values are [server,application,session]"); 149 } 150 151 /** set the value throwontimeout 152 * Yes or No. Specifies how timeout conditions are handled. If the value is Yes, an exception is 153 * generated to provide notification of the timeout. If the value is No, execution continues past the 154 * cfclock tag. Default is Yes. 155 * @param throwontimeout value to set 156 **/ 157 public void setThrowontimeout(boolean throwontimeout) { 158 this.throwontimeout = throwontimeout; 159 } 160 161 /** set the value name 162 * @param name value to set 163 * @throws ApplicationException 164 **/ 165 public void setName(String name) throws ApplicationException { 166 if(name==null) return; 167 this.name = name.trim(); 168 if(name.length()==0)throw new ApplicationException("invalid attribute definition","attribute [name] can't be a empty string"); 169 } 170 171 @Override 172 public int doStartTag() throws PageException { 173 //if(timeoutInMillis==0)timeoutInMillis=30000; 174 //print.out("doStartTag"); 175 manager=pageContext.getConfig().getLockManager(); 176 // check attributes 177 if(name!=null && scope!=SCOPE_NONE) { 178 throw new LockException( 179 LockException.OPERATION_CREATE, 180 this.name, 181 "invalid attribute combination", 182 "attribute [name] and [scope] can't be used together"); 183 } 184 if(name==null && scope==SCOPE_NONE) { 185 name="id-"+id; 186 } 187 188 String lockType = null; 189 if(name==null) { 190 String cid=pageContext.getConfig().getId(); 191 // Session 192 if(scope==SCOPE_REQUEST){ 193 lockType="request"; 194 name="__request_"+cid+"__"+ ((RequestImpl)pageContext.requestScope())._getId(); 195 } 196 // Session 197 else if(scope==SCOPE_SESSION){ 198 lockType="session"; 199 name="__session_"+cid+"__"+ pageContext.sessionScope()._getId(); 200 } 201 // Application 202 else if(scope==SCOPE_APPLICATION){ 203 lockType="application"; 204 name="__application_"+cid+"__"+((ApplicationImpl)pageContext.applicationScope())._getId(); 205 } 206 // Server 207 else if(scope==SCOPE_SERVER){ 208 lockType="server"; 209 name="__server_"+((ServerImpl)pageContext.serverScope())._getId(); 210 } 211 } 212 Struct cflock=new StructImpl(); 213 cflock.set("succeeded",Boolean.TRUE); 214 cflock.set("errortext",""); 215 pageContext.variablesScope().set("cflock",cflock); 216 start=System.nanoTime(); 217 try { 218 ((PageContextImpl)pageContext).setActiveLock(new ActiveLock(type,name,timeoutInMillis)); // this has to be first, otherwise LockTimeoutException has nothing to release 219 data = manager.lock(type,name,timeoutInMillis,pageContext.getId()); 220 } 221 catch (LockTimeoutException e) { 222 _release(pageContext,System.nanoTime()-start); 223 //print.out("LockTimeoutException"); 224 name=null; 225 String errorText=e.getMessage(); 226 if(lockType!=null)errorText=("a timeout occurred on ["+lockType+"] scope lock"); 227 228 cflock.set("succeeded",Boolean.FALSE); 229 cflock.set("errortext",errorText); 230 231 if(throwontimeout) throw new LockException( 232 LockException.OPERATION_TIMEOUT, 233 this.name, 234 errorText); 235 236 return SKIP_BODY; 237 } 238 catch (InterruptedException e) { 239 _release(pageContext,System.nanoTime()-start); 240 cflock.set("succeeded",Boolean.FALSE); 241 cflock.set("errortext",e.getMessage()); 242 243 if(throwontimeout) throw Caster.toPageException(e); 244 245 return SKIP_BODY; 246 247 248 } 249 250 return EVAL_BODY_INCLUDE; 251 } 252 253 private void _release(PageContext pc, long exe) { 254 ActiveLock al = ((PageContextImpl)pc).releaseActiveLock(); 255 // listener 256 ((ConfigWebImpl)pc.getConfig()).getActionMonitorCollector() 257 .log(pageContext, "lock", "Lock", exe, al.name+":"+al.timeoutInMillis); 258 259 } 260 261 262 @Override 263 public void doFinally() { 264 _release(pageContext,System.nanoTime()-start); 265 if(name!=null)manager.unlock(data); 266 } 267 268 269 }