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    }