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