/* * This is part of Geomajas, a GIS framework, http://www.geomajas.org/. * * Copyright 2008-2015 Geosparc nv, http://www.geosparc.com/, Belgium. * * The program is available in open source according to the GNU Affero * General Public License. All contributions in this program are covered * by the Geomajas Contributors License Agreement. For full licensing * details, see LICENSE.txt in the project root. */ package org.geomajas.gwt.client.service; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.geomajas.annotation.Api; import org.geomajas.command.dto.GetConfigurationRequest; import org.geomajas.command.dto.GetConfigurationResponse; import org.geomajas.configuration.client.ClientApplicationInfo; import org.geomajas.configuration.client.ClientLayerTreeInfo; import org.geomajas.configuration.client.ClientMapInfo; import org.geomajas.configuration.client.ClientToolbarInfo; import org.geomajas.configuration.client.ClientWidgetInfo; import org.geomajas.gwt.client.command.AbstractCommandCallback; import org.geomajas.gwt.client.command.GwtCommand; import org.geomajas.gwt.client.command.GwtCommandDispatcher; /** * Service for fetching specific configuration objects from the server. * * @author Pieter De Graef * @author Joachim Van der Auwera * @since 1.0.0 */ @Api(allMethods = true) public final class ClientConfigurationService { // map is not synchronized as this class runs in JavaScript which only has one execution thread private static final Map<String, ClientApplicationInfo> CONFIG = new HashMap<String, ClientApplicationInfo>(); // map is not synchronized as this class runs in JavaScript which only has one execution thread private static final Map<String, List<DelayedCallback>> BACKLOG = new HashMap<String, List<DelayedCallback>>(); private static final ClientConfigurationLoader DEFAULT_LOADER; private static final ClientConfigurationSetter SETTER = new ClientConfigurationSetterImpl(); private static ClientConfigurationLoader configurationLoader; static { DEFAULT_LOADER = new ConfigurationLoaderImpl(); configurationLoader = DEFAULT_LOADER; } private ClientConfigurationService() { // final class, hide constructor } /** * Set the {@link ClientConfigurationLoader} which is responsible for getting the application configuration from the * server. * * @param configurationLoader configuration loader */ public static void setConfigurationLoader(ClientConfigurationLoader configurationLoader) { ClientConfigurationService.configurationLoader = configurationLoader; } /** * Clear the cached data, forcing data to be reloaded on the next request. */ public static void clear() { CONFIG.clear(); } /** * Get the configuration for a specific widget. This method will search within an application context at the highest * level. If the requested configuration can't be found there, it will go deeper by investigating all map * configurations as well. This means that it will find maps, tool-bars and layer trees as well. * * @param applicationId * The application name wherein to search for the widget configuration. * @param name * The actual widget configuration bean name. * @param callback * The call-back that is executed when the requested widget configuration is found. This is called * asynchronously. If the widget configuration is not found, null is passed as value! */ public static void getApplicationWidgetInfo(final String applicationId, final String name, final WidgetConfigurationCallback callback) { if (!CONFIG.containsKey(applicationId)) { if (addCallback(applicationId, new DelayedCallback(name, callback))) { configurationLoader.loadClientApplicationInfo(applicationId, SETTER); } } else { execute(applicationId, name, callback); } } /** * Add a delayed callback for the given application id. Returns whether this is the first request for the * application id. * * @param applicationId application id * @param callback callback * @return true when first request for that application id */ private static boolean addCallback(String applicationId, DelayedCallback callback) { boolean isFirst = false; List<DelayedCallback> list = BACKLOG.get(applicationId); if (null == list) { list = new ArrayList<DelayedCallback>(); BACKLOG.put(applicationId, list); isFirst = true; } list.add(callback); return isFirst; } /** * Get the configuration for a specific widget. This method will search within the context of a specific map. This * means that it will find maps, tool-bars and layer trees as well as the widget configurations within a map. * * @param applicationId * The application name wherein to search for the widget configuration. * @param mapId * The map wherein to search for the widget configuration. * @param name * The actual widget configuration bean name (can also be a layer tree or a tool-bar). * @param callback * The call-back that is executed when the requested widget configuration is found. This is called * asynchronously. If the widget configuration is not found, null is passed as value! */ public static void getMapWidgetInfo(final String applicationId, final String mapId, final String name, final WidgetConfigurationCallback callback) { if (!CONFIG.containsKey(applicationId)) { if (addCallback(applicationId, new DelayedCallback(mapId, name, callback))) { configurationLoader.loadClientApplicationInfo(applicationId, SETTER); } } else { execute(applicationId, mapId, name, callback); } } // ------------------------------------------------------------------------ // Private methods: // ------------------------------------------------------------------------ @SuppressWarnings({ "rawtypes", "unchecked" }) private static void execute(final String application, final String name, final WidgetConfigurationCallback callback) { ClientApplicationInfo applicationInfo = CONFIG.get(application); // First search the application level widget configurations: ClientWidgetInfo widgetInfo = applicationInfo.getWidgetInfo(name); if (widgetInfo != null) { callback.execute(widgetInfo); return; } // Next, search within each map configuration: for (ClientMapInfo mapInfo : applicationInfo.getMaps()) { widgetInfo = search(mapInfo, name); if (widgetInfo != null) { callback.execute(widgetInfo); return; } } callback.execute(null); // found nothing, still invoke the callback! } @SuppressWarnings({ "rawtypes", "unchecked" }) private static void execute(final String application, final String mapId, final String name, final WidgetConfigurationCallback callback) { // Go over all maps, and search therein for the ClientWidgetInfo: List<ClientMapInfo> maps = CONFIG.get(application).getMaps(); for (ClientMapInfo mapInfo : maps) { if (mapId.equals(mapInfo.getId())) { ClientWidgetInfo widgetInfo = search(mapInfo, name); if (widgetInfo != null) { callback.execute(widgetInfo); return; } } } callback.execute(null); // found nothing, still invoke the callback! } private static ClientWidgetInfo search(ClientMapInfo mapInfo, String name) { // Corner case, what if the bean name equals the map ID? Just return the damned map: if (mapInfo.getId().equals(name)) { return mapInfo; } // Corner case: check the layer trees: ClientLayerTreeInfo layerTree = mapInfo.getLayerTree(); if (null != layerTree && name.equals(layerTree.getId())) { return mapInfo.getLayerTree(); } // Corner case: check the tool-bar: ClientToolbarInfo toolBar = mapInfo.getToolbar(); if (null != toolBar && name.equals(toolBar.getId())) { return mapInfo.getToolbar(); } // Now search all widget info objects within the map: Map<String, ClientWidgetInfo> widgets = mapInfo.getWidgetInfo(); if (null != widgets && widgets.containsKey(name)) { return widgets.get(name); } // Bean was not found: return null; } /** * Encapsulation of the callback with the information to retrieve the requested object from the application * configuration. * * @author Joachim Van der Auwera */ private static final class DelayedCallback { private final String mapId; private final String widgetKey; private final WidgetConfigurationCallback callback; private DelayedCallback(String widgetKey, WidgetConfigurationCallback callback) { this(null, widgetKey, callback); } private DelayedCallback(String mapId, String widgetKey, WidgetConfigurationCallback callback) { this.mapId = mapId; this.widgetKey = widgetKey; this.callback = callback; } public void run(String applicationId) { if (null == mapId) { execute(applicationId, widgetKey, callback); } else { execute(applicationId, mapId, widgetKey, callback); } } } /** * Callback for the configuration loader which allows storing the loaded application configuration. * * @author Joachim Van der Auwera */ private static class ClientConfigurationSetterImpl implements ClientConfigurationSetter { public void set(String applicationId, ClientApplicationInfo applicationInfo) { CONFIG.put(applicationId, applicationInfo); List<DelayedCallback> callbacks = BACKLOG.get(applicationId); BACKLOG.remove(applicationId); if (null != callbacks) { for (DelayedCallback callback : callbacks) { callback.run(applicationId); } } } } /** * Default {@link ClientConfigurationLoader} implementation. * * @author Joachim Van der Auwera */ private static class ConfigurationLoaderImpl implements ClientConfigurationLoader { public void loadClientApplicationInfo(final String applicationId, final ClientConfigurationSetter setter) { GetConfigurationRequest request = new GetConfigurationRequest(); request.setApplicationId(applicationId); GwtCommand command = new GwtCommand(GetConfigurationRequest.COMMAND); command.setCommandRequest(request); GwtCommandDispatcher.getInstance().execute(command, new AbstractCommandCallback<GetConfigurationResponse>() { public void execute(GetConfigurationResponse response) { setter.set(applicationId, response.getApplication()); } }); } } }