001    /*
002      GRANITE DATA SERVICES
003      Copyright (C) 2012 GRANITE DATA SERVICES S.A.S.
004    
005      This file is part of Granite Data Services.
006    
007      Granite Data Services is free software; you can redistribute it and/or modify
008      it under the terms of the GNU Library General Public License as published by
009      the Free Software Foundation; either version 2 of the License, or (at your
010      option) any later version.
011    
012      Granite Data Services is distributed in the hope that it will be useful, but
013      WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014      FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015      for more details.
016    
017      You should have received a copy of the GNU Library General Public License
018      along with this library; if not, see <http://www.gnu.org/licenses/>.
019    */
020    
021    package org.granite.client.tide.impl;
022    
023    import java.util.ArrayList;
024    import java.util.HashMap;
025    import java.util.List;
026    import java.util.Map;
027    import java.util.Map.Entry;
028    
029    import org.granite.client.tide.BeanManager;
030    import org.granite.client.tide.Context;
031    import org.granite.client.tide.ContextManager;
032    import org.granite.client.tide.EventBus;
033    import org.granite.client.tide.InstanceStore;
034    import org.granite.client.tide.InstanceStoreFactory;
035    import org.granite.client.tide.Platform;
036    
037    /**
038     * @author William DRAI
039     */
040    public class SimpleContextManager implements ContextManager {
041        
042        static final String DEFAULT_CONTEXT = "__DEFAULT__CONTEXT__";
043        
044        public static final String CONTEXT_CREATE = "org.granite.tide.contextCreate";
045        public static final String CONTEXT_DESTROY = "org.granite.tide.contextDestroy";
046        
047        protected final Platform platform;
048        protected final EventBus eventBus;
049        private InstanceStoreFactory instanceStoreFactory = new DefaultInstanceStoreFactory();
050        private BeanManager beanManager = new SimpleBeanManager();
051        private Map<String, Context> contextsById = new HashMap<String, Context>();
052        private List<String> contextsToDestroy = new ArrayList<String>();
053        
054        
055        protected SimpleContextManager() {
056            // CDI proxying...
057            this.platform = null;
058            this.eventBus = null;
059        }
060        
061        public SimpleContextManager(Platform platform) {
062            this.platform = platform;
063            this.eventBus = new SimpleEventBus();
064        }
065    
066        public SimpleContextManager(Platform platform, EventBus eventBus) {
067            this.platform = platform;
068            this.eventBus = eventBus;
069        }
070        
071        public void setInstanceStoreFactory(InstanceStoreFactory instanceStoreFactory) {
072            this.instanceStoreFactory = instanceStoreFactory;
073        }
074        
075        public void setBeanManager(BeanManager beanManager) {
076            this.beanManager = beanManager;
077        }
078        
079        public static class DefaultInstanceStoreFactory implements InstanceStoreFactory {
080                    @Override
081                    public InstanceStore createStore(Context context) {
082                            return new SimpleInstanceStore(context);
083                    }       
084        }
085        
086        
087        /**
088         *  Determine if the specified context is the global one
089         *  
090         *  @param context
091         *  @return true if global
092         */
093        public boolean isGlobal(Context context) {
094            return contextsById.get(DEFAULT_CONTEXT) == context;
095        }
096    
097        /**
098         *  Return the global context
099         *  
100         *  @return context
101         */ 
102        public Context getContext() {
103            return getContext(null, null, true);
104        }
105        
106        /**
107         *  Return a context from its id
108         *  
109         *  @param contextId context id
110         *  @return context
111         */ 
112        public Context getContext(String contextId) {
113            return getContext(contextId, null, true);
114        }
115        
116        
117        protected Context createContext(Context parentCtx, String contextId) {
118            Context ctx = new Context(this, parentCtx, contextId);
119            ctx.initContext(platform, eventBus, beanManager, instanceStoreFactory.createStore(ctx));
120            return ctx;
121        }
122        
123        /**
124         *  Return a context from its id
125         *  
126         *  @param contextId context id
127         *  @param create should create when not existing
128         *  @return context
129         */
130        public Context getContext(String contextId, String parentContextId, boolean create) {
131            Context ctx = contextsById.get(contextId != null ? contextId : DEFAULT_CONTEXT);
132            if (ctx == null && create) {
133                Context parentCtx = contextsById.get(parentContextId == null ? DEFAULT_CONTEXT : parentContextId);
134                if (parentContextId != null && parentCtx == null)
135                    throw new IllegalStateException("Parent context not found for id " + parentContextId);
136                
137                ctx = createContext(parentCtx, contextId);
138                contextsById.put(contextId != null ? contextId : DEFAULT_CONTEXT, ctx);
139                if (contextId != null)
140                    ctx.getEventBus().raiseEvent(ctx, CONTEXT_CREATE);
141                ctx.postInit();
142            }
143            return ctx;
144        }
145    
146        /**
147         *  @private
148         *  Create a new context if it does not exist
149         * 
150         *  @param contextId the requested context id
151         *  @return the context
152         */
153        public Context newContext(String contextId, String parentContextId) {
154            Context ctx = contextsById.get(contextId != null ? contextId : DEFAULT_CONTEXT);
155            if (ctx != null && ctx.isFinished()) {
156                ctx.clear(false);
157                contextsById.remove(contextId);
158                removeFromContextsToDestroy(contextId);
159                ctx = null;
160            }
161            if (ctx == null) {
162                Context parentCtx = contextsById.get(parentContextId != null ? parentContextId : DEFAULT_CONTEXT);
163                ctx = createContext(parentCtx, contextId);
164                if (contextId != null)
165                    contextsById.put(contextId, ctx);
166                ctx.getEventBus().raiseEvent(ctx, CONTEXT_CREATE);
167                ctx.postInit();
168            }
169            return ctx;
170        }
171        
172        /**
173         *  @private
174         *  Destroy a context
175         * 
176         *  @param contextId context id
177         *  @param force force complete destruction of context
178         */
179        public void destroyContext(String contextId, boolean force) {
180            Context ctx = contextId != null ? contextsById.get(contextId) : null;
181            if (ctx != null) {
182                // Destroy child contexts
183                for (Context c : contextsById.values()) {
184                    if (c.getParentContext() == ctx)
185                        destroyContext(c.getContextId(), force);
186                }
187                
188                removeFromContextsToDestroy(contextId);
189                ctx.getEventBus().raiseEvent(ctx, CONTEXT_DESTROY);
190                contextsById.get(contextId).clear(force);
191                contextsById.remove(contextId);
192            }
193        }     
194        
195        /**
196         *  Returns the list of conversation contexts
197         * 
198         *  @return conversation contexts
199         */
200        public List<Context> getAllContexts() {
201            List<Context> contexts = new ArrayList<Context>();
202            for (Entry<String, Context> ectx : contextsById.entrySet()) {
203                if (!ectx.getKey().equals(DEFAULT_CONTEXT))
204                    contexts.add(ectx.getValue());
205            }
206            return contexts;
207        }       
208        
209    //    /**
210    //     *  Execute a function for each conversation context
211    //     * 
212    //     *  @param parentContext parent context
213    //     *  @param callback callback function
214    //     *  @param token token passed to the function
215    //     */
216    //    public function forEachChildContext(parentContext:Context, callback:Function, token:Object = null):void {
217    //        for each (var ctx:Context in _ctx) {
218    //            if (ctx.meta_parentContext === parentContext) {
219    //                if (token)
220    //                    callback(ctx, token);
221    //                else
222    //                    callback(ctx);
223    //            }
224    //        }
225    //    }       
226        
227        /**
228         *  @private
229         *  Destroy all contexts
230         * 
231         *  @param force force complete destruction of contexts (all event listeners...), used for testing
232         */
233        public void destroyContexts(boolean force) {
234            contextsToDestroy.clear();
235            
236            Context globalCtx = contextsById.get(DEFAULT_CONTEXT);
237            List<String> contextIdsToDestroy = new ArrayList<String>();
238            for (Entry<String, Context> ectx : contextsById.entrySet()) {
239                if (!ectx.getKey().equals(DEFAULT_CONTEXT) && ectx.getValue().getParentContext() == globalCtx)
240                    contextIdsToDestroy.add(ectx.getKey());
241            }
242            for (String contextId : contextIdsToDestroy)
243                    destroyContext(contextId, force);
244            
245            globalCtx.clear(force);
246        }
247        
248        /**
249         *  @private
250         *  Destroy finished contexts and reset current pending contexts
251         */
252        public void destroyFinishedContexts() {
253            for (String contextId : contextsToDestroy)
254                destroyContext(contextId, false);
255            contextsToDestroy.clear();
256        }
257        
258        
259        /**
260         *  @private
261         *  Remove context from the list of contexts to destroy
262         *  
263         *  @param contextId context id
264         */
265        public void removeFromContextsToDestroy(String contextId) {
266            int idx = contextsToDestroy.indexOf(contextId);
267            if (idx >= 0)
268                contextsToDestroy.remove(idx);
269        }
270        
271        /**
272         *  @private
273         *  Add context to the list of contexts to destroy
274         *  
275         *  @param contextId context id
276         */
277        public void addToContextsToDestroy(String contextId) {
278            if (contextsToDestroy.contains(contextId))
279                return;
280            contextsToDestroy.add(contextId);
281        }
282        
283        
284        public Context retrieveContext(Context sourceContext, String contextId, boolean wasConversationCreated, boolean wasConversationEnded) {
285            Context context = null;
286            if (!isGlobal(sourceContext) && contextId == null && wasConversationEnded) {
287                // The conversation of the source context was ended
288                // Get results in the current conversation when finished
289                context = sourceContext;
290                context.markAsFinished();
291            }
292            else if (!isGlobal(sourceContext) && contextId == null && !sourceContext.isContextIdFromServer()) {
293                // A call to a non conversational component was issued from a conversation context
294                // Get results in the current conversation
295                context = sourceContext;
296            }
297            else if (!isGlobal(sourceContext) && contextId != null
298                && (sourceContext.getContextId() == null || (!sourceContext.getContextId().equals(contextId) && !wasConversationCreated))) {
299                // The conversationId has been updated by the server
300                String previousContextId = sourceContext.getContextId();
301                context = sourceContext;
302                context.setContextId(contextId, true);
303                updateContextId(previousContextId, context);
304            }
305            else {
306                context = getContext(contextId);
307                if (contextId != null)
308                    context.setContextId(contextId, true);
309            }
310            
311            return context;
312        }
313        
314        /**
315         *  @private
316         *  
317         *  Destroys component instances in all contexts
318         * 
319         *  @param name component name
320         */
321        // TODO: destroy component instances
322    //    public void destroyComponentInstances(String name) {
323    //        for (Context ctx : contextsById.values())
324    //            ctx.destroy(name, true);
325    //    }
326        
327        /**
328         *  @private
329         * 
330         *  Defines new context for existing id
331         * 
332         *  @param previousContextId existing id
333         *  @param context new context
334         */
335        public void updateContextId(String previousContextId, Context context) {
336            if (previousContextId != null)
337                contextsById.remove(previousContextId);
338            contextsById.put(context.getContextId(), context);
339        }
340    
341    }