Explication lien DRL D2

De EjnTricks

Viewer icon.png Objectif

Des liens sur des objets (document, répertoire, tâche, recherche) permettent d'accéder directement à ceux-ci. Cet article se propose d'étudier le fonctionnement de ces liens en version 4.1. Malgré son potentiel, il sera également mis en évidence qu'il est impossible de le réutiliser en ajoutant des paramètres spécifiques pour cette version.

Il est également important de rappeler que D2 est une application GWT - Sencha et qu'il est indispensable de bien connaître le fonctionnement de ce framework. En effet, le code client est écrit en Java, mais converti en Javascript. De plus, le code server entraine l'écriture de deux interfaces, une publiée sur le client, une pour le code serveur.

L'analyse a nécessité de décompiler l'ensemble du code D2, avec l'outil jd-gui.

Enfin, l'outil Fiddler permet une première approche de l'analyse, car cela met en évidence les différents appels.


Hand-icon.png Votre avis

Nobody voted on this yet

 You need to enable JavaScript to vote


Analyse

L'utilisation d'un lien sur un document, sous la forme http://<SERVER>:<PORT>/D2/?docbase=docbase03&locateId=0900000380002c1a#d2, est pris comme exemple. Après analyse, il parait évident d'orienter les recherches vers le controller com.emc.x3.portal.client.components.locate.LocateController. Cependant cela ne montrera pas tout le cheminement mis en place décrit à travers l'article.

Trames réseaux

Sur l'instance étudiée, les trames réseaux aboutissant à l'affichage du document, après connexion sont :

URL Argument Objectif
/D2/x3_portal/loginService com.emc.x3.portal.client.components.login.RpcLoginService|initContext Connexion à l'application.
/D2/x3_portal/preferenceService com.emc.x3.client.services.preference.RpcPreferenceService|getPortalMenuPosition
/D2/x3_portal/preferenceService com.emc.x3.client.services.preference.RpcPreferenceService|getPortalMenuWidth
/D2/x3_portal/skinService com.emc.x3.portal.client.components.skin.RpcSkinService|getAvailableSkins
/D2/x3_portal/filterService com.emc.x3.portal.client.components.filter.RpcFilterService|getFilters Disponibilité filtre CURRENT / ALL.
/D2/x3_portal/menuService com.emc.x3.portal.client.components.menu.RpcMenuService|getFullMenusForType
/D2/x3_portal/preferenceService com.emc.x3.client.services.preference.RpcPreferenceService|getPreference
/D2/x3_portal/spaceService com.emc.x3.portal.client.components.space.RpcSpaceService|getUserLastSpaces Dernier environnement de travail utilisé.
/D2/x3_portal/loginService com.emc.x3.portal.client.components.login.RpcLoginService|copyContext Copie du contexte restauré dans le contexte courant.
/D2/x3_portal/preferenceService com.emc.x3.client.services.preference.RpcPreferenceService|getPortalRowHeight
/D2/x3_portal/filterService com.emc.x3.portal.client.components.filter.RpcFilterService|getFilters Disponibilité filtre CURRENT / ALL.
/D2/x3_portal/menuService com.emc.x3.portal.client.components.menu.RpcMenuService|getFullMenusForType
/D2/x3_portal/preferenceService com.emc.x3.client.services.preference.RpcPreferenceService|getPreference
/D2/x3_portal/preferenceService com.emc.x3.client.services.preference.RpcPreferenceService|getPortalRowHeight
/D2/x3_portal/favoritesService com.emc.x3.client.services.RpcFavoritesService|getFavoritesDoclist
/D2/x3_portal/searchFormService com.emc.x3.client.services.RpcSearchFormService|getQueryFormId
/D2/x3_portal/doclistService com.emc.x3.client.services.RpcDoclistService|getDoclistFilterOptions
/D2/x3_portal/docgalleryService com.emc.x3.client.services.RpcDocgalleryService|getDocgalleryFilterOptions
/D2/x3_portal/preferenceService com.emc.x3.client.services.preference.RpcPreferenceService|getPortalRowHeight
/D2/x3_portal/browserService com.emc.x3.client.services.RpcBrowserService|getRepositoryNodes Liste les docbases pour l'arbre de navigation.
/D2/x3_portal/searchFormService com.emc.x3.client.services.RpcSearchFormService|getQueryForm
/D2/x3_portal/doclistService com.emc.x3.client.services.RpcDoclistService|getCabinetDocListItem
/D2/x3_portal/filterService com.emc.x3.portal.client.components.filter.RpcFilterService|getFilters Disponibilité filtre CURRENT / ALL.
/D2/x3_portal/menuService com.emc.x3.portal.client.components.menu.RpcMenuService|getFullMenusForType
/D2/x3_portal/browserService com.emc.x3.client.services.RpcBrowserService|getChildrenFromRepository Liste les cabinets de la docbase.
/D2/x3_portal/spaceService com.emc.x3.portal.client.components.space.RpcSpaceService|saveUserLastSpaces Sauvegarde de l'environnement de travail.
/D2/x3_portal/browserService com.emc.x3.client.services.RpcBrowserService|getLocationPathIds Récupération des emplacements de l'objet.
/D2/x3_portal/browserService com.emc.x3.client.services.RpcBrowserService|getChildrenFromNode Liste les fils du cabinet cible.
/D2/x3_portal/browserService com.emc.x3.client.services.RpcBrowserService|getChildrenFromNode Suite logique de l'arborescence.
/D2/x3_portal/browserService com.emc.x3.client.services.RpcBrowserService|getChildrenFromNode Suite logique de l'arborescence.
/D2/x3_portal/browserService com.emc.x3.client.services.RpcBrowserService|getChildrenFromNode Suite logique de l'arborescence.
/D2/x3_portal/browserService com.emc.x3.client.services.RpcBrowserService|getChildrenFromNode Suite logique de l'arborescence.
/D2/x3_portal/detailsLocationsService com.emc.x3.client.services.RpcDetailsLocationsService|getDetailsContent Parce que l'on affiche le widget des emplacements.
/D2/x3_portal/doclistService com.emc.x3.client.services.RpcDoclistService|getFolderDocListItemFromId Affiche les documents du répertoire contenant le document cible.
/D2/x3_portal/filterService com.emc.x3.portal.client.components.filter.RpcFilterService|getFilters Disponibilité filtre CURRENT / ALL.
/D2/x3_portal/menuService com.emc.x3.portal.client.components.menu.RpcMenuService|getFullMenusForType Menu contextuel pour le répertoire affiché.
/D2/x3_portal/thumbnailService com.emc.x3.client.services.RpcThumbnailService|getThumbnailsUrls Le preview du document cible.
/D2/x3_portal/detailsLocationsService com.emc.x3.client.services.RpcDetailsLocationsService|getDetailsContent Récupère le panneau des détails pour le document cible.
/D2/x3_portal/menuService com.emc.x3.portal.client.components.menu.RpcMenuService|getFullMenusForType Menu contextuel pour le document cible.

