All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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:
  • *
      *
    1. 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: * *
      *
    1. Call {@link #registerGlobalDataConsumer(IsGlobalDataConsumer)} to * register your consumer.
    2. *
    3. Use {@link Consumer#requires(Enum)} to specify which global data * the consumer requires.
    4. *
    5. * Finally, calling {@link Consumer#ensureRequirements()} will fire * {@link IsGlobalDataConsumer#onRequiredGlobalDataReady()} wheh all required * global data are ready.
    6. *
    * 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