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    }