Un grand nombre d'appels est constaté avant celui réellement intéressant. Ensuite, les appels ne sont que des rafraîchissement de l'interface pour l'arbre de navigation, puis des différents Widgets présentés.


Service browserService

Les trames réseaux orientent donc vers la fonction getLocationPathIds de browserService, dont l'extrait de la classe com.emc.x3.server.services.RpcBrowserServiceImpl, jar X3-Browser-4.1.0.jar, est:

public List<String> getLocationPathIds(String uid, String id) throws X3ServerException {
	Context context = getContext(uid);
	try
	{
		return this.locateService.locate(context, id);
	} catch (Exception e) {
		LOG.error("{}", e);
	} throw ExceptionUtil.createServerException(e);
}

A cet instant, l'identifiant du document est déjà connu, il est donc trop tard pour cette analyse. Par contre, il "suffit" de trouver les composants faisant appel à cette fonction. Le service RpcBrowserServiceImpl implémente l'interface com.emc.x3.client.services.RpcBrowserService. Mais comme c'est du code GWT, c'est à partir de la déclaration asynchrone, soit com.emc.x3.client.services.RpcBrowserServiceAsync, qu'il faut chercher la hiérarchie des appels.


Controller BrowserController

"Par chance", il n'existe qu'un seul appel à cette fonction, dans la classe com.emc.x3.client.browser.BrowserController, jar X3-Browser-4.1.0.jar, au niveau de la méthode subscribeToLocate :

public void subscribeToLocate()
{
	try
	{
		getPubSubWidget().subscribe(X3PubSubEvents.D2_ACTION_LOCATE_OBJECT, new OpenAjaxCallback(getPubSubWidget())
		{
			public void onReceive(X3PubSubEvents event, OpenAjaxMessage message) {
				String ids = message.getId(true);

				if ((!message.getSender().equals(BrowserController.this.getPubSubWidget().getWidgetId())) && (ids != null)) {
					AsyncCallback callback = new X3AsyncCallback(ids)
					{
						public void onSuccess(List<String> result) {
							if (result != null) {
								BrowserController.LOG.log(Level.FINE, "START");
								BrowserController.this.locateElement(this.val$ids, result);
								BrowserController.LOG.log(Level.FINE, "STOP");
							}
						}

						protected String getFailureMessage()
						{
							return "Error while fetching browser content";
						}
					};
					int pos = ids.indexOf("¬");
					String id = pos == -1 ? ids : ids.substring(0, pos);
					BrowserController.this.browserService.getLocationPathIds(BrowserController.this.getPubSubWidget().getContainerUid(), id, callback);
				}
			} } );
	} catch (Exception e) {
		LOG.log(Level.SEVERE, "error", e);
	}
}

L'appel est dans la déclaration d'un écouteur sur l'événement X3PubSubEvents.D2_ACTION_LOCATE_OBJECT, ce qui est finalement assez logique avec le nom de la méthode subscribeToLocate. L'identifiant du document est récupéré depuis l'instance de OpenAjaxMessage. Le code de la fonction getId n'apporte rien à l'analyse :

