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.type.scope.storage;
020
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.HashSet;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029import lucee.commons.lang.RandomUtil;
030import lucee.commons.lang.SizeOf;
031import lucee.commons.lang.StringUtil;
032import lucee.runtime.PageContext;
033import lucee.runtime.config.Config;
034import lucee.runtime.dump.DumpData;
035import lucee.runtime.dump.DumpProperties;
036import lucee.runtime.engine.ThreadLocalPageContext;
037import lucee.runtime.exp.PageException;
038import lucee.runtime.listener.ApplicationContext;
039import lucee.runtime.op.Duplicator;
040import lucee.runtime.type.Collection;
041import lucee.runtime.type.Sizeable;
042import lucee.runtime.type.Struct;
043import lucee.runtime.type.StructImpl;
044import lucee.runtime.type.dt.DateTime;
045import lucee.runtime.type.dt.DateTimeImpl;
046import lucee.runtime.type.util.CollectionUtil;
047import lucee.runtime.type.util.KeyConstants;
048import lucee.runtime.type.util.StructSupport;
049import lucee.runtime.type.util.StructUtil;
050
051public abstract class StorageScopeImpl extends StructSupport implements StorageScope,Sizeable {
052
053        public static Collection.Key CFID=KeyConstants._cfid;
054        public static Collection.Key CFTOKEN=KeyConstants._cftoken;
055        public static Collection.Key URLTOKEN=KeyConstants._urltoken;
056        public static Collection.Key LASTVISIT=KeyConstants._lastvisit;
057        public static Collection.Key HITCOUNT=KeyConstants._hitcount;
058        public static Collection.Key TIMECREATED=KeyConstants._timecreated;
059        public static Collection.Key SESSION_ID=KeyConstants._sessionid;
060
061
062        private static int _id=0;
063        private int id=0;
064
065        private static final long serialVersionUID = 7874930250042576053L;
066        private static Set<Collection.Key> FIX_KEYS=new HashSet<Collection.Key>();
067        static {
068                FIX_KEYS.add(CFID);
069                FIX_KEYS.add(CFTOKEN);
070                FIX_KEYS.add(URLTOKEN);
071                FIX_KEYS.add(LASTVISIT);
072                FIX_KEYS.add(HITCOUNT);
073                FIX_KEYS.add(TIMECREATED);
074        }
075        
076
077        protected static Set<Collection.Key> ignoreSet=new HashSet<Collection.Key>();
078        static {
079                ignoreSet.add(CFID);
080                ignoreSet.add(CFTOKEN);
081                ignoreSet.add(URLTOKEN);
082        }
083        
084        
085        protected boolean isinit=true;
086        protected Struct sct;
087        protected long lastvisit;
088        protected DateTime _lastvisit;
089        protected int hitcount=0;
090        protected DateTime timecreated;
091        private boolean hasChanges=false;
092        private String strType;
093        private int type;
094        private long timeSpan=-1;
095        private String storage;
096        private Map<String, String> tokens; 
097        
098        
099        /**
100         * Constructor of the class
101         * @param sct
102         * @param timecreated
103         * @param _lastvisit
104         * @param lastvisit
105         * @param hitcount
106         */
107        public StorageScopeImpl(Struct sct, DateTime timecreated, DateTime _lastvisit, long lastvisit, int hitcount,String strType,int type) {
108                this.sct=sct;
109                this.timecreated=timecreated;
110                if(_lastvisit==null)    this._lastvisit=timecreated;
111                else                                    this._lastvisit=_lastvisit;
112                
113                if(lastvisit==-1)               this.lastvisit=this._lastvisit.getTime();
114                else                                    this.lastvisit=lastvisit;
115
116                this.hitcount=hitcount;
117                this.strType=strType;
118                this.type=type;
119        id=++_id;
120        }
121        
122        /**
123         * Constructor of the class
124         * @param other
125         * @param deepCopy
126         */
127        public StorageScopeImpl(StorageScopeImpl other, boolean deepCopy) {
128                this.sct=(Struct)Duplicator.duplicate(other.sct,deepCopy);
129                this.timecreated=other.timecreated;
130                this._lastvisit=other._lastvisit;
131                this.hitcount=other.hitcount;
132                this.isinit=other.isinit;
133                this.lastvisit=other.lastvisit;
134                this.strType=other.strType;
135                this.type=other.type;
136                this.timeSpan=other.timeSpan;
137        id=++_id;
138        }
139
140        @Override
141        public void touchBeforeRequest(PageContext pc) {
142                
143                hasChanges=false;
144                setTimeSpan(pc);
145                
146                
147                //lastvisit=System.currentTimeMillis();
148                if(sct==null) sct=new StructImpl();
149                sct.setEL(KeyConstants._cfid, pc.getCFID());
150                sct.setEL(KeyConstants._cftoken, pc.getCFToken());
151                sct.setEL(URLTOKEN, pc.getURLToken());
152                sct.setEL(LASTVISIT, _lastvisit);
153                _lastvisit=new DateTimeImpl(pc.getConfig());
154                lastvisit=System.currentTimeMillis();
155                
156                if(type==SCOPE_CLIENT){
157                        sct.setEL(HITCOUNT, new Double(hitcount++));
158                }
159                else {
160                        sct.setEL(SESSION_ID, pc.getApplicationContext().getName()+"_"+pc.getCFID()+"_"+pc.getCFToken());
161                }
162                sct.setEL(TIMECREATED, timecreated);
163        }
164
165        public void resetEnv(PageContext pc){
166                _lastvisit=new DateTimeImpl(pc.getConfig());
167                timecreated=new DateTimeImpl(pc.getConfig());
168                touchBeforeRequest(pc);
169                
170        }
171        
172        void setTimeSpan(PageContext pc) {
173                ApplicationContext ac=pc.getApplicationContext();
174                this.timeSpan=getType()==SCOPE_SESSION?
175                                ac.getSessionTimeout().getMillis():
176                                ac.getClientTimeout().getMillis();
177        }
178        
179        @Override
180        public void setMaxInactiveInterval(int interval) {
181                this.timeSpan=interval*1000L;
182        }
183
184        @Override
185        public int getMaxInactiveInterval() {
186                return (int)(this.timeSpan/1000L);
187        }
188        
189        @Override
190        public final boolean isInitalized() {
191                return isinit;
192        }
193        
194        @Override
195        public final void initialize(PageContext pc) {
196                // StorageScopes need only request initialisation no global init, they are not reused;
197        }
198        
199        @Override
200        public void touchAfterRequest(PageContext pc) {
201                
202                sct.setEL(LASTVISIT, _lastvisit);
203                sct.setEL(TIMECREATED, timecreated);
204                
205                if(type==SCOPE_CLIENT){
206                        sct.setEL(HITCOUNT, new Double(hitcount));
207                }
208        }
209        
210        @Override
211        public final void release() {
212                clear();
213                isinit=false;
214        }
215        
216        @Override
217        public final void release(PageContext pc) {
218                clear();
219                isinit=false;
220        }
221        
222        
223        /**
224         * @return returns if the scope is empty or not, this method ignore the "constant" entries of the scope (cfid,cftoken,urltoken)
225         */
226        public boolean hasContent() {
227                if(sct.size()==(type==SCOPE_CLIENT?6:5) && sct.containsKey(URLTOKEN) && sct.containsKey(KeyConstants._cftoken) && sct.containsKey(KeyConstants._cfid)) {
228                        return false;
229                }
230                return true;
231        }
232        
233        @Override
234        public void  clear() {
235                sct.clear();
236        }
237
238        @Override
239        public boolean containsKey(Key key) {
240                return sct.containsKey(key);
241        }
242
243        @Override
244        public Object get(Key key) throws PageException {
245                return sct.get(key);
246        }
247
248        @Override
249        public Object get(Key key, Object defaultValue) {
250                return sct.get(key, defaultValue);
251        }
252
253        @Override
254        public Iterator<Collection.Key> keyIterator() {
255                return sct.keyIterator();
256        }
257    
258    @Override
259        public Iterator<String> keysAsStringIterator() {
260        return sct.keysAsStringIterator();
261    }
262        
263        @Override
264        public Iterator<Entry<Key, Object>> entryIterator() {
265                return sct.entryIterator();
266        }
267        
268        @Override
269        public Iterator<Object> valueIterator() {
270                return sct.valueIterator();
271        }
272
273        @Override
274        public lucee.runtime.type.Collection.Key[] keys() {
275                return CollectionUtil.keys(this);
276        }
277
278
279        @Override
280        public Object remove(Key key) throws PageException {
281                hasChanges=true;
282                return sct.remove(key);
283        }
284
285        @Override
286        public Object removeEL(Key key) {
287                hasChanges=true;
288                return sct.removeEL(key);
289        }
290
291        @Override
292        public Object set(Key key, Object value) throws PageException {
293                hasChanges=true;
294                return sct.set(key, value);
295        }
296
297        @Override
298        public Object setEL(Key key, Object value) {
299                hasChanges=true;
300                return sct.setEL(key, value);
301        }
302
303        @Override
304        public int size() {
305                return sct.size();
306        }
307
308
309        @Override
310        public boolean castToBooleanValue() throws PageException {
311                return sct.castToBooleanValue();
312        }
313    
314    @Override
315    public Boolean castToBoolean(Boolean defaultValue) {
316        return sct.castToBoolean(defaultValue);
317    }
318
319        @Override
320        public DateTime castToDateTime() throws PageException {
321                return sct.castToDateTime();
322        }
323    
324    @Override
325    public DateTime castToDateTime(DateTime defaultValue) {
326        return sct.castToDateTime(defaultValue);
327    }
328
329        @Override
330        public double castToDoubleValue() throws PageException {
331                return sct.castToDoubleValue();
332        }
333    
334    @Override
335    public double castToDoubleValue(double defaultValue) {
336        return sct.castToDoubleValue(defaultValue);
337    }
338
339        @Override
340        public String castToString() throws PageException {
341                return sct.castToString();
342        }
343        
344        @Override
345        public String castToString(String defaultValue) {
346                return sct.castToString(defaultValue);
347        }
348
349        @Override
350        public int compareTo(boolean b) throws PageException {
351                return sct.compareTo(b);
352        }
353
354        @Override
355        public int compareTo(DateTime dt) throws PageException {
356                return sct.compareTo(dt);
357        }
358
359        @Override
360        public int compareTo(double d) throws PageException {
361                return sct.compareTo(d);
362        }
363
364        @Override
365        public int compareTo(String str) throws PageException {
366                return sct.compareTo(str);
367        }
368
369        @Override
370        public long lastVisit() {
371                return lastvisit;
372        }
373
374        public Collection.Key[] pureKeys() {
375                List<Collection.Key> keys=new ArrayList<Collection.Key>();
376                Iterator<Key> it = keyIterator();
377                Collection.Key key;
378                while(it.hasNext()){
379                        key=it.next();
380                        if(!FIX_KEYS.contains(key))keys.add(key);
381                }
382                return keys.toArray(new Collection.Key[keys.size()]);
383        }
384        
385        @Override
386        public void store(Config config){
387                //do nothing
388        }
389
390        @Override
391        public void unstore(Config config){
392                //do nothing
393        }
394
395        /**
396         * @return the hasChanges
397         */
398        public boolean hasChanges() {
399                return hasChanges;
400        }
401        
402
403        @Override
404        public boolean containsValue(Object value) {
405                return sct.containsValue(value);
406        }
407
408        @Override
409        public java.util.Collection values() {
410                return sct.values();
411        }
412        
413        @Override
414        public long sizeOf() {
415                return SizeOf.size(sct);
416        }
417        
418        public final int getType() {
419                return type;
420        }
421
422        public final String getTypeAsString() {
423                return strType;
424        }
425        
426        
427
428        @Override
429        public final DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) {
430                return StructUtil.toDumpTable(this, StringUtil.ucFirst(getTypeAsString())+" Scope ("+getStorageType()+")", pageContext, maxlevel, dp);
431        }
432        
433
434        public long getLastAccess() { return lastvisit;}
435        public long getTimeSpan() { return timeSpan;}
436        
437        
438        public void touch() {
439                lastvisit=System.currentTimeMillis();
440                _lastvisit=new DateTimeImpl(ThreadLocalPageContext.getConfig());
441        }
442        
443        public boolean isExpired() {
444            return (getLastAccess()+getTimeSpan())<System.currentTimeMillis();
445    }
446
447
448        
449        @Override
450        public void setStorage(String storage) {
451                this.storage=storage;
452        }
453
454        @Override
455        public String getStorage() {
456                return storage;
457        }
458        
459        public static String encode(String input) {
460                int len=input.length();
461                StringBuilder sb=new StringBuilder();
462                char c;
463                for(int i=0;i<len;i++){
464                        c=input.charAt(i);
465                        if((c>='0' && c<='9') || (c>='a' && c<='z') || (c>='A' && c<='Z') || c=='_' || c=='-')
466                                sb.append(c);
467                        else {
468                                sb.append('$');
469                                sb.append(Integer.toString((c),Character.MAX_RADIX));
470                                sb.append('$');
471                        }
472                }
473                
474                return sb.toString();
475        }
476
477        public static String decode(String input) {
478                int len=input.length();
479                StringBuilder sb=new StringBuilder();
480                char c;
481                int ni;
482                for(int i=0;i<len;i++){
483                        c=input.charAt(i);
484                        if(c=='$') {
485                                ni=input.indexOf('$',i+1);
486                                sb.append((char)Integer.parseInt(input.substring(i+1,ni),Character.MAX_RADIX));
487                                i=ni;
488                        }
489                                
490                        else {
491                                sb.append(c);
492                        }
493                }
494                return sb.toString();
495        }
496        
497    public int _getId() {
498        return id;
499    }
500    
501
502        
503        public long getCreated() {
504                return timecreated==null?0:timecreated.getTime();
505        }
506        
507        @Override
508        public synchronized String generateToken(String key, boolean forceNew) {
509        if(tokens==null) 
510                tokens = new HashMap<String,String>();
511        
512        // get existing
513        String token;
514        if(!forceNew) {
515                token = tokens.get(key);
516                if(token!=null) return token;
517        }
518        
519        // create new one
520        token = RandomUtil.createRandomStringLC(40);
521        tokens.put(key, token);
522        return token;
523    }
524        
525        @Override
526        public synchronized boolean verifyToken(String token, String key) {
527                if(tokens==null) return false;
528        String _token = tokens.get(key);
529        return _token!=null && _token.equalsIgnoreCase(token);
530    }
531}