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