public String getId(boolean raw)
{
	String id = null;
	if ((isMultipleIds()) && (!raw)) {
		String[] ids = getIds();
		if ((ids != null) && (ids.length > 0))
			id = ids[0];
	} else {
		id = getParameter("oam_id");
	}
	return id;
}

A ce stade, le controller écoute donc un événement qui véhicule l'identifiant du document. La transformation de l'argument depuis l'URL vers le message n'est cependant pas encore mis en évidence. La prochaine étape consiste à trouver l'origine de l'événement.


Controller LocateController

Le plus simple est de rechercher tous les appels à la variable X3PubSubEvents.D2_ACTION_LOCATE_OBJECT. Contrairement à la précédente recherche de ce type, il y a 24 résultats. Mais un appel se distingue particulièrement, depuis la méthode publishClientLocate de la classe com.emc.x3.portal.client.components.locate.LocateController, dans les classes de D2 :

public void publishClientLocate(String widgetInitType, String containerUID)
{
	if (this.locateTarget.equals(widgetInitType))
	{
		OpenAjaxMessage oam = new OpenAjaxMessage(this.widget, this.locateId);

		if (containerUID != null) {
			oam.setContainerUid(containerUID);
		}

		if (this.locateTarget.equals(X3TypeWidgets.BROWSER.getType()))
			publishMessageToEvent(X3PubSubEvents.D2_ACTION_LOCATE_OBJECT, oam);
		else if (this.locateTarget.equals(X3TypeWidgets.TASKFOLDERS.getType()))
			publishMessageToEvent(X3PubSubEvents.D2_ACTION_LOCATE_TASK, oam);
		else if (this.locateTarget.equals(X3TypeWidgets.SEARCH.getType())) {
			publishMessageToEvent(X3PubSubEvents.D2_ACTION_LOCATE_SEARCH, oam);
		}
		this.locateRequired = false;
	}
}

Dans cette méthode, trois types d'événement sont levés, en fonction de la cible :

  • X3PubSubEvents.D2_ACTION_LOCATE_OBJECT
  • X3PubSubEvents.D2_ACTION_LOCATE_TASK
  • X3PubSubEvents.D2_ACTION_LOCATE_SEARCH

A chaque fois, le message, instance de OpenAjaxMessage, est initialisé avec le contenu de la variable this.locateId. L'étude du constructeur de OpenAjaxMessage permet de confirmer le passage de l'identifiant :

public OpenAjaxMessage(IPubSubWidget pubSubWidget, String id)
{
	this(pubSubWidget);
	setId(id);
}

Il reste à trouver comment la variable locateId est initialisée avec la valeur fournie dans l'URL. Pour ce faire, il suffit de rechercher dans la classe son initialisation, qui est effectuée au niveau du constructeur :

private LocateController(LocateWidget widgetArg) {
	super(widgetArg);
	this.widget = widgetArg;

	this.location = ClientUtils.getLocation();
	this.locateId = ClientUtils.parseParameters(this.location, "locateId");
	this.locateTarget = ClientUtils.parseParameters(ClientUtils.getLocation(), "locateTarget", X3TypeWidgets.BROWSER.getType());
	this.locatePath = ClientUtils.parseParameters(ClientUtils.getLocation(), "locatePath");

	if (this.locateId != null) {
		this.locateRequired = true;
	}

	this.oac = new OpenAjaxCallback(this.widget)
	{
		public void onReceive(X3PubSubEvents event, OpenAjaxMessage message)
		{
			if (!message.getSender().equals(X3InternalWidgets.LOCATE.toString())) {
				if (message.getSender().equals(X3InternalWidgets.MULTISEARCH.toString())) {
					LocateController.this.logger.log(Level.INFO, "receiving selector request");
					String cuid = message.getContainerUid();

					PortalTabItem currentTabItem = LocateController.this.widget.getPortalView().getPortalTabItem();
					PortalTabItem targetTabItem = LocateController.this.getPortalTabItem(cuid);

					String targetRepositoryName = targetTabItem.getRepositoryName();
					String targetUserName = targetTabItem.getUserName();

					List tabItems = new ArrayList();
					for (int i = 0; i < LocateController.this.widget.getPortalView().getPortalTabPanel().getItemCount(); i++) {
						PortalTabItem tempTabItem = (PortalTabItem)LocateController.this.widget.getPortalView().getPortalTabPanel().getItem(i);

						if ((tempTabItem.getRepositoryName() == null) || (!tempTabItem.getRepositoryName().equals(targetRepositoryName)) || (tempTabItem.getUserName() == null) || (!tempTabItem.getUserName().equals(targetUserName)))
							continue;
						tabItems.add(tempTabItem);
					}

					if (LocateController.this.getSpacesForWidgetType(tabItems, X3TypeWidgets.BROWSER.toString()).size() > 0);
					tabItems = LocateController.this.getSpacesForWidgetType(tabItems, X3TypeWidgets.BROWSER.toString());

					if (tabItems.size() > 0) {
						if (!tabItems.contains(currentTabItem)) {
							targetTabItem = (PortalTabItem)tabItems.get(0);
							targetTabItem.ensureAutoSelect();
							message.setContainerUid(targetTabItem.getUid());
						}
						else {
							message.setContainerUid(currentTabItem.getUid());
						}
						LocateController.this.logger.log(Level.INFO, "publishing again the message");
						message.setTargetType(null);
						message.setSender(X3InternalWidgets.LOCATE.toString());
						if ((!event.equals(X3PubSubEvents.D2_ACTION_EXECUTE_MANAGER)) && (!event.equals(X3PubSubEvents.D2_ACTION_DISPLAY_DIALOG))) {
							LocateController.this.widget.publish(event, message);
						}
					}
				}
				if ((event.equals(X3PubSubEvents.D2_ACTION_LOCATE_OBJECT)) || (event.equals(X3PubSubEvents.D2_ACTION_LOCATE_SEARCH)))
				{
					PortalTabItem tabItem = LocateController.this.widget.getPortalView().getPortalTabItem();
					List widgets = tabItem.getAllWidgets();
					boolean alreadySelected = false;

					for (int i = 0; i < widgets.size(); i++) {
						BaseWidget widget = (BaseWidget)widgets.get(i);

						if ((widget.getWidgetType().equals(X3TypeWidgets.DOCLIST.getType())) || (widget.getWidgetType().equals(X3TypeWidgets.DOCGALLERY.getType()))) {
							if ((widget.getParent() instanceof WidgetTabItem)) {
								WidgetTabItem widgetTabItem = (WidgetTabItem)widget.getParent();
								WidgetTabPanel widgetTabPanel = (WidgetTabPanel)widgetTabItem.getParent();

								if (widgetTabPanel.getSelectedItem().equals(widgetTabItem)) {
									alreadySelected = true;
									break;
								}
							} else {
								if ((!(widget.getParent() instanceof PortalAccordionLayoutContainer)) || 
									(!widget.isExpanded())) continue;
								alreadySelected = true;
								break;
							}
						}

					}

					if ((!alreadySelected) && 
						(!LocateController.this.selectWidget(null, X3TypeWidgets.DOCLIST.getType()))) {
						LocateController.this.selectWidget(null, X3TypeWidgets.DOCGALLERY.getType());
					}

					LocateController.this.selectWidget(null, X3TypeWidgets.BROWSER.getType());
				}
				else if (event.equals(X3PubSubEvents.D2_ACTION_LOCATE_TASK)) {
					LocateController.this.selectWidget(null, X3TypeWidgets.TASKS.getType());
					LocateController.this.selectWidget(null, X3TypeWidgets.TASKFOLDERS.getType());
				}
			}
		}
	};
	subscribeToMultiSearchEvents();
	subscribeToWidgetInitialize();
	subscribeToCopyLocator();
	subscribeToAutoPublishLocate();
}

A première vue, ce n'est pas si évident que cela. Cependant, le paramètre locateId dans l'URL est clairement identifié. L'étude de la fonction parseParameters, de la classe com.emc.x3.client.common.utils.ClientUtils, confirme cette extraction :

public static String parseParameters(String parameters, String param, String defaultValue) {
	String paramValue = defaultValue;

	if ((parameters != null) && (param != null))
	{
		int indexStart = parameters.indexOf(param);
		if (indexStart >= 0) {
			indexStart += param.length() + 1;
			int indexNext = parameters.indexOf("&", indexStart);
			int indexSharp = parameters.indexOf("#", indexStart);
			if ((indexNext != -1) && ((indexSharp == -1) || ((indexSharp != -1) && (indexNext < indexSharp))))
				paramValue = parameters.substring(indexStart, indexNext);
			else if ((indexNext == -1) && (indexSharp != -1))
				paramValue = parameters.substring(indexStart, indexSharp);
			else if ((indexNext == -1) && (indexSharp == -1)) {
				paramValue = parameters.substring(indexStart);
			}
		}
	}

	return paramValue;
}

L'extraction de la valeur depuis l'URL et son utilisation sont à ce stade identifiées. Il reste à comprendre pourquoi l'événement est levé. Encore une fois, l'analyse des appels va permettre de comprendre l'orchestration. L'identifiant est extrait depuis le constructeur de LocatorController et utilisé dans la méthode publishClientLocate. Cette dernière est appelée dans la méthode subscribeToWidgetInitialize, toujours de la classe LocatorController :

public void subscribeToWidgetInitialize()
{
	try
	{
		subscribeToEvent(X3PubSubEvents.D2_EVENT_WIDGET_INITIALIZED, new OpenAjaxCallback(this.widget)
		{
			public void onReceive(X3PubSubEvents event, OpenAjaxMessage message)
			{
				LocateController.this.logger.log(Level.INFO, "receiving widget initialize");

				if (LocateController.this.locateRequired)
					LocateController.this.publishClientLocate(message.getId(), message.getContainerUid());
			} } );
	}
	catch (Exception e) {
		this.logger.log(Level.SEVERE, "error", e);
	}
}

Or la méthode subscribeToWidgetInitialize est elle même exécutée depuis le constructeur. L'analyse touche presque à sa fin. La transformation du paramètre en argument de message est identifiée, ainsi que son exécution et son écoute. Il reste donc à trouver comment est utilisé le controller LocatorController.


Widget LocateWidget

Le controller LocatorController est un singleton, constructeur privé et une fonction statique createInstance.

private static LocateController singleton;

public static synchronized LocateController createInstance(LocateWidget widget)
{
	if (singleton == null) {
		synchronized (LocateController.class) {
			singleton = new LocateController(widget);
		}
	}
	return singleton;
}

public static synchronized LocateController getInstance() {
	return singleton;
}

Ceci va grandement faciliter la fin de l'analyse car il suffit de rechercher l'appel à la fonction createInstance. En toute logique D2, un controller de "même" nom associé à un widget, c'est la classe com.emc.x3.portal.client.components.locate.LocateWidget :

public LocateWidget(PortalView portalView)
{
	super(X3InternalWidgets.LOCATE.toString(), "portal", X3InternalWidgets.LOCATE.toString());
	this.portalView = portalView;
	setLayout(new FitLayout());
	setHeaderVisible(false);
	setBorders(false);
	this.controller = LocateController.createInstance(this);
}


Composant PortalView

A première vue, il est légitime de penser que le widget LocateWidget doive être ajouté dans les déclaration des workspaces. Or, il n'est jamais référencé lors de la mise en place d'une application. Encore une fois, en étudiant les appels au constructeur, l'initialisation est trouvée dans le constructeur du composant PortalView :

