
org.ioc.commons.model.globaldata.GlobalDataController Maven / Gradle / Ivy
package org.ioc.commons.model.globaldata;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.ioc.commons.flowcontrol.appcontroller.AppController;
import org.ioc.commons.flowcontrol.common.Receiver;
import org.ioc.commons.flowcontrol.common.impl.ReceiverAdapter;
import org.ioc.commons.model.globaldata.GlobalDataControllerException.ErrorCode;
import org.ioc.commons.ui.HasLoader;
/**
* A tool class for controling the global data from the entire application based
* on providers/consumers.
*
*
* - Global data provider usage:
*
* - Call {@link #registerGlobalDataProvider(Enum, Provider)} to register your provider.
*
* Example:
*
* public enum GlobalData implements IsGlobalData {
* {@literal @}GlobalDataType(LoggedUserInfo.class)
* LoggedUserInfo,
* }
*
* public class AppControllerImpl implements {@link AppController} {
*
* {@literal @}Override
* {@literal @}Performs(Operation.LoadingInitialData)
* {@literal @}Fires(Event.ApplicationReady)
* public void run(HasOneWidget parentView, Object... params) {
*
* [...]
*
* this.globalDataController.registerGlobalDataProvider(GlobalData.LoggedUserInfo, new Provider<LoggedUserInfo>() {
*
* {@literal}Override
* public void provide(Receiver<LoggedUserInfo> callback) {
* serviceManager.serviceFactory().securityManagementService().getLoggedUserInfo().to(callback)
* .with("workarea", "userGroupNames")
* .performing(Operation.LoadingLoggedUserInfo, operationManager);
* }
* });
* }
* }
*
* Global data consumer usage:
*
*
* - Call {@link #registerGlobalDataConsumer(IsGlobalDataConsumer)} to
* register your consumer.
* - Use {@link Consumer#requires(Enum)} to specify which global data
* the consumer requires.
* -
* Finally, calling {@link Consumer#ensureRequirements()} will fire
* {@link IsGlobalDataConsumer#onRequiredGlobalDataReady()} wheh all required
* global data are ready.
*
* Example:
*
* private Consumer<GlobalData> globalDataConsumer;
*
* public MyViewPresenter(MyView display, EventBus<Event> eventbus,
* OperationManager<Operation> operationManager, ActionController<Action> actionController) {
* super(display, eventbus, operationManager, actionController);
* this.globalDataConsumer = this.globalDataController.registerGlobalDataConsumer(this);
* }
*
* {@literal @}Override
* final public void onInitDisplay() {
* super.onInitDisplay();
*
* //
* // Ensure we have a logger user info
* //
* this.globalDataConsumer.requires(GlobalData.LoggedUserInfo);
* this.globalDataConsumer.ensureRequirements();
* }
*
* {@literal @}Override
* final public void onRequiredGlobalDataReady() {
*
* //
* // Continuing from onInitDisplay
* //
*
* //
* // Checking for access permission
* //
* LoggedUserInfo loggedUserInfo = (LoggedUserInfo) this.globalDataController
* .getGlobalDataValue(GlobalData.LoggedUserInfo);
*
* logger.info("Checking for access permission...");
*
* if (loggedUserInfo == null) {
* logger.info("... access denied");
*
* throw new AccessDeniedException(
* "The logged user cannot have permission for accessing to the application");
* } else {
* logger.info("... access granted");
* }
* }
*
*
*
*
* @author Jesús Lunar Pérez
*
* @param
* Global data type
*/
public class GlobalDataController> {
private enum GlobalDataValueStatus {
NotRead, Fetching, Cached, Constant
}
private class StoreEntry {
private Provider> provider;
private Object value;
private GlobalDataValueStatus status = GlobalDataValueStatus.NotRead;
private boolean fireOnUpdate;
private boolean fireOnReady;
}
public interface Provider {
public void provide(Receiver callback);
}
public interface Consumer> {
/**
* Declares a global data type parameter as required by this consumer.
*
* After declarations, a call to method {@link #ensureRequirements()}
* must be done. Calls to this method after a
* {@link #ensureRequirements()} call will not take effect.
*
* @param globalData
* The required global data by this consumer.
*/
void requires(G globalData);
RequirementStatus getRequirementStatus();
/**
* Ensure all required global data type declared by
* {@link #requires(Enum)} calls will be loaded.
*
* This method will call methods in the {@link IsGlobalDataConsumer}
* registered by
* {@link GlobalDataController#registerGlobalDataConsumer(IsGlobalDataConsumer)}
* .
*
*
* - When all the requirements are loaded, the method
* {@link IsGlobalDataConsumer#onRequiredGlobalDataReady()} will be
* called
* - If one of the requirements is not loaded yet, the method
* {@link IsGlobalDataConsumer#onRequiredGlobalDataNotReadyYet()} will
* be called while it is being loaded.
*
*
*
*/
void ensureRequirements();
}
public enum RequirementStatus {
NotReady, Ready
}
private class ConsumerImpl implements Consumer {
private Set requirements = new HashSet();
private IsGlobalDataConsumer forThisObject;
private RequirementStatus lastStatus;
public ConsumerImpl(IsGlobalDataConsumer forThisObject) {
this.forThisObject = forThisObject;
}
@Override
public void requires(G globalData) {
/**
* New requirement. Resetting last status
*/
lastStatus = null;
/**
* When a provider are not registered, it will throw an exception
*/
String message = "There is no providers registered for " + globalData + ".";
if (!globalDataStore.containsKey(globalData)) {
throw new GlobalDataControllerException(ErrorCode.NO_REGISTERED_PROVIDERS, message);
}
StoreEntry storeEntry = globalDataStore.get(globalData);
/**
* If there is a registered provider but the value has not been
* read, we start the fetching
*/
if (storeEntry.status == GlobalDataValueStatus.NotRead) {
fetchData(globalData);
}
this.requirements.add(globalData);
}
@Override
public void ensureRequirements() {
/**
* If ALL the requires are loaded and cached, then it calls
* onRequiredGlobalDataReady()
*/
/**
* In case some one is not loaded yet, it executes
* onRequiredGlobalDataNotReadyYet()
*/
RequirementStatus newStatus = RequirementStatus.Ready;
for (G requirement : requirements) {
StoreEntry storeEntry = globalDataStore.get(requirement);
boolean requirementOk = storeEntry.status == GlobalDataValueStatus.Cached
|| storeEntry.status == GlobalDataValueStatus.Constant;
if (!requirementOk) {
storeEntry.fireOnReady = true;
newStatus = RequirementStatus.NotReady;
break;
}
}
if (lastStatus == null || lastStatus != newStatus) {
lastStatus = newStatus;
switch (newStatus) {
case Ready:
forThisObject.onRequiredGlobalDataReady();
break;
case NotReady:
forThisObject.onRequiredGlobalDataNotReadyYet();
break;
}
} else {
/*
* The status has not been changed. We are not going to invoke
* anything again.
*/
}
}
boolean isRequiring(G globalData) {
return this.requirements.contains(globalData);
}
@Override
public RequirementStatus getRequirementStatus() {
return lastStatus;
}
}
public GlobalDataController() {
this(null);
}
public GlobalDataController(HasLoader globalLoadingIndicator) {
this.globalLoadingIndicator = globalLoadingIndicator;
}
private HasLoader globalLoadingIndicator;
private final Map globalDataStore = new HashMap();
private final Map, ConsumerImpl> registeredConsumers = new HashMap, ConsumerImpl>();
public final HasLoader getGlobalLoadingIndicator() {
return globalLoadingIndicator;
}
public final void setGlobalLoadingIndicator(HasLoader globalLoadingIndicator) {
this.globalLoadingIndicator = globalLoadingIndicator;
}
/**
* It register a provider for a type of global data.
*
* This method is used when the global data may change as opposed to
* setConstantGlobalData method.
*
* @param globalData
* @param provider
*/
public void registerGlobalDataProvider(G globalData, Provider provider) {
String message = "You are trying to register a provider for " + globalData
+ " which was already registered or has a constant value";
if (this.globalDataStore.containsKey(globalData)) {
throw new GlobalDataControllerException(ErrorCode.PROVIDER_ALREADY_REGISTERED, message);
}
StoreEntry se = new StoreEntry();
se.provider = provider;
this.globalDataStore.put(globalData, se);
}
/**
* It set a constant value for a type of global data.
*
* This method is used when is known that the global data will never change,
* as opposed to registerGlobalDataProvider method.
*
* @param globalData
* Global data
* @param value
* Value for the global data.
*/
public void setGlobalDataConstantValue(G globalData, T value) {
StoreEntry storeEntry = this.globalDataStore.get(globalData);
if (storeEntry == null) {
storeEntry = new StoreEntry();
}
/**
* Just fires when something changes
*/
storeEntry.fireOnUpdate = (value == null && storeEntry.value != null)
|| (value != null && !value.equals(storeEntry.value));
storeEntry.value = value;
storeEntry.status = GlobalDataValueStatus.Constant;
this.globalDataStore.put(globalData, storeEntry);
notifyConsumers(globalData, storeEntry);
}
public void refreshGlobalData(final G globalData) {
String message = "There is no providers for " + globalData + ".";
if (!this.globalDataStore.containsKey(globalData)) {
throw new GlobalDataControllerException(ErrorCode.NO_REGISTERED_PROVIDERS, message);
}
StoreEntry storeEntry = this.globalDataStore.get(globalData);
/**
* The constant values don't notify anything
*/
if (storeEntry.status != GlobalDataValueStatus.Constant && storeEntry.status != GlobalDataValueStatus.Fetching) {
fetchData(globalData);
storeEntry.fireOnUpdate = true;
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
protected void fetchData(final G globalData) {
StoreEntry storeEntry = this.globalDataStore.get(globalData);
Provider> provider = storeEntry.provider;
String message = "Cannot fetch a global value for " + globalData
+ ". There is no a registered provider for it.";
if (provider == null) {
throw new GlobalDataControllerException(ErrorCode.NO_REGISTERED_PROVIDERS, message);
}
storeEntry.status = GlobalDataValueStatus.Fetching;
refreshGlobalLoadingIndicatorStatus();
provider.provide(new ReceiverAdapter() {
@Override
public void success(Object globalDataValue) {
StoreEntry storeEntry = globalDataStore.get(globalData);
storeEntry.value = globalDataValue;
storeEntry.status = GlobalDataValueStatus.Cached;
refreshGlobalLoadingIndicatorStatus();
notifyConsumers(globalData, storeEntry);
}
});
}
private void refreshGlobalLoadingIndicatorStatus() {
if (this.globalLoadingIndicator == null) {
return;
}
boolean loading = false;
for (StoreEntry storeEntry : this.globalDataStore.values()) {
if (storeEntry.status == GlobalDataValueStatus.Fetching) {
loading = true;
break;
}
}
if (this.globalLoadingIndicator.isLoading() != loading) {
this.globalLoadingIndicator.setLoading(loading);
}
}
/**
* It registers a new global data consumer.
*
* Once registered, use {@link Consumer#requires(Enum)} method to specify
* which global data the consumer requires.
*
* Finally, calling {@link Consumer#ensureRequirements()} will fire
* {@link IsGlobalDataConsumer#onRequiredGlobalDataReady()} wheh all
* required global data are ready.
*
* @param forThisObject
* The new consumer to be registered.
* @return A consumer handler.
*/
public Consumer registerGlobalDataConsumer(IsGlobalDataConsumer forThisObject) {
ConsumerImpl consumer = this.registeredConsumers.get(forThisObject);
if (consumer == null) {
synchronized (registeredConsumers) {
this.registeredConsumers.put(forThisObject, (consumer = new ConsumerImpl(forThisObject)));
}
}
return consumer;
}
/**
* Get the value from a global data type.
*
* In case the global data was registered using a provider, the value will
* be got from it the first time and that value will be cached. A
* call to refreshGlobalData() will remove the cached value and will fetch
* it from the provider again.
*
* In case the gloabal data value has been set using
* setGlobalDataConstantValue, this value will be returned.
* refreshGlobalData() doesn't have any effect in that case.
*
* @param globalData
* @return Global data value
*/
public Object getGlobalDataValue(G globalData) {
StoreEntry entry = globalDataStore.get(globalData);
return (entry != null) ? entry.value : null;
}
protected void notifyConsumers(final G globalData, StoreEntry storeEntry) {
boolean fireOnReady = storeEntry.fireOnReady;
boolean fireOnUpdate = storeEntry.fireOnUpdate;
if (!fireOnReady && !fireOnUpdate) {
// Nothing to notify
return;
}
List consumers = null;
synchronized (registeredConsumers) {
consumers = new ArrayList.ConsumerImpl>(registeredConsumers.values());
}
for (ConsumerImpl consumer : consumers) {
if (consumer.isRequiring(globalData)) {
/*
* It checks again if all the requeriments are loaded
*/
if (fireOnReady) {
storeEntry.fireOnReady = false;
consumer.ensureRequirements();
}
/*
* Moreover, if we were waiting for a refresh, it executes the
* method
*/
if (fireOnUpdate) {
storeEntry.fireOnUpdate = false;
consumer.forThisObject.onRequiredGlobalDataUpdated(globalData);
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy