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;
020
021import java.util.Date;
022import java.util.Iterator;
023import java.util.Map;
024import java.util.Map.Entry;
025
026import javax.servlet.http.HttpSession;
027
028import lucee.print;
029import lucee.commons.collection.MapFactory;
030import lucee.commons.io.log.Log;
031import lucee.commons.lang.ExceptionUtil;
032import lucee.commons.lang.SizeOf;
033import lucee.commons.lang.StringUtil;
034import lucee.commons.lang.types.RefBoolean;
035import lucee.commons.lang.types.RefBooleanImpl;
036import lucee.runtime.CFMLFactoryImpl;
037import lucee.runtime.PageContext;
038import lucee.runtime.PageContextImpl;
039import lucee.runtime.cache.CacheConnection;
040import lucee.runtime.config.Config;
041import lucee.runtime.config.ConfigImpl;
042import lucee.runtime.db.DataSource;
043import lucee.runtime.exp.ApplicationException;
044import lucee.runtime.exp.ExceptionHandler;
045import lucee.runtime.exp.ExpressionException;
046import lucee.runtime.exp.PageException;
047import lucee.runtime.exp.PageRuntimeException;
048import lucee.runtime.functions.cache.Util;
049import lucee.runtime.interpreter.VariableInterpreter;
050import lucee.runtime.listener.ApplicationContext;
051import lucee.runtime.listener.ApplicationListener;
052import lucee.runtime.op.Caster;
053import lucee.runtime.type.KeyImpl;
054import lucee.runtime.type.Struct;
055import lucee.runtime.type.StructImpl;
056import lucee.runtime.type.scope.client.ClientCache;
057import lucee.runtime.type.scope.client.ClientCookie;
058import lucee.runtime.type.scope.client.ClientDatasource;
059import lucee.runtime.type.scope.client.ClientFile;
060import lucee.runtime.type.scope.client.ClientMemory;
061import lucee.runtime.type.scope.session.SessionCache;
062import lucee.runtime.type.scope.session.SessionCookie;
063import lucee.runtime.type.scope.session.SessionDatasource;
064import lucee.runtime.type.scope.session.SessionFile;
065import lucee.runtime.type.scope.session.SessionMemory;
066import lucee.runtime.type.scope.storage.MemoryScope;
067import lucee.runtime.type.scope.storage.StorageScope;
068import lucee.runtime.type.scope.storage.StorageScopeCleaner;
069import lucee.runtime.type.scope.storage.StorageScopeEngine;
070import lucee.runtime.type.scope.storage.clean.DatasourceStorageScopeCleaner;
071import lucee.runtime.type.scope.storage.clean.FileStorageScopeCleaner;
072import lucee.runtime.type.wrap.MapAsStruct;
073import lucee.runtime.util.PageContextUtil;
074
075import org.safehaus.uuid.UUIDGenerator;
076
077/**
078 * Scope Context handle Apllication and Session Scopes
079 */
080public final class ScopeContext {
081
082        private static final int MINUTE = 60*1000;
083        private static final long CLIENT_MEMORY_TIMESPAN =  5*MINUTE;
084        private static final long SESSION_MEMORY_TIMESPAN =  5*MINUTE;
085        
086        private static UUIDGenerator generator = UUIDGenerator.getInstance();
087        private Map<String,Map<String,Scope>> cfSessionContextes=MapFactory.<String,Map<String,Scope>>getConcurrentMap();
088        private Map<String,Map<String,Scope>> cfClientContextes=MapFactory.<String,Map<String,Scope>>getConcurrentMap();
089        private Map<String,Application> applicationContextes=MapFactory.<String,Application>getConcurrentMap();
090
091        private int maxSessionTimeout=0;
092
093        private static Cluster cluster;
094        private static Server server=null;
095        
096
097        private StorageScopeEngine client;
098        private StorageScopeEngine session;
099        private CFMLFactoryImpl factory;
100        private Log log;
101        
102        
103        
104        public ScopeContext(CFMLFactoryImpl factory) {
105                this.factory=factory;
106        }
107
108        /**
109         * @return the log
110         */
111        private Log getLog() {
112                if(log==null) {
113                        this.log=((ConfigImpl)factory.getConfig()).getLog("scope");
114                        
115                }
116                return log;
117        }
118        
119        public void info(String msg) {info(getLog(), msg);}
120        public void error(String msg) {error(getLog(),msg);}
121        public void error(Throwable t) {
122                ExceptionUtil.rethrowIfNecessary(t);
123                error(getLog(), t);
124        }
125        
126        public static void info(Log log,String msg) {
127                if(log!=null)log.log(Log.LEVEL_INFO,"scope-context", msg);
128        }
129        
130
131        public static void error(Log log,String msg) {
132                if(log!=null)log.log(Log.LEVEL_ERROR,"scope-context", msg);
133        }
134        
135        public static void error(Log log,Throwable t) {
136                if(log!=null)log.log(Log.LEVEL_ERROR,"scope-context",ExceptionUtil.getStacktrace(t, true));
137        }
138        
139
140        /**
141         * return a map matching key from given map
142         * @param parent
143         * @param key key of the map
144         * @return matching map, if no map exist it willbe one created
145         */
146        private Map<String,Scope> getSubMap(Map<String,Map<String,Scope>> parent, String key) {
147                        
148                Map<String,Scope> context=parent.get(key);
149                if(context!=null) return context;
150                
151                context = MapFactory.<String,Scope>getConcurrentMap();
152                parent.put(key,context);
153                return context;
154                
155        }
156        
157        /**
158         * return the server Scope for this context
159         * @param pc
160         * @return server scope
161         */
162        public static Server getServerScope(PageContext pc) {
163            if(server==null) {
164                server=new ServerImpl(pc);
165            }
166                return server;
167        }
168        
169        /* *
170         * Returns the current Cluster Scope, if there is no current Cluster Scope, this method returns null. 
171         * @param pc
172         * @param create
173         * @return
174         * @throws SecurityException
175         * /
176        public static Cluster getClusterScope() {
177            return cluster;
178        }*/
179
180        /**
181         * Returns the current Cluster Scope, if there is no current Cluster Scope and create is true, returns a new Cluster Scope.
182         * If create is false and the request has no valid Cluster Scope, this method returns null. 
183         * @param pc
184         * @param create
185         * @return
186         * @throws PageException 
187         */
188        public static Cluster getClusterScope(Config config, boolean create) throws PageException {
189                if(cluster==null && create) {
190                cluster=((ConfigImpl)config).createClusterScope();
191                 
192            }
193                return cluster;
194        }
195        
196        public static void clearClusterScope() {
197                cluster=null;
198        }
199        
200        
201        public Client getClientScope(PageContext pc) throws PageException {
202                ApplicationContext appContext = pc.getApplicationContext(); 
203                // get Context
204                        Map<String, Scope> context = getSubMap(cfClientContextes,appContext.getName());
205                        
206                // get Client
207                        boolean isMemory=false;
208                        String storage = appContext.getClientstorage();
209                        if(StringUtil.isEmpty(storage,true)){
210                                storage=ConfigImpl.DEFAULT_STORAGE_CLIENT;
211                        }
212                        else if("ram".equalsIgnoreCase(storage)) {
213                                storage="memory";
214                                isMemory=true;
215                        }
216                        else if("registry".equalsIgnoreCase(storage)) {
217                                storage="file";
218                        }
219                        else {
220                                storage=storage.toLowerCase();
221                                if("memory".equals(storage))isMemory=true;
222                        }
223                        
224                        //final boolean doMemory=isMemory || !appContext.getClientCluster();
225                        Client existing=(Client) context.get(pc.getCFID());
226                        Client client=appContext.getClientCluster()?null:existing;
227                        //client=doMemory?(Client) context.get(pc.getCFID()):null;
228                        
229                        
230                        if(client==null || client.isExpired() || !client.getStorage().equalsIgnoreCase(storage)) {
231                                if("file".equals(storage)){
232                                        client=ClientFile.getInstance(appContext.getName(),pc,getLog());
233                                }
234                                else if("cookie".equals(storage))
235                                        client=ClientCookie.getInstance(appContext.getName(),pc,getLog());
236                                else if("memory".equals(storage)){
237                                        if(existing!=null) client=existing;
238                                        client=ClientMemory.getInstance(pc,getLog());
239                                }
240                                else{
241                                        DataSource ds = ((PageContextImpl)pc).getDataSource(storage,null);
242                                        if(ds!=null)client=ClientDatasource.getInstance(storage,pc,getLog());
243                                        else client=ClientCache.getInstance(storage,appContext.getName(),pc,existing,getLog(),null);
244                                        
245                                        if(client==null){
246                                                // datasource not enabled for storage
247                                                if(ds!=null)
248                                                        throw new ApplicationException("datasource ["+storage+"] is not enabled to be used as session/client storage, you have to enable it in the lucee administrator.");
249                                                
250                                                CacheConnection cc = Util.getCacheConnection(pc.getConfig(),storage,null);
251                                                if(cc!=null) 
252                                                        throw new ApplicationException("cache ["+storage+"] is not enabled to be used  as a session/client storage, you have to enable it in the lucee administrator.");
253                                                
254                                                throw new ApplicationException("there is no cache or datasource with name ["+storage+"] defined.");
255                                        }
256                                        
257                                }
258                                client.setStorage(storage);
259                                context.put(pc.getCFID(),client);
260                        }
261                        else
262                                getLog().log(Log.LEVEL_INFO,"scope-context", "use existing client scope for "+appContext.getName()+"/"+pc.getCFID()+" from storage "+storage);
263                        
264                        client.touchBeforeRequest(pc);
265                        return client;
266        }
267        
268        public Client getClientScopeEL(PageContext pc) {
269                try {
270                        return getClientScope(pc);
271                } catch (PageException pe) {
272                        throw new PageRuntimeException(pe);
273                }
274        }
275        
276        /*public ClientPlus getClientScopeEL(PageContext pc) {
277                ClientPlus client=null;
278                ApplicationContext appContext = pc.getApplicationContext(); 
279                // get Context
280                        Map context=getSubMap(cfClientContextes,appContext.getName());
281                        
282                // get Client
283                        String storage = appContext.getClientstorage();
284                        if(!StringUtil.isEmpty(storage))storage=storage.toLowerCase();
285                        else storage="";
286                        
287                        client=(ClientPlus) context.get(pc.getCFID());
288                        if(client==null || client.isExpired() || !client.getStorageType().equalsIgnoreCase(storage)) {
289                                if(StringUtil.isEmpty(storage) || "file".equals(storage) || "registry".equals(storage)){
290                                        storage="file";
291                                        client=ClientFile.getInstance(appContext.getName(),pc,getLog());
292                                }
293                                else if("cookie".equals(storage))
294                                        client=ClientCookie.getInstance(appContext.getName(),pc,getLog());
295                                else if("memory".equals(storage) || "ram".equals(storage)){
296                                        //storage="ram";
297                                        client=ClientMemory.getInstance(pc,getLog());
298                                }
299                                else{
300                                        DataSource ds = ((ConfigImpl)pc.getConfig()).getDataSource(storage,null);
301                                        if(ds!=null)client=ClientDatasource.getInstanceEL(storage,pc,getLog());
302                                        else client=ClientCache.getInstanceEL(storage,appContext.getName(),pc,getLog());
303
304                                }
305                                client.setStorage(storage);
306                                context.put(pc.getCFID(),client);
307                        }
308                        else
309                                getLog().info("scope-context", "use existing client scope for "+appContext.getName()+"/"+pc.getCFID()+" from storage "+storage);
310                        
311                        
312                        client.initialize(pc);
313                        return client;
314        }*/
315
316
317
318        /**
319         * return the session count of all application contextes
320         * @return
321         */
322        public int getSessionCount(PageContext pc) {
323                if(pc.getSessionType()==Config.SESSION_TYPE_J2EE) return 0;
324                
325                Iterator<Entry<String, Map<String, Scope>>> it = cfSessionContextes.entrySet().iterator();
326                Entry<String, Map<String, Scope>> entry;
327                int count=0;
328                while(it.hasNext()) {
329                        entry = it.next();
330                        count+=getSessionCount(entry.getValue());
331                }
332                return count;
333        }
334        
335        /**
336         * return the session count of this application context
337         * @return
338         */
339        public int getAppContextSessionCount(PageContext pc) {
340                ApplicationContext appContext = pc.getApplicationContext(); 
341                if(pc.getSessionType()==Config.SESSION_TYPE_J2EE) return 0;
342
343                Map<String, Scope> context = getSubMap(cfSessionContextes,appContext.getName());
344                return getSessionCount(context);
345        }
346        
347        private int getSessionCount(Map<String, Scope> context) {
348                Iterator<Entry<String, Scope>> it = context.entrySet().iterator();
349                Entry<String, Scope> entry;
350                int count=0;
351                Session s;
352                while(it.hasNext()) {
353                        entry = it.next();
354                        s=(Session)entry.getValue();
355                        if(!s.isExpired())
356                                count++;
357                }
358                return count;
359        }
360        
361
362
363        /**
364         * return all session context of this application context
365         * @param pc
366         * @return
367         */
368        public Struct getAllSessionScopes(PageContext pc) {
369                return getAllSessionScopes(pc.getApplicationContext().getName());
370        }
371        
372        public Struct getAllApplicationScopes() {
373                Struct trg=new StructImpl();
374                StructImpl.copy(MapAsStruct.toStruct(applicationContextes, true), trg, false);
375                return trg;
376        }
377        
378        public Struct getAllCFSessionScopes() {
379                Struct trg=new StructImpl();
380                StructImpl.copy(MapAsStruct.toStruct(this.cfSessionContextes, true), trg, false);
381                return trg;
382        }
383        
384        /**
385         * return the size in bytes of all session contextes
386         * @return size in bytes
387         * @throws ExpressionException 
388         */
389        public long getScopesSize(int scope) throws ExpressionException {
390                if(scope==Scope.SCOPE_APPLICATION)return SizeOf.size(applicationContextes);
391                if(scope==Scope.SCOPE_CLUSTER)return SizeOf.size(cluster);
392                if(scope==Scope.SCOPE_SERVER)return SizeOf.size(server);
393                if(scope==Scope.SCOPE_SESSION)return SizeOf.size(this.cfSessionContextes);
394                if(scope==Scope.SCOPE_CLIENT)return SizeOf.size(this.cfClientContextes);
395                
396                throw new ExpressionException("can only return information of scope that are not request dependent");
397        }
398        
399        /**
400         * get all session contexts of given applicaton name
401         * @param pc
402         * @param appName
403         * @return
404         * @deprecated use instead getAllSessionScopes(String appName)
405         */
406        public Struct getAllSessionScopes(PageContext pc, String appName) {
407        return getAllSessionScopes(appName);
408        }
409        
410        /**
411         * get all session contexts of given applicaton name
412         * @param pc
413         * @param appName
414         * @return
415         */
416        public Struct getAllSessionScopes(String appName) {
417        //if(pc.getSessionType()==Config.SESSION_TYPE_J2EE)return new StructImpl();
418                return getAllSessionScopes(getSubMap(cfSessionContextes,appName),appName);
419        }
420        
421        private Struct getAllSessionScopes(Map<String,Scope> context, String appName) {
422                Iterator<Entry<String, Scope>> it = context.entrySet().iterator();
423                Entry<String, Scope> entry;
424                Struct sct=new StructImpl();
425                Session s;
426                while(it.hasNext()) {
427                        entry = it.next();
428                        s=(Session)entry.getValue();
429                        if(!s.isExpired())
430                                sct.setEL(KeyImpl.init(appName+"_"+entry.getKey()+"_0"), s);
431                }
432                return sct;
433        }
434
435        /**
436         * return the session Scope for this context (cfid,cftoken,contextname)
437         * @param pc PageContext 
438         * @return session matching the context
439         * @throws PageException
440         */
441        public Session getSessionScope(PageContext pc,RefBoolean isNew) throws PageException {
442        if(pc.getSessionType()==Config.SESSION_TYPE_CFML)return getCFSessionScope(pc,isNew);
443                return getJSessionScope(pc,isNew);
444        }
445        
446        public boolean hasExistingSessionScope(PageContext pc) {
447        if(pc.getSessionType()==Config.SESSION_TYPE_CFML)return hasExistingCFSessionScope(pc);
448                return hasExistingJSessionScope(pc);
449        }
450        
451        private boolean hasExistingJSessionScope(PageContext pc) {
452                HttpSession httpSession=pc.getSession();
453        if(httpSession==null) return false;
454        
455        Session session=(Session) httpSession.getAttribute(pc.getApplicationContext().getName());
456        return session instanceof JSession;
457        }
458        
459        
460        private boolean hasExistingCFSessionScope(PageContext pc) {
461                
462                ApplicationContext appContext = pc.getApplicationContext(); 
463                // get Context
464                        Map<String, Scope> context = getSubMap(cfSessionContextes,appContext.getName());
465                        
466                // get Session
467                        String storage = appContext.getSessionstorage();
468                        if(StringUtil.isEmpty(storage,true))storage="memory";
469                        else if("ram".equalsIgnoreCase(storage)) storage="memory";
470                        else if("registry".equalsIgnoreCase(storage)) storage="file";
471                        else storage=storage.toLowerCase();
472                        
473                        
474                        
475                        Session session=(Session) context.get(pc.getCFID());
476                        
477                        if(!(session instanceof StorageScope) || session.isExpired() || !((StorageScope)session).getStorage().equalsIgnoreCase(storage)) {
478                                
479                                if("memory".equals(storage)) return false;
480                                else if("file".equals(storage))
481                                        return SessionFile.hasInstance(appContext.getName(),pc);
482                                else if("cookie".equals(storage))
483                                        return SessionCookie.hasInstance(appContext.getName(),pc);
484                                else {
485                                        DataSource ds = ((ConfigImpl)pc.getConfig()).getDataSource(storage,null);
486                                        if(ds!=null && ds.isStorage()){
487                                                if(SessionDatasource.hasInstance(storage,pc)) return true;
488                                        }
489                                        return  SessionCache.hasInstance(storage,appContext.getName(),pc);
490                                }
491                        }
492                        return true;
493        }
494        
495        
496        /**
497         * return cf session scope
498         * @param pc PageContext
499         * @param checkExpires 
500         * @param listener 
501         * @return cf session matching the context
502         * @throws PageException 
503         */
504        private Session getCFSessionScope(PageContext pc, RefBoolean isNew) throws PageException {
505                
506                ApplicationContext appContext = pc.getApplicationContext(); 
507                // get Context
508                        Map<String, Scope> context = getSubMap(cfSessionContextes,appContext.getName());
509                        
510                // get Session
511                        boolean isMemory=false;
512                        String storage = appContext.getSessionstorage();
513                        if(StringUtil.isEmpty(storage,true)){
514                                storage=ConfigImpl.DEFAULT_STORAGE_SESSION;
515                                isMemory=true;
516                        }
517                        else if("ram".equalsIgnoreCase(storage)) {
518                                storage="memory";
519                                isMemory=true;
520                        }
521                        else if("registry".equalsIgnoreCase(storage)) {
522                                storage="file";
523                        }
524                        else {
525                                storage=storage.toLowerCase();
526                                if("memory".equals(storage))isMemory=true;
527                        }
528                        
529                        //final boolean doMemory=isMemory || !appContext.getSessionCluster();
530                        
531                        Session existing=(Session) context.get(pc.getCFID());
532                        Session session=appContext.getSessionCluster()?null:existing;
533                        //Session session=doMemory?(appContext.getSessionCluster()?null:(Session) context.get(pc.getCFID())):null;
534                        
535                        if(session==null || !(session instanceof StorageScope) || session.isExpired() || !((StorageScope)session).getStorage().equalsIgnoreCase(storage)) {
536                                if(isMemory){
537                                        if(existing!=null) session=existing;
538                                        else session=SessionMemory.getInstance(pc,isNew,getLog());
539                                }
540                                else if("file".equals(storage)){
541                                        session=SessionFile.getInstance(appContext.getName(),pc,getLog());
542                                }
543                                else if("cookie".equals(storage))
544                                        session=SessionCookie.getInstance(appContext.getName(),pc,getLog());
545                                else{
546                                        DataSource ds = ((PageContextImpl)pc).getDataSource(storage,null);
547                                        if(ds!=null && ds.isStorage())session=SessionDatasource.getInstance(storage,pc,getLog(),null);
548                                        else {
549                                                session=SessionCache.getInstance(storage,appContext.getName(),pc,existing,getLog(),null);
550                                        }
551                                        
552                                        if(session==null){
553                                                // datasource not enabled for storage
554                                                if(ds!=null)
555                                                        throw new ApplicationException(
556                                                                        "datasource ["+storage+"] is not enabled to be used as session/client storage, " +
557                                                                        "you have to enable it in the lucee administrator or define key \"storage=true\" for datasources defined in Application.cfc .");
558                                                
559                                                CacheConnection cc = Util.getCacheConnection(pc.getConfig(),storage,null);
560                                                if(cc!=null) 
561                                                        throw new ApplicationException("cache ["+storage+"] is not enabled to be used  as a session/client storage, you have to enable it in the lucee administrator.");
562                                                
563                                                throw new ApplicationException("there is no cache or datasource with name ["+storage+"] defined.");
564                                        }
565                                }
566                                ((StorageScope)session).setStorage(storage);
567                                context.put(pc.getCFID(),session);
568                                isNew.setValue(true);
569                        }
570                        else {
571                                getLog().log(Log.LEVEL_INFO,"scope-context", "use existing session scope for "+appContext.getName()+"/"+pc.getCFID()+" from storage "+storage);
572                        }
573                        session.touchBeforeRequest(pc);
574                        return session;
575        }
576        
577        public void removeSessionScope(PageContext pc) throws PageException {
578                
579                //CFSession
580                Session sess = getCFSessionScope(pc, new RefBooleanImpl());
581                ApplicationContext appContext = pc.getApplicationContext(); 
582                Map<String, Scope> context = getSubMap(cfSessionContextes,appContext.getName());
583                if(context!=null) {
584                        context.remove(pc.getCFID());
585                        if(sess instanceof StorageScope)((StorageScope)sess).unstore(pc.getConfig());
586                }
587                
588                // JSession
589                HttpSession httpSession=pc.getSession();
590        if(httpSession!=null) {
591                httpSession.removeAttribute(appContext.getName());
592        }
593        }
594        
595        public void removeClientScope(PageContext pc) throws PageException {
596                Client cli = getClientScope(pc);
597                ApplicationContext appContext = pc.getApplicationContext(); 
598                Map<String, Scope> context = getSubMap(cfClientContextes,appContext.getName());
599                if(context!=null) {
600                        context.remove(pc.getCFID());
601                        if(cli!=null)cli.unstore(pc.getConfig());
602                }
603        }
604        
605
606        public boolean remove(int type, String appName, String cfid) {
607                Map<String, Map<String, Scope>> contextes = type==Scope.SCOPE_CLIENT?cfClientContextes:cfSessionContextes;
608                Map<String, Scope> context = getSubMap(contextes,appName);
609                Object res = context.remove(cfid);
610                getLog().log(Log.LEVEL_INFO,"scope-context", "remove "+VariableInterpreter.scopeInt2String(type)+" scope "+appName+"/"+cfid+" from memory");
611                
612                return res!=null;
613        }
614
615        /**
616         * return j session scope
617         * @param pc PageContext
618         * @param listener 
619         * @return j session matching the context
620         * @throws PageException
621         */
622        private Session getJSessionScope(PageContext pc, RefBoolean isNew) throws PageException {
623        HttpSession httpSession=pc.getSession();
624        ApplicationContext appContext = pc.getApplicationContext(); 
625        Object session=null;// this is from type object, because it is possible that httpSession return object from prior restart
626                
627        int s=(int) appContext.getSessionTimeout().getSeconds();
628        if(maxSessionTimeout<s)maxSessionTimeout=s;
629        if(httpSession!=null) {
630                httpSession.setMaxInactiveInterval(maxSessionTimeout+60);// we let the http session run a minute longer so we are sure it exists when sessionTimeout is triggered.
631                session= httpSession.getAttribute(appContext.getName());
632        }
633        else {
634                Map<String, Scope> context = getSubMap(cfSessionContextes,appContext.getName());
635                session=context.get(pc.getCFID());
636        }
637        
638        JSession jSession=null;
639                if(session instanceof JSession) {
640                        jSession=(JSession) session;
641            try {
642                if(jSession.isExpired()) {
643                        jSession.touch();
644                }
645                info(getLog(), "use existing JSession for "+appContext.getName()+"/"+pc.getCFID());
646                
647            }
648            catch(ClassCastException cce) {
649                error(getLog(), cce);
650                // if there is no HTTPSession
651                        if(httpSession==null) return getCFSessionScope(pc, isNew);
652                        
653                jSession=new JSession();
654                httpSession.setAttribute(appContext.getName(),jSession);
655                                isNew.setValue(true);
656            }
657                }
658                else {
659                        // if there is no HTTPSession
660                        if(httpSession==null) return getCFSessionScope(pc, isNew);
661                        
662                        info(getLog(), "create new JSession for "+appContext.getName()+"/"+pc.getCFID());
663                        jSession=new JSession();
664                    httpSession.setAttribute(appContext.getName(),jSession);
665                        isNew.setValue(true);
666                        Map<String, Scope> context = getSubMap(cfSessionContextes,appContext.getName());
667                        context.put(pc.getCFID(),jSession);
668                }
669                jSession.touchBeforeRequest(pc);
670                return jSession;    
671        }
672
673        /**
674         * return the application Scope for this context (cfid,cftoken,contextname)
675         * @param pc PageContext 
676         * @param listener 
677         * @param isNew 
678         * @return session matching the context
679         * @throws PageException 
680         */
681        public Application getApplicationScope(PageContext pc, RefBoolean isNew) {
682                ApplicationContext appContext = pc.getApplicationContext(); 
683                // getApplication Scope from Context
684                        ApplicationImpl application;
685                        Object objApp=applicationContextes.get(appContext.getName());
686                        if(objApp!=null) {
687                            application=(ApplicationImpl)objApp;
688                            if(application.isExpired()) {
689                                application.release();  
690                                isNew.setValue(true);
691                            }
692                        }
693                        else {
694                                application=new ApplicationImpl();
695                                applicationContextes.put(appContext.getName(),application);     
696                        isNew.setValue(true);
697                        }
698                        application.touchBeforeRequest(pc);
699                        //if(newApplication)listener.onApplicationStart(pc);
700                        
701                        return application;
702        }
703        
704        public void removeApplicationScope(PageContext pc) {
705                applicationContextes.remove(pc.getApplicationContext().getName());
706        }
707
708
709    /**
710     * remove all unused scope objects
711     */
712    public void clearUnused() {
713        
714        Log log=getLog();
715        try{
716        // create cleaner engine for session/client scope
717        if(session==null)session=new StorageScopeEngine(factory,log,new StorageScopeCleaner[]{
718                        new FileStorageScopeCleaner(Scope.SCOPE_SESSION, null)//new SessionEndListener())
719                        ,new DatasourceStorageScopeCleaner(Scope.SCOPE_SESSION, null)//new SessionEndListener())
720                        //,new CacheStorageScopeCleaner(Scope.SCOPE_SESSION, new SessionEndListener())
721        });
722        if(client==null)client=new StorageScopeEngine(factory,log,new StorageScopeCleaner[]{
723                        new FileStorageScopeCleaner(Scope.SCOPE_CLIENT, null)
724                        ,new DatasourceStorageScopeCleaner(Scope.SCOPE_CLIENT, null)
725                        //,new CacheStorageScopeCleaner(Scope.SCOPE_CLIENT, null) //Cache storage need no control, if there is no listener
726        });
727
728        
729        
730        // store session/client scope and remove from memory
731        storeUnusedStorageScope(factory, Scope.SCOPE_CLIENT);
732        storeUnusedStorageScope(factory, Scope.SCOPE_SESSION);
733        
734        // remove unused memory based client/session scope (invoke onSessonEnd)
735        clearUnusedMemoryScope(factory, Scope.SCOPE_CLIENT);
736        clearUnusedMemoryScope(factory, Scope.SCOPE_SESSION);
737        
738        
739        // session must be executed first, because session creates a reference from client scope
740        session.clean();
741        client.clean();
742        
743        // clean all unused application scopes
744        clearUnusedApplications(factory);
745        }
746        catch(Throwable t){
747                        ExceptionUtil.rethrowIfNecessary(t);
748                error(t);
749        }
750    }
751    
752    /**
753     * remove all scope objects
754     */
755    public void clear() {
756        try{
757                Scope scope;
758                //Map.Entry entry,e;
759                //Map context;
760                
761                // release all session scopes
762                Iterator<Entry<String, Map<String, Scope>>> sit = cfSessionContextes.entrySet().iterator();
763                Entry<String, Map<String, Scope>> sentry;
764                Map<String, Scope> context;
765                Iterator<Entry<String, Scope>> itt;
766                Entry<String, Scope> e;
767                
768                        while(sit.hasNext()){
769                        sentry=sit.next();
770                        context = sentry.getValue();
771                        itt = context.entrySet().iterator();
772                        while(itt.hasNext()){
773                                e = itt.next();
774                                scope=e.getValue();
775                                scope.release();
776                        }
777                }
778                cfSessionContextes.clear();
779                
780                // release all application scopes
781                Iterator<Entry<String, Application>> ait = applicationContextes.entrySet().iterator();
782                Entry<String, Application> aentry;
783                while(ait.hasNext()){
784                        aentry = ait.next();
785                        scope=aentry.getValue();
786                        scope.release();
787                }
788                applicationContextes.clear();
789                
790                // release server scope
791                if(server!=null){
792                        server.release();
793                        server=null;
794                }
795        
796        }
797        catch(Throwable t){
798                        ExceptionUtil.rethrowIfNecessary(t);
799                        t.printStackTrace();
800                }
801    }
802
803    
804
805        private void storeUnusedStorageScope(CFMLFactoryImpl cfmlFactory, int type) {
806        Map<String, Map<String, Scope>> contextes = type==Scope.SCOPE_CLIENT?cfClientContextes:cfSessionContextes;
807                long timespan = type==Scope.SCOPE_CLIENT?CLIENT_MEMORY_TIMESPAN:SESSION_MEMORY_TIMESPAN;
808                String strType=VariableInterpreter.scopeInt2String(type);
809                
810                if(contextes.size()==0)return;
811                long now = System.currentTimeMillis();
812                Object[] arrContextes= contextes.keySet().toArray();
813                Object applicationName,cfid,o;
814                Map<String, Scope> fhm;
815                for(int i=0;i<arrContextes.length;i++) {
816                        
817                        applicationName=arrContextes[i];
818            fhm = contextes.get(applicationName);
819            if(fhm.size()>0){
820                        Object[] arrClients= fhm.keySet().toArray();
821                int count=arrClients.length;
822                for(int y=0;y<arrClients.length;y++) {
823                        cfid=arrClients[y];
824                        o=fhm.get(cfid);
825                        if(!(o instanceof StorageScope)) continue;
826                                StorageScope scope=(StorageScope)o;
827                                if(scope.lastVisit()+timespan<now && !(scope instanceof MemoryScope)) {
828                                        getLog().log(Log.LEVEL_INFO,"scope-context", "remove from memory "+strType+" scope for "+applicationName+"/"+cfid+" from storage "+scope.getStorage());
829                                        
830                                        //if(scope instanceof StorageScope)((StorageScope)scope).store(cfmlFactory.getConfig());
831                                        fhm.remove(arrClients[y]);
832                                        count--;
833                                }
834                        }
835                if(count==0)contextes.remove(arrContextes[i]);
836            }
837                }
838        }
839    
840        /**
841         * @param cfmlFactory 
842         * 
843         */
844        private void clearUnusedMemoryScope(CFMLFactoryImpl cfmlFactory, int type) {
845        Map<String, Map<String, Scope>> contextes = type==Scope.SCOPE_CLIENT?cfClientContextes:cfSessionContextes;
846                if(contextes.size()==0)return;
847                
848                
849                
850        Object[] arrContextes= contextes.keySet().toArray();
851                ApplicationListener listener = cfmlFactory.getConfig().getApplicationListener();
852                Object applicationName,cfid,o;
853                Map<String, Scope> fhm;
854                
855                for(int i=0;i<arrContextes.length;i++) {
856                        applicationName=arrContextes[i];
857            fhm = contextes.get(applicationName);
858
859                        if(fhm.size()>0){
860                        Object[] cfids= fhm.keySet().toArray();
861                int count=cfids.length;
862                for(int y=0;y<cfids.length;y++) {
863                        cfid=cfids[y];
864                        o=fhm.get(cfid);
865                        if(!(o instanceof MemoryScope)) continue;
866                        MemoryScope scope=(MemoryScope) o;
867                                // close
868                                if(scope.isExpired()) {
869                                        // TODO macht das sinn? ist das nicht kopierleiche?
870                                        ApplicationImpl application=(ApplicationImpl) applicationContextes.get(applicationName);
871                                        long appLastAccess=0;
872                                        if(application!=null){
873                                                appLastAccess=application.getLastAccess();
874                                                application.touch();
875                                        }
876                                        scope.touch();
877                        
878                                        try {
879                                                if(type==Scope.SCOPE_SESSION)listener.onSessionEnd(cfmlFactory,(String)applicationName,(String)cfid);
880                                        } 
881                                        catch (Throwable t) {
882                                                ExceptionUtil.rethrowIfNecessary(t);
883                                                t.printStackTrace();
884                                                ExceptionHandler.log(cfmlFactory.getConfig(),Caster.toPageException(t));
885                                        }
886                                        finally {
887                                                if(application!=null)application.setLastAccess(appLastAccess);
888                                                fhm.remove(cfids[y]);
889                                                scope.release();
890                                                getLog().log(Log.LEVEL_INFO,"scope-context", "remove memory based "+VariableInterpreter.scopeInt2String(type)+" scope for "+applicationName+"/"+cfid);
891                                                count--;
892                                        }
893                                }
894                        }
895                if(count==0)contextes.remove(arrContextes[i]);
896            }
897                }
898        }
899        
900        private void clearUnusedApplications(CFMLFactoryImpl jspFactory) {
901        
902        if(applicationContextes.size()==0)return;
903                
904                long now=System.currentTimeMillis();
905                Object[] arrContextes= applicationContextes.keySet().toArray();
906                ApplicationListener listener = jspFactory.getConfig().getApplicationListener();
907                for(int i=0;i<arrContextes.length;i++) {
908            Application application=applicationContextes.get(arrContextes[i]);
909                        
910                        if(application.getLastAccess()+application.getTimeSpan()<now) {
911                //SystemOut .printDate(jspFactory.getConfigWebImpl().getOut(),"Clear application scope:"+arrContextes[i]+"-"+this);
912                application.touch();
913                                try {
914                                        listener.onApplicationEnd(jspFactory,(String)arrContextes[i]);
915                                } 
916                                catch (Throwable t) {
917                                        ExceptionUtil.rethrowIfNecessary(t);
918                                        ExceptionHandler.log(jspFactory.getConfig(),Caster.toPageException(t));
919                                }
920                                finally {
921                                        applicationContextes.remove(arrContextes[i]);
922                                        application.release();
923                                }
924                                
925                        }
926                }
927        }
928        
929
930        public void clearApplication(PageContext pc) throws PageException {
931        
932        if(applicationContextes.size()==0) throw new ApplicationException("there is no application context defined");
933                
934        String name = pc.getApplicationContext().getName();
935        CFMLFactoryImpl jspFactory = (CFMLFactoryImpl)pc.getCFMLFactory();
936                
937        Application application=applicationContextes.get(name);
938        if(application==null) throw new ApplicationException("there is no application context defined with name ["+name+"]");
939        ApplicationListener listener = PageContextUtil.getApplicationListener(pc);
940        application.touch();
941                try {
942                        listener.onApplicationEnd(jspFactory,name);
943                } 
944                finally {
945                        applicationContextes.remove(name);
946                        application.release();
947                }
948        }
949
950        /**
951         * @return returns a new CFIs
952         */
953        public static String getNewCFId() {
954                return generator.generateRandomBasedUUID().toString();
955        }
956        
957        /**
958         * @return returns a new CFToken
959         */
960        public static String getNewCFToken() {
961                return "0";
962        }
963
964        public void invalidateUserScope(PageContextImpl pc,boolean migrateSessionData,boolean migrateClientData) throws PageException {
965                ApplicationContext appContext = pc.getApplicationContext();
966
967                // get in memory scopes
968                Map<String, Scope> clientContext = getSubMap(cfClientContextes,appContext.getName());
969                UserScope clientScope = (UserScope) clientContext.get(pc.getCFID());
970                Map<String, Scope> sessionContext = getSubMap(cfSessionContextes,appContext.getName());
971                UserScope sessionScope = (UserScope) sessionContext.get(pc.getCFID());
972                
973                // remove  Scopes completly
974                removeSessionScope(pc);
975                removeClientScope(pc);
976                
977                pc.resetIdAndToken();
978
979                _migrate(pc,clientContext,clientScope,migrateClientData);
980                _migrate(pc,sessionContext,sessionScope,migrateSessionData);
981        }
982
983        private static void _migrate(PageContextImpl pc, Map<String, Scope> context, UserScope scope, boolean migrate) {
984                if(scope==null) return;
985                if(!migrate) scope.clear();
986                scope.resetEnv(pc);
987                context.put(pc.getCFID(), scope);
988        }
989        
990
991}