public PortalView(PortalController controller)
{
	this.controller = controller;

	this.restoredTabSession = new ArrayList();

	this.viewport = new Viewport();
	this.viewport.setId("x3-portal");
	add(this.viewport);

	this.borderLayout = new PortalBorderLayout();
	this.viewport.setLayout(this.borderLayout);

	this.centerContainer = new LayoutContainer(new BorderLayout());
	this.centerContainer.setId("x3-portal-content-container");
	BorderLayoutData centerLayoutData = new BorderLayoutData(Style.LayoutRegion.CENTER);
	this.viewport.add(this.centerContainer, centerLayoutData);

	this.multisearchPanel = new PortalCollapsablePanel(new FitLayout());
	this.multisearchPanel.setId("x3-portal-multisearch-panel");

	this.multisearchContainer = new LayoutContainer(new BorderLayout());
	this.multisearchContainer.setId("x3-portal-multisearch-container");

	BorderLayoutData multisearchLayoutData = new BorderLayoutData(Style.LayoutRegion.EAST, 500.0F, 125, 700);
	multisearchLayoutData.setSplit(true);
	multisearchLayoutData.setCollapsible(true);
	multisearchLayoutData.setHideCollapseTool(true);

	RowData eastLayoutData = new RowData();

	this.multisearchPanel.add(this.multisearchContainer, eastLayoutData);

	this.viewport.add(this.multisearchPanel, multisearchLayoutData);

	BorderLayoutData pinEastLayoutData = new BorderLayoutData(Style.LayoutRegion.NORTH, 20.0F);
	pinEastLayoutData.setMargins(new Margins(0, 0, 0, 10));
	Image pinIconEast = new Image();
	pinIconEast.setResource(this.menuIcon.pin_out());
	pinIconEast.addStyleName("x3-pin");

	pinIconEast.addClickHandler(new ClickHandler(pinIconEast)
	{
		public void onClick(ClickEvent event) {
			PortalView.this.logger.log(Level.FINE, " - Click Expand/Collapse Menu button ");
			if (PortalView.this.multisearchPanel.isFrozen()) {
				PortalView.this.borderLayout.collapse(Style.LayoutRegion.EAST);
				PortalView.this.borderLayout.hide(Style.LayoutRegion.EAST);
				PortalView.this.multisearchPanel.setFrozen(false);
				this.val$pinIconEast.setResource(PortalView.this.menuIcon.pin_out());
				PortalView.this.getPortalTabPanel().getSelectedItem().applyStyle();
				PortalView.this.multisearchButton.setStyleAttribute("display", "block");
			} else {
				PortalView.this.borderLayout.expand(Style.LayoutRegion.EAST);
				PortalView.this.multisearchPanel.setFrozen(true);
				this.val$pinIconEast.setResource(PortalView.this.menuIcon.pin_in());
				PortalView.this.getPortalTabPanel().getSelectedItem().applyStyle();
				PortalView.this.multisearchButton.setStyleAttribute("display", "none");
			}
		}
	});
	this.multisearchContainer.add(pinIconEast, pinEastLayoutData);

	this.multisearchButton = new Button();
	this.multisearchButton.setId("x3-multisearch-button");
	this.multisearchButton.setIcon(AbstractImagePrototype.create(this.multiSearchBundle.search()));
	this.multisearchButton.addListener(Events.Select, new Listener()
	{
		public void handleEvent(ComponentEvent ce) {
			PortalCollapsePanel cp = (PortalCollapsePanel)PortalView.this.multisearchPanel.getData("collapse");
			if (cp != null)
				cp.setExpanded(true);
		}
	});
	this.centerContainer.add(this.multisearchButton);

	this.borderLayout.collapse(Style.LayoutRegion.EAST);
	this.borderLayout.hide(Style.LayoutRegion.EAST);
	this.multisearchPanel.setFrozen(false);

	this.loginController = new LoginController(this.controller);
	this.loginWindow = new LoginWindow(this.loginController);
	this.loginWindow.addListener(Events.Hide, new Listener()
	{
		public void handleEvent(BaseEvent be)
		{
			if (PortalView.this.lastTab != null) {
				if (PortalView.this.lastTab.getTabPanel() == null)
					PortalView.this.lastTab = ((TabItem)PortalView.this.tabContentPanel.getItem(0));
				PortalView.this.tabContentPanel.setSelection(PortalView.this.lastTab);
			}
			else {
				PortalView.this.loginWindow.show();
				PortalView.this.layout(true);
			}
		}
	});
	BorderLayoutData serviceLayoutData = new BorderLayoutData(Style.LayoutRegion.SOUTH, 0.0F);
	LayoutContainer serviceContainer = new LayoutContainer(new FlowLayout());
	serviceContainer.setId("x3-service-container");
	this.viewport.add(serviceContainer, serviceLayoutData);

	PreferenceWidget preferenceWidget = new PreferenceWidget(this);
	serviceContainer.add(preferenceWidget);

	FullscreenerWidget fullscreenerWidget = new FullscreenerWidget(this);
	serviceContainer.add(fullscreenerWidget);
	this.baseWidgets.add(fullscreenerWidget);

	TransferBoxManager.createInstance(this);
	serviceContainer.add(new DownloadWidget(this));
	serviceContainer.add(new UploadWidget(this));

	serviceContainer.add(new GenericActionWidget(this));

	DialogWidget dialogWidget = new DialogWidget(this);
	serviceContainer.add(dialogWidget);
	this.baseWidgets.add(dialogWidget);

	ManagerWidget managerWidget = new ManagerWidget(this);
	serviceContainer.add(managerWidget);

	serviceContainer.add(new ReloadWidget(this));

	LocateWidget locateWidget = new LocateWidget(this);
	serviceContainer.add(locateWidget);

	this.tabContentPanel = new PortalTabPanel(this);
	this.tabContentPanel.setId("x3-spaces");
	this.tabContentPanel.setResizeTabs(false);
	this.tabContentPanel.setAnimScroll(true);
	this.tabContentPanel.setTabScroll(true);
	this.tabContentPanel.setCloseContextMenu(true);
	this.tabContentPanel.setPlain(false);
	this.tabContentPanel.setMinTabWidth(150);

	BorderLayoutData tabContentLayoutData = new BorderLayoutData(Style.LayoutRegion.CENTER);

	this.centerContainer.add(this.tabContentPanel, tabContentLayoutData);

	PortalView refView = this;

	this.selectTabListener = new Listener(locateWidget, refView) {
		MultiSearchWidget multisearchWidget = null;

		public void handleEvent(TabPanelEvent be)
		{
			PortalView.this.lastTab = ((TabItem)be.getItem());

			this.val$locateWidget.makeAllCurrentWidgetLoseFocus(((PortalTabItem)PortalView.this.lastTab).getUid());

			FilterController.getInstance().fetchMenus(((PortalTabItem)PortalView.this.lastTab).getUid(), null);

			if (PortalView.this.menuWidget != null) {
				((MenuController)PortalView.this.menuWidget.getController()).fetchMenus(((PortalTabItem)PortalView.this.lastTab).getUid(), null, null, true);
			}
			if (PortalView.this.optionWidget != null) {
				PortalView.this.optionWidget.setContainerUid(((PortalTabItem)PortalView.this.lastTab).getUid());
			}

			SimpleCallback callbackPref = new SimpleCallback()
			{
				public void onError(Throwable t)
				{
				}

				public void onDone()
				{
					((PortalTabItem)PortalView.this.lastTab).displayContent();
				}
			};
			PreferenceController.getInstance().updatePreference(((PortalTabItem)PortalView.this.lastTab).getUid(), callbackPref);

			if (this.multisearchWidget == null) {
				this.multisearchWidget = new MultiSearchWidget(this.val$refView, ((PortalTabItem)PortalView.this.lastTab).getUid());
				this.multisearchWidget.setId("x3-portal-multisearch");

				this.multisearchWidget.setHeaderVisible(false);

				BorderLayoutData multisearchContentLayoutData = new BorderLayoutData(Style.LayoutRegion.CENTER);

				PortalView.this.multisearchContainer.add(this.multisearchWidget, multisearchContentLayoutData);
				PortalView.this.multisearchContainer.sync(true);

				PortalView.this.baseWidgets.add(this.multisearchWidget);
			}
			else {
				((MultiSearchController)this.multisearchWidget.getController()).setContainerUid(((PortalTabItem)PortalView.this.lastTab).getUid());
			}
			if (this.multisearchWidget.isRendered())
				this.multisearchWidget.applyStyle();
		}
	};
	SpaceBuilder.createInstance(this);
}

Le composant PortalView représente la "page" de l'application. Par conséquent, le widget LocateWidget est automatiquement inclus.


Rafraîchissement

