/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* ***
*
* Community License: GPL 3.0
*
* This file is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* This file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* ***
*
* Available Commercial License: GraniteDS SLA 1.0
*
* This is the appropriate option if you are creating proprietary
* applications and you are not prepared to distribute and share the
* source code of your application under the GPL v3 license.
*
* Please visit http://www.granitedataservices.com/license for more
* details.
*/
package org.granite.client.tide;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.granite.client.tide.data.EntityManager;
import org.granite.client.tide.data.impl.EntityManagerImpl;
import org.granite.client.tide.data.impl.JavaBeanDataManager;
import org.granite.client.tide.data.impl.RemoteInitializerImpl;
import org.granite.client.tide.data.spi.DataManager;
import org.granite.client.tide.impl.DefaultApplication;
import org.granite.client.tide.impl.SimpleEventBus;
import org.granite.client.tide.impl.SimpleInstanceStore;
import org.granite.logging.Logger;
/**
* General Tide context implementation
* It can either wrap a Spring or CDI container or be used separately
*
* Currently only one context can be active at a time
*
* A context is created by a context manager
*
* @see org.granite.client.tide.ContextManager
*
* @author William DRAI
*/
public class Context {
static final Logger log = Logger.getLogger(Context.class);
private String contextId = null;
private boolean isContextIdFromServer = false;
private boolean finished = false;
private ContextManager contextManager = null;
private Object platformContext = null;
private InstanceStore instanceStore = new SimpleInstanceStore(this, null);
private Map<String, Object> initialBeans = new HashMap<String, Object>();
private Map<String, Object> properties = new HashMap<String, Object>();
private Application application = new DefaultApplication();
private EventBus eventBus = new SimpleEventBus();
private DataManager dataManager = new JavaBeanDataManager();
private EntityManager entityManager;
protected Context() {
// CDI proxying...
}
/**
* Create a context using the specified manager and context id
* Should not be used directly
* @param contextManager context manager
* @param parentCtx parent context for conversation contexts (not supported yet)
* @param contextId context id
*/
public Context(ContextManager contextManager, Context parentCtx, String contextId) {
this.contextManager = contextManager;
// TODO: conversation contexts
// parentCtx
this.contextId = contextId;
}
/**
* Manager for this context
* @return context manager
*/
public ContextManager getContextManager() {
return contextManager;
}
/**
* Set the internal platform context
* @param context internal context
*/
public void setPlatformContext(Object context) {
this.platformContext = context;
}
/**
* Get platform context
* @return internal context
*/
public Object getPlatformContext() {
return platformContext;
}
/**
* Entity manager for this context
* @return entity manager
*/
public EntityManager getEntityManager() {
return entityManager;
}
/**
* Set the data manager for this context
* @param dataManager data manager
* @see org.granite.client.tide.data.spi.DataManager
*/
public void setDataManager(DataManager dataManager) {
this.dataManager = dataManager;
}
/**
* Data manager for this context
* @return data manager
*/
public DataManager getDataManager() {
return dataManager;
}
/**
* Map of beans defined before the initialization of the context so they can be registered in the DI container
* @return map of initialization beans keyed by name
*/
public Map<String, Object> getInitialBeans() {
return Collections.unmodifiableMap(initialBeans);
}
/**
* Is this context the global context ?
* @return is global context
*/
public boolean isGlobal() {
return contextManager.isGlobal(this);
}
/**
* Initialize the context
* @param application application for this context (depends on the target platform/framework)
* @param eventBus event bus for this context (depends on the framework and/or the DI container)
* @param instanceStore instance store (depends on the DI container)
*/
public void initContext(Application application, EventBus eventBus, InstanceStore instanceStore) {
this.application = application;
if (eventBus != null)
this.eventBus = eventBus;
if (instanceStore != null)
this.instanceStore = instanceStore;
application.initContext(this, initialBeans);
this.entityManager = new EntityManagerImpl("", dataManager);
this.entityManager.setRemoteInitializer(new RemoteInitializerImpl(this));
instanceStore.init();
}
/**
* Event bus for this context
* @return event bus
*/
public EventBus getEventBus() {
return eventBus;
}
public void postInit() {
}
/**
* Parent context for conversation contexts
* @return parent context
*/
public Context getParentContext() {
return null;
}
/**
* Context id
* @return context id
*/
public String getContextId() {
return contextId;
}
/**
* Indicate that the context id has been defined by the server
* Unused for now
* @return true if id received from server
*/
public boolean isContextIdFromServer() {
return isContextIdFromServer;
}
/**
* Indicate that the context is eligible for destruction
* @return true is finished
*/
public boolean isFinished() {
return finished;
}
/**
* Update the context id
* @param contextId context id
* @param fromServer is this id received from the server ?
*/
public void setContextId(String contextId, boolean fromServer) {
String previousContextId = this.contextId;
this.contextId = contextId;
this.isContextIdFromServer = fromServer;
contextManager.updateContextId(previousContextId, this);
}
/**
* Return a component instance by its name in the container
* InstanceStore implementations are free to (but don't have to) automatically create a suitable component instance
* with the expected name when no instance exists
* @param name component name
* @param <T> component type
* @return component instance
*/
public <T> T byName(String name) {
return instanceStore.byName(name, this);
}
/**
* Return a component instance by its name in the container
* Does not create a default proxy ({@link org.granite.client.tide.impl.ComponentImpl}) if no instance exists
* @param name component name
* @param <T> component type
* @return component instance or null if not found
*/
public <T> T byNameNoProxy(String name) {
return instanceStore.getNoProxy(name, this);
}
/**
* Return a component instance looked up by its type
* If more than one instance is found, throws a runtime exception
* @param type expected component type
* @param <T> expected component type
* @return component instance
*/
public <T> T byType(Class<T> type) {
return instanceStore.byType(type, this);
}
/**
* Return an array of all component instances implementing the expected type
* @param type expected component type
* @param <T> expected component type
* @return array of component instances
*/
public <T> T[] allByType(Class<T> type) {
return instanceStore.allByType(type, this, true);
}
/**
* Return an array of all component instances implementing the expected type
* @param type expected component type
* @param create if true, should create an instance if none is existing
* @param <T> expected component type
* @return array of component instances or null if no instance found
*/
public <T> T[] allByType(Class<T> type, boolean create) {
return instanceStore.allByType(type, this, create);
}
/**
* Return a map of all component instances annotated with the specified annotation
* @param annotationClass annotation
* @return map of component instances keyed by name
*/
public Map<String, Object> allByAnnotatedWith(Class<? extends Annotation> annotationClass) {
return instanceStore.allByAnnotatedWith(annotationClass, this);
}
/**
* Return a list of all component names in this context
* @return list of names
*/
public List<String> allNames() {
return instanceStore.allNames();
}
/**
* Set a component instance as a managed instance with the specified name in the context
* May not work with all containers (Spring and CDI are static and cannot be modified after initialization)
* @param name component name
* @param instance component instance
* @param <T> component type
* @return component instance
*/
public <T> T set(String name, T instance) {
return instanceStore.set(name, instance);
}
/**
* Set a component instance as a managed instance in the context
* May not work with all containers (Spring and CDI are static and cannot be modified after initialization)
* @param instance component instance
* @param <T> component type
* @return component instance
*/
public <T> T set(T instance) {
return instanceStore.set(instance);
}
/**
* Remove the component instance having the specified name from the context
* May not work with all containers (Spring and CDI are static and cannot be modified after initialization)
* @param name component name
*/
public void remove(String name) {
instanceStore.remove(name);
}
/**
* Remove the component instance from the context
* May not work with all containers (Spring and CDI are static and cannot be modified after initialization)
* @param instance component instance
*/
public void remove(Object instance) {
instanceStore.remove(instance);
}
/**
* Clear all data and instances in the context
*/
public void clear() {
entityManager.clear();
instanceStore.clear();
}
/**
* Initialize an instance when it is added to the context
* @param instance component instance
* @param name component name
*/
public void initInstance(Object instance, String name) {
if (name != null && instance instanceof NameAware)
((NameAware)instance).setName(name);
if (instance instanceof ContextAware)
((ContextAware)instance).setContext(this);
if (instance instanceof Initializable)
((Initializable)instance).init();
if (instance.getClass().isAnnotationPresent(ApplicationConfigurable.class))
application.configure(instance);
instanceStore.inject(instance, name, properties);
}
/**
* Destroy an instance when it is removed from the context
* @param instance component instance
*/
public void destroyInstance(Object instance) {
}
public static interface Properties {
public Set<String> keySet();
public Object get(String key);
}
public void defineProperties(Properties properties) {
if (properties == null)
return;
for (String key : properties.keySet()) {
if (!key.startsWith("$"))
continue;
int idx = key.indexOf(".", 1);
if (idx < 0)
continue;
this.properties.put(key.substring(1), properties.get(key));
String componentName = key.substring(1, idx);
if (!instanceStore.exists(componentName))
continue;
Object instance = instanceStore.byName(componentName, this);
if (instance == null)
continue;
String propertyName = key.substring(idx+1);
Object value = properties.get(key);
String setterName = "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
Method setter = null;
for (Method s : instance.getClass().getMethods()) {
if (s.getName().equals(setterName)) {
setter = s;
break;
}
}
if (setter == null) {
log.warn("No setter found for %s", key);
continue;
}
try {
setter.invoke(instance, value);
}
catch (Exception e) {
throw new RuntimeException("Could not set value for bundle property " + key);
}
}
}
/**
* Check that this context is not finished
* @throws org.granite.client.tide.InvalidContextException when context finished
*/
public void checkValid() {
if (finished)
throw new InvalidContextException(contextId, "Invalid context");
}
/**
* Convenience method to defer execution of a method in the main UI thread
* @param runnable runnable method
*/
public void callLater(Runnable runnable) {
application.execute(platformContext, runnable);
}
/**
* Mark this context as eligible for destruction
*/
public void markAsFinished() {
this.finished = true;
}
}