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 }