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;
036
037import java.lang.annotation.Annotation;
038import java.util.Collections;
039import java.util.HashMap;
040import java.util.List;
041import java.util.Map;
042
043import org.granite.client.tide.data.EntityManager;
044import org.granite.client.tide.data.impl.EntityManagerImpl;
045import org.granite.client.tide.data.impl.JavaBeanDataManager;
046import org.granite.client.tide.data.impl.RemoteInitializerImpl;
047import org.granite.client.tide.data.spi.DataManager;
048import org.granite.client.tide.impl.DefaultApplication;
049import org.granite.client.tide.impl.SimpleEventBus;
050import org.granite.client.tide.impl.SimpleInstanceStore;
051import org.granite.logging.Logger;
052
053/**
054 * General Tide context implementation
055 * It can either wrap a Spring or CDI container or be used separately
056 *
057 * Currently only one context can be active at a time
058 *
059 * A context is created by a context manager
060 *
061 * @see org.granite.client.tide.ContextManager
062 *
063 * @author William DRAI
064 */
065public class Context {
066    
067    static final Logger log = Logger.getLogger(Context.class);
068       
069    private String contextId = null;
070    private boolean isContextIdFromServer = false;
071    private boolean finished = false;
072    
073    private ContextManager contextManager = null;
074    
075    private InstanceStore instanceStore = new SimpleInstanceStore(this);
076    private Map<String, Object> initialBeans = new HashMap<String, Object>();
077    
078    private Application application = new DefaultApplication();
079        private EventBus eventBus = new SimpleEventBus();
080    
081        private DataManager dataManager = new JavaBeanDataManager();
082    private EntityManager entityManager;
083    
084    
085    protected Context() {
086        // CDI proxying...
087    }
088
089    /**
090     * Create a context using the specified manager and context id
091     * Should not be used directly
092     * @param contextManager context manager
093     * @param parentCtx parent context for conversation contexts (not supported yet)
094     * @param contextId context id
095     */
096    public Context(ContextManager contextManager, Context parentCtx, String contextId) {
097        this.contextManager = contextManager;
098        // TODO: conversation contexts 
099        // parentCtx
100        this.contextId = contextId;
101    }
102
103    /**
104     * Managed for this context
105     * @return context manager
106     */
107    public ContextManager getContextManager() {
108        return contextManager;
109    }
110
111    /**
112     * Entity manager for this context
113     * @return entity manager
114     */
115    public EntityManager getEntityManager() {
116        return entityManager;
117    }
118
119    /**
120     * Set the data manager for this context
121     * @param dataManager data manager
122     * @see org.granite.client.tide.data.spi.DataManager
123     */
124    public void setDataManager(DataManager dataManager) {
125        this.dataManager = dataManager;
126    }
127
128    /**
129     * Data manager for this context
130     * @return data manager
131     */
132    public DataManager getDataManager() {
133        return dataManager;
134    }
135
136    /**
137     * Map of beans defined before the initialization of the context so they can be registered in the DI container
138     * @return map of initialization beans keyed by name
139     */
140    public Map<String, Object> getInitialBeans() {
141        return Collections.unmodifiableMap(initialBeans);
142    }
143
144    /**
145     * Initialize the context
146     * @param application application for this context (depends on the target platform/framework)
147     * @param eventBus event bus for this context (depends on the framework and/or the DI container)
148     * @param instanceStore instance store (depends on the DI container)
149     */
150    public void initContext(Application application, EventBus eventBus, InstanceStore instanceStore) {
151        this.application = application;
152        this.eventBus = eventBus;
153        this.instanceStore = instanceStore;
154        application.initContext(this, initialBeans);
155        this.entityManager = new EntityManagerImpl("", dataManager);
156        this.entityManager.setRemoteInitializer(new RemoteInitializerImpl(this));
157    }
158
159    /**
160     * Event bus for this context
161     * @return event bus
162     */
163    public EventBus getEventBus() {
164        return eventBus;
165    }
166    
167    public void postInit() {
168        // TODO: postInit ?
169    }
170
171    /**
172     * Parent context for conversation contexts
173     * @return parent context
174     */
175    public Context getParentContext() {
176        return null;
177    }
178
179    /**
180     * Context id
181     * @return context id
182     */
183    public String getContextId() {
184        return contextId;
185    }
186
187    /**
188     * Indicate that the context id has been defined by the server
189     * Unused for now
190     * @return true if id received from server
191     */
192    public boolean isContextIdFromServer() {
193        return isContextIdFromServer;
194    }
195
196    /**
197     * Indicate that the context is eligible for destruction
198     * @return true is finished
199     */
200    public boolean isFinished() {
201        return finished;
202    }
203    
204    /**
205     *  Update the context id
206     *  @param contextId context id
207     *  @param fromServer is this id received from the server ?
208     */
209    public void setContextId(String contextId, boolean fromServer) {
210        String previousContextId = this.contextId;
211        this.contextId = contextId;
212        this.isContextIdFromServer = fromServer;
213        contextManager.updateContextId(previousContextId, this);
214    }
215
216    /**
217     * Return a component instance by its name in the container
218     * InstanceStore implementations are free to (but don't have to) automatically create a suitable component instance
219     * with the expected name when no instance exists
220     * @param name component name
221     * @param <T> component type
222     * @return component instance
223     */
224    public <T> T byName(String name) {
225        return instanceStore.byName(name, this);
226    }
227
228    /**
229     * Return a component instance by its name in the container
230     * Does not create a default proxy ({@link org.granite.client.tide.impl.ComponentImpl}) if no instance exists
231     * @param name component name
232     * @param <T> component type
233     * @return component instance or null if not found
234     */
235    public <T> T byNameNoProxy(String name) {
236        return instanceStore.getNoProxy(name, this);
237    }
238
239    /**
240     * Return a component instance looked up by its type
241     * If more than one instance is found, throws a runtime exception
242     * @param type expected component type
243     * @param <T> expected component type
244     * @return component instance
245     */
246    public <T> T byType(Class<T> type) {
247        return instanceStore.byType(type, this);
248    }
249
250    /**
251     * Return an array of all component instances implementing the expected type
252     * @param type expected component type
253     * @param <T> expected component type
254     * @return array of component instances
255     */
256    public <T> T[] allByType(Class<T> type) {
257        return instanceStore.allByType(type, this, true);
258    }
259    /**
260     * Return an array of all component instances implementing the expected type
261     * @param type expected component type
262     * @param create if true, should create an instance if none is existing
263     * @param <T> expected component type
264     * @return array of component instances or null if no instance found
265     */
266    public <T> T[] allByType(Class<T> type, boolean create) {
267        return instanceStore.allByType(type, this, create);
268    }
269
270    /**
271     * Return a map of all component instances annotated with the specified annotation
272     * @param annotationClass annotation
273     * @return map of component instances keyed by name
274     */
275    public Map<String, Object> allByAnnotatedWith(Class<? extends Annotation> annotationClass) {
276        return instanceStore.allByAnnotatedWith(annotationClass, this);
277    }
278
279    /**
280     * Return a list of all component names in this context
281     * @return list of names
282     */
283    public List<String> allNames() {
284        return instanceStore.allNames();
285    }
286
287    /**
288     * Set a component instance as a managed instance with the specified name in the context
289     * May not work with all containers (Spring and CDI are static and cannot be modified after initialization)
290     * @param name component name
291     * @param instance component instance
292     * @param <T> component type
293     * @return component instance
294     */
295    public <T> T set(String name, T instance) {
296        return instanceStore.set(name, instance);
297    }
298
299    /**
300     * Set a component instance as a managed instance in the context
301     * May not work with all containers (Spring and CDI are static and cannot be modified after initialization)
302     * @param instance component instance
303     * @param <T> component type
304     * @return component instance
305     */
306    public <T> T set(T instance) {
307        return instanceStore.set(instance);
308    }
309
310    /**
311     * Remove the component instance having the specified name from the context
312     * May not work with all containers (Spring and CDI are static and cannot be modified after initialization)
313     * @param name component name
314     */
315    public void remove(String name) {
316        instanceStore.remove(name);
317    }
318
319    /**
320     * Clear all data and instances in the context
321     */
322    public void clear() {
323        entityManager.clear();
324        instanceStore.clear();
325    }
326
327    /**
328     * Initialize an instance when it is added to the context
329     * @param instance component instance
330     * @param name component name
331     */
332    public void initInstance(Object instance, String name) {
333        if (name != null && instance instanceof NameAware)
334                ((NameAware)instance).setName(name);
335        if (instance instanceof ContextAware)
336                ((ContextAware)instance).setContext(this);
337        if (instance instanceof Initializable)
338                ((Initializable)instance).init();
339        if (instance.getClass().isAnnotationPresent(ApplicationConfigurable.class))
340                application.configure(instance);
341    }
342
343    /**
344     * Check that this context is not finished
345     * @throws org.granite.client.tide.InvalidContextException when context finished
346     */
347    public void checkValid() {
348        if (finished)
349            throw new InvalidContextException(contextId, "Invalid context");
350    }
351
352    /**
353     * Convenience method to defer execution of a method in the main UI thread
354     * @param runnable runnable method
355     */
356    public void callLater(Runnable runnable) {
357        application.execute(runnable);
358    }
359
360    /**
361     * Mark this context as eligible for destruction
362     */
363    public void markAsFinished() {
364        this.finished = true;
365    }
366}