L'analyse du traitement de l'URL s'est terminée après avoir identifié l'origine dans PortalView. Cependant cela n'explique par le rafraîchissement automatique de l'interface à savoir :

  • Positionnement sur le bon espace de travail.
  • Rafraîchissement de l'arbre de navigation.
  • Rafraîchissement des différents widgets.

Tout va s'effectuer lors de l'initialisation du controller LocateController. Dans le constructeur, il y a l'initialisation de la variable oac qui est une fonction exécutée lors de l'écoute d'événements :

private LocateController(LocateWidget widgetArg) {
	super(widgetArg);
	this.widget = widgetArg;

	this.location = ClientUtils.getLocation();
	this.locateId = ClientUtils.parseParameters(this.location, "locateId");
	this.locateTarget = ClientUtils.parseParameters(ClientUtils.getLocation(), "locateTarget", X3TypeWidgets.BROWSER.getType());
	this.locatePath = ClientUtils.parseParameters(ClientUtils.getLocation(), "locatePath");

	if (this.locateId != null) {
		this.locateRequired = true;
	}

	this.oac = new OpenAjaxCallback(this.widget)
	{
		public void onReceive(X3PubSubEvents event, OpenAjaxMessage message)
		{
			if (!message.getSender().equals(X3InternalWidgets.LOCATE.toString())) {
				if (message.getSender().equals(X3InternalWidgets.MULTISEARCH.toString())) {
					LocateController.this.logger.log(Level.INFO, "receiving selector request");
					String cuid = message.getContainerUid();

					PortalTabItem currentTabItem = LocateController.this.widget.getPortalView().getPortalTabItem();
					PortalTabItem targetTabItem = LocateController.this.getPortalTabItem(cuid);

					String targetRepositoryName = targetTabItem.getRepositoryName();
					String targetUserName = targetTabItem.getUserName();

					List tabItems = new ArrayList();
					for (int i = 0; i < LocateController.this.widget.getPortalView().getPortalTabPanel().getItemCount(); i++) {
						PortalTabItem tempTabItem = (PortalTabItem)LocateController.this.widget.getPortalView().getPortalTabPanel().getItem(i);

						if ((tempTabItem.getRepositoryName() == null) || (!tempTabItem.getRepositoryName().equals(targetRepositoryName)) || (tempTabItem.getUserName() == null) || (!tempTabItem.getUserName().equals(targetUserName)))
							continue;
						tabItems.add(tempTabItem);
					}

					if (LocateController.this.getSpacesForWidgetType(tabItems, X3TypeWidgets.BROWSER.toString()).size() > 0);
					tabItems = LocateController.this.getSpacesForWidgetType(tabItems, X3TypeWidgets.BROWSER.toString());

					if (tabItems.size() > 0) {
						if (!tabItems.contains(currentTabItem)) {
							targetTabItem = (PortalTabItem)tabItems.get(0);
							targetTabItem.ensureAutoSelect();
							message.setContainerUid(targetTabItem.getUid());
						}
						else {
							message.setContainerUid(currentTabItem.getUid());
						}
						LocateController.this.logger.log(Level.INFO, "publishing again the message");
						message.setTargetType(null);
						message.setSender(X3InternalWidgets.LOCATE.toString());
						if ((!event.equals(X3PubSubEvents.D2_ACTION_EXECUTE_MANAGER)) && (!event.equals(X3PubSubEvents.D2_ACTION_DISPLAY_DIALOG))) {
							LocateController.this.widget.publish(event, message);
						}
					}
				}
				if ((event.equals(X3PubSubEvents.D2_ACTION_LOCATE_OBJECT)) || (event.equals(X3PubSubEvents.D2_ACTION_LOCATE_SEARCH)))
				{
					PortalTabItem tabItem = LocateController.this.widget.getPortalView().getPortalTabItem();
					List widgets = tabItem.getAllWidgets();
					boolean alreadySelected = false;

					for (int i = 0; i < widgets.size(); i++) {
						BaseWidget widget = (BaseWidget)widgets.get(i);

						if ((widget.getWidgetType().equals(X3TypeWidgets.DOCLIST.getType())) || (widget.getWidgetType().equals(X3TypeWidgets.DOCGALLERY.getType()))) {
							if ((widget.getParent() instanceof WidgetTabItem)) {
								WidgetTabItem widgetTabItem = (WidgetTabItem)widget.getParent();
								WidgetTabPanel widgetTabPanel = (WidgetTabPanel)widgetTabItem.getParent();

								if (widgetTabPanel.getSelectedItem().equals(widgetTabItem)) {
									alreadySelected = true;
									break;
								}
							} else {
								if ((!(widget.getParent() instanceof PortalAccordionLayoutContainer)) || 
									(!widget.isExpanded())) continue;
								alreadySelected = true;
								break;
							}
						}

					}

					if ((!alreadySelected) && 
						(!LocateController.this.selectWidget(null, X3TypeWidgets.DOCLIST.getType()))) {
						LocateController.this.selectWidget(null, X3TypeWidgets.DOCGALLERY.getType());
					}

					LocateController.this.selectWidget(null, X3TypeWidgets.BROWSER.getType());
				}
				else if (event.equals(X3PubSubEvents.D2_ACTION_LOCATE_TASK)) {
					LocateController.this.selectWidget(null, X3TypeWidgets.TASKS.getType());
					LocateController.this.selectWidget(null, X3TypeWidgets.TASKFOLDERS.getType());
				}
			}
		}
	};
	subscribeToMultiSearchEvents();
	subscribeToWidgetInitialize();
	subscribeToCopyLocator();
	subscribeToAutoPublishLocate();
}

