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