Cette instance est ensuite utilisée pour écouter les événements :

  • X3PubSubEvents.D2_ACTION_DISPLAY_DIALOG
  • X3PubSubEvents.D2_ACTION_EXECUTE_MANAGER
  • X3PubSubEvents.D2_ACTION_LOCATE_OBJECT

Ceci est mis en place dans la méthode subscribeToMultiSearchEvents, exécutée depuis le constructeur :

public void subscribeToMultiSearchEvents()
{
	try
	{
		subscribeToEvent(X3PubSubEvents.D2_ACTION_DISPLAY_DIALOG, this.oac);
		subscribeToEvent(X3PubSubEvents.D2_ACTION_EXECUTE_MANAGER, this.oac);
		subscribeToEvent(X3PubSubEvents.D2_ACTION_LOCATE_OBJECT, this.oac);
	}
	catch (Exception e) {
		this.logger.log(Level.SEVERE, "error", e);
	}
}

L'analyse est un peu plus complexe et ferrait grossir inutilement l'article. Mais il faut retenir que le controller LocateController écoute lui même l'événement X3PubSubEvents.D2_ACTION_LOCATE_OBJECT. Lors de son écoute, les espaces de travail en cours d'affichage sont parcourus, afin de provoquer l'affichage des widgets cibles et force la sélection sur le widget de l'arbre de navigation.


Résumé

Comme annoncé au début de l'article, cela devient évident qu'il fallait chercher vers le widget LocateWidget pour comprendre le fonctionnement des liens URLS. Grossièrement, le fonction se résume aux étapes suivantes:

  • Affichage de la page principale, composant PortalView.
  • Initialisation du widget LocateWidget.
  • Lors de l'initialisation, récupération des paramètres depuis l'URL.
  • Levé d'un événement avec l'identifiant de l'objet.
  • Ecoute de cet événement pour positionner sur le bon espace de travail.
  • Traitement de l'événement par le widget BrowerWidget, qui lui même va lever les événements pour rafraîchir les différents widgets.

A la lecture du constructeur de LocateController, trois paramètres sont lus depuis l'URL :

  • locateId, identifiant de l'objet
  • locateTarget, identifiant du widget cible. Cet argument n'est utilisé que lors des liens sur les tâches ou des recherches.
  • locatePath, utilisé pour peupler la variable, final, locatePath. Or celle-ci n'est pas utilisée par la suite...

Le principe de cette analyse s'applique pour les liens sur les tâches ou les recherches. Mais il est impossible d'utiliser des arguments spécifiques pour passer des informations de recherche par exemple. Le fonctionnement ne peut donc pas être étendu dans l'état, ce qui devrait changer avec les prochaines versions de D2.


Dans le cas d'un répertoire, ce n'est pas le contenu du répertoire qui est affiché, mais son répertoire parent. Il ne faut donc pas s'attendre à avoir le contenu du répertoire cible, identifiant dans l'URL, affiché mais bien le contenu du répertoire parent.


Construction liens

En complément, l'analyse de la construction du lien s'effectue tout simplement en lisant le code de la fonction getUrlForLocate, mettant en évidence l'utilisation des différents paramètres :

public String getUrlForLocate(String objectId, String objectType, String parentType, String parentPath) {
	String tmpLocation = this.location;

	int pos = tmpLocation.indexOf('?');

	int posSharp = tmpLocation.indexOf("#d2");
	if (pos != -1)
		tmpLocation = tmpLocation.substring(0, pos);
	else if (posSharp != -1) {
		tmpLocation = tmpLocation.substring(0, posSharp);
	}
	StringBuilder url = new StringBuilder();
	url.append(tmpLocation);
	url.append("?");

	if (!GWT.isProdMode())
	{
		String gwtCodeServer = ClientUtils.parseParameters(this.location, "gwt.codesvr");
		if (gwtCodeServer != null)
		{
			url.append("gwt.codesvr=");
			url.append(gwtCodeServer);
			url.append("&");
		}
	}

	url.append("docbase").append("=").append(this.widget.getPortalView().getPortalTabItem().getRepositoryName());
	url.append("&");
	url.append("locateId").append("=").append(objectId);

	if ((parentType != null) && (parentType.equals("node_inbox_tasks")))
	{
		url.append("&");
		url.append("locateTarget").append("=").append(X3TypeWidgets.TASKFOLDERS.getType());
	}
	else if ((objectType != null) && ((objectType.equals("d2c_query_category")) || (objectType.equals("d2c_query"))))
	{
		url.append("&");
		url.append("locateTarget").append("=").append(X3TypeWidgets.SEARCH.getType());
	}

	return url.toString();
}