/*
* RHQ Management Platform
* Copyright (C) 2005-2014 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program 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 and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.coregui.client.inventory.resource.detail;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.smartgwt.client.data.DSCallback;
import com.smartgwt.client.data.DSRequest;
import com.smartgwt.client.data.DSResponse;
import com.smartgwt.client.types.SelectionStyle;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import com.smartgwt.client.widgets.grid.events.SelectionChangedHandler;
import com.smartgwt.client.widgets.grid.events.SelectionEvent;
import com.smartgwt.client.widgets.menu.Menu;
import com.smartgwt.client.widgets.menu.MenuItem;
import com.smartgwt.client.widgets.menu.MenuItemSeparator;
import com.smartgwt.client.widgets.menu.events.ClickHandler;
import com.smartgwt.client.widgets.menu.events.MenuItemClickEvent;
import com.smartgwt.client.widgets.tree.Tree;
import com.smartgwt.client.widgets.tree.TreeGrid;
import com.smartgwt.client.widgets.tree.TreeNode;
import com.smartgwt.client.widgets.tree.events.DataArrivedEvent;
import com.smartgwt.client.widgets.tree.events.DataArrivedHandler;
import com.smartgwt.client.widgets.tree.events.NodeContextClickEvent;
import com.smartgwt.client.widgets.tree.events.NodeContextClickHandler;
import org.rhq.core.domain.criteria.ResourceCriteria;
import org.rhq.core.domain.criteria.ResourceGroupCriteria;
import org.rhq.core.domain.operation.OperationDefinition;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.domain.resource.composite.ResourceComposite;
import org.rhq.core.domain.resource.composite.ResourceLineageComposite;
import org.rhq.core.domain.resource.composite.ResourcePermission;
import org.rhq.core.domain.resource.group.ResourceGroup;
import org.rhq.core.domain.util.PageList;
import org.rhq.core.domain.util.ResourceTypeUtility;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.LinkManager;
import org.rhq.coregui.client.UserSessionManager;
import org.rhq.coregui.client.ViewId;
import org.rhq.coregui.client.ViewPath;
import org.rhq.coregui.client.components.tree.EnhancedTreeNode;
import org.rhq.coregui.client.gwt.GWTServiceLookup;
import org.rhq.coregui.client.gwt.ResourceGWTServiceAsync;
import org.rhq.coregui.client.gwt.ResourceGroupGWTServiceAsync;
import org.rhq.coregui.client.inventory.InventoryView;
import org.rhq.coregui.client.inventory.groups.detail.ResourceGroupContextMenu;
import org.rhq.coregui.client.inventory.groups.detail.ResourceGroupDetailView;
import org.rhq.coregui.client.inventory.resource.detail.ResourceTreeDatasource.AutoGroupTreeNode;
import org.rhq.coregui.client.inventory.resource.detail.ResourceTreeDatasource.ResourceTreeNode;
import org.rhq.coregui.client.inventory.resource.detail.ResourceTreeDatasource.SubCategoryTreeNode;
import org.rhq.coregui.client.inventory.resource.factory.ResourceFactoryCreateWizard;
import org.rhq.coregui.client.inventory.resource.factory.ResourceFactoryImportWizard;
import org.rhq.coregui.client.inventory.resource.type.ResourceTypeRepository;
import org.rhq.coregui.client.util.Log;
import org.rhq.coregui.client.util.enhanced.EnhancedVLayout;
import org.rhq.coregui.client.util.message.Message;
import org.rhq.coregui.client.util.preferences.UserPreferenceNames.UiSubsystem;
/**
* @author Jay Shaughnessy
* @author Greg Hinkle
*/
public class ResourceTreeView extends EnhancedVLayout {
private TreeGrid treeGrid;
private String selectedNodeId;
// private Label loadingLabel;
private Resource rootResource;
private Menu resourceContextMenu;
private ResourceGroupContextMenu autoGroupContextMenu;
// Maps autogroup/type backing group ids to the corresponding autogroup/type nodes.
private Map<Integer, AutoGroupTreeNode> autoGroupNodeMap = new HashMap<Integer, AutoGroupTreeNode>();
private Map<UiSubsystem, Boolean> showSubsystems;
public ResourceTreeView() {
super();
setWidth("250");
setHeight100();
setShowResizeBar(true);
}
@Override
public void onInit() {
}
private void buildTree() {
treeGrid = new CustomResourceTreeGrid();
// don't auto-fetch data, the initial fetch is requested manually using initial lineage information
treeGrid.setAutoFetchData(false);
treeGrid.setAnimateFolders(false);
treeGrid.setSelectionType(SelectionStyle.SINGLE);
treeGrid.setShowRollOver(false);
treeGrid.setSortField("name");
treeGrid.setShowHeader(false);
// disable what the tree grid may do, defer to loadingLabel to handle all of our cases
treeGrid.setLoadingDataMessage(null);
treeGrid.setLeaveScrollbarGap(false);
resourceContextMenu = new Menu();
autoGroupContextMenu = new ResourceGroupContextMenu();
treeGrid.addSelectionChangedHandler(new SelectionChangedHandler() {
public void onSelectionChanged(SelectionEvent selectionEvent) {
if (!selectionEvent.isRightButtonDown() && selectionEvent.getState()) {
ListGridRecord selectedRecord = treeGrid.getSelectedRecord();
if (selectedRecord instanceof ResourceTreeNode) {
ResourceTreeNode resourceNode = (ResourceTreeNode) selectedRecord;
if (!resourceNode.isLocked()) {
Log.info("Resource Node selected in tree: " + selectedRecord);
selectedNodeId = resourceNode.getID();
String viewPath = "Resource/" + resourceNode.getResource().getId();
String currentViewPath = History.getToken();
if (!currentViewPath.startsWith(viewPath)) {
CoreGUI.goToView(viewPath);
}
} else {
treeGrid.deselectRecord(resourceNode);
if (null != selectedNodeId) {
treeGrid.selectRecord(treeGrid.getTree().findById(selectedNodeId));
}
}
} else if (selectedRecord instanceof AutoGroupTreeNode) {
Log.info("AutoGroup Node selected in tree: " + selectedRecord);
AutoGroupTreeNode agNode = (AutoGroupTreeNode) selectedRecord;
selectedNodeId = agNode.getID();
// [BZ 827203] Disable this view to prevent fast-click issues. It will get re-enabled when
// the detail resource or group view is done with its async init and calls
// notifyViewRenderedListeners() on itself.
disable();
try {
CoreGUI.showBusy(true);
getAutoGroupBackingGroup(agNode, new AsyncCallback<ResourceGroup>() {
public void onSuccess(ResourceGroup result) {
CoreGUI.showBusy(false);
;
renderAutoGroup(result);
// Make sure to re-enable ourselves.
enable();
}
public void onFailure(Throwable caught) {
CoreGUI.showBusy(false);
// Make sure to re-enable ourselves.
enable();
CoreGUI.getErrorHandler().handleError(MSG.view_tree_common_loadFailed_selection(),
caught);
}
});
} catch (RuntimeException re) {
CoreGUI.showBusy(false);
// Make sure to re-enable ourselves.
enable();
CoreGUI.getErrorHandler().handleError(MSG.view_tree_common_loadFailed_selection(), re);
}
} else if (selectedRecord instanceof SubCategoryTreeNode) {
treeGrid.deselectRecord(selectedRecord);
treeGrid.getTree().openFolder((TreeNode) selectedRecord);
if (null != selectedNodeId) {
treeGrid.selectRecord(treeGrid.getTree().findById(selectedNodeId));
}
} else if (null != selectedRecord) {
// TODO: Should we thrown an exception, don't know what this is
treeGrid.deselectRecord(selectedRecord);
if (null != selectedNodeId) {
treeGrid.selectRecord(treeGrid.getTree().findById(selectedNodeId));
}
}
}
}
});
// This constructs the context menu for the resource at the time of the click.
// setContextMenu(resourceContextMenu);
treeGrid.addNodeContextClickHandler(new NodeContextClickHandler() {
public void onNodeContextClick(final NodeContextClickEvent event) {
// stop the browser right-click menu
event.cancel();
// don't select the node on a right click, since we're not navigating to it
treeGrid.deselectRecord(event.getNode());
TreeNode eventNode = event.getNode();
// re-select the current node if necessary
if (null != selectedNodeId) {
TreeNode selectedNode = treeGrid.getTree().findById(selectedNodeId);
if (!eventNode.equals(selectedNode)) {
treeGrid.selectRecord(selectedNode);
}
}
if (eventNode instanceof AutoGroupTreeNode) {
showContextMenu((AutoGroupTreeNode) eventNode);
} else if (eventNode instanceof ResourceTreeNode) {
if (!((ResourceTreeNode) eventNode).isLocked()) {
showContextMenu((ResourceTreeNode) eventNode);
}
}
}
});
treeGrid.addDataArrivedHandler(new DataArrivedHandler() {
public void onDataArrived(DataArrivedEvent dataArrivedEvent) {
TreeNode parent = dataArrivedEvent.getParentNode();
// always expand the parent when new data arrives underneath, it means the user has expanded the node
if (parent instanceof EnhancedTreeNode) {
treeGrid.getTree().openFolder(parent);
treeGrid.markForRedraw();
}
// if for some reason expansion has caused our selection to be lost then make sure it is again properly selected
ListGridRecord selectedRecord = treeGrid.getSelectedRecord();
TreeNode selectedNode = null != selectedNodeId ? treeGrid.getTree().findById(selectedNodeId) : null;
if (null != selectedNode && !selectedNode.equals(selectedRecord)) {
updateSelection();
}
}
});
}
private void getAutoGroupBackingGroup(final AutoGroupTreeNode agNode, final AsyncCallback<ResourceGroup> callback) {
final ResourceGroupGWTServiceAsync resourceGroupService = GWTServiceLookup.getResourceGroupService();
// get the children tree nodes and build a child resourceId array
TreeNode[] children = treeGrid.getTree().getChildren(agNode);
final int[] childIds = new int[children.length];
for (int i = 0, size = children.length; (i < size); ++i) {
childIds[i] = ((ResourceTreeNode) children[i]).getResource().getId();
}
// get the backing group if it exists, otherwise create the group
ResourceGroupCriteria criteria = new ResourceGroupCriteria();
criteria.addFilterPrivate(true);
criteria.addFilterResourceTypeId(agNode.getResourceType().getId());
criteria.addFilterAutoGroupParentResourceId(agNode.getParentResource().getId());
criteria.addFilterVisible(false);
CoreGUI.showBusy(true);
;
resourceGroupService.findResourceGroupsByCriteria(criteria, new AsyncCallback<PageList<ResourceGroup>>() {
public void onFailure(Throwable caught) {
CoreGUI.showBusy(false);
;
callback.onFailure(new RuntimeException(MSG.view_tree_common_loadFailed_node(), caught));
}
public void onSuccess(PageList<ResourceGroup> result) {
if (result.isEmpty()) {
// Not found, create new backing group
// the backing group name is a display name using a unique parentResource-resourceType combo
final String backingGroupName = agNode.getBackingGroupName();
ResourceGroup backingGroup = new ResourceGroup(backingGroupName);
backingGroup.setAutoGroupParentResource(agNode.getParentResource());
backingGroup.setResourceType(agNode.getResourceType());
backingGroup.setVisible(false);
resourceGroupService.createPrivateResourceGroup(backingGroup, childIds,
new AsyncCallback<ResourceGroup>() {
public void onFailure(Throwable caught) {
CoreGUI.showBusy(false);
;
callback.onFailure(new RuntimeException(MSG.view_tree_common_loadFailed_create(),
caught));
}
public void onSuccess(ResourceGroup result) {
CoreGUI.showBusy(false);
;
// store a map entry from backingGroupId to AGTreeNode so we can easily
// get back to this node given the id of the backing group (from the viewpath)
autoGroupNodeMap.put(result.getId(), agNode);
// now that we know that backing group id, hold onto it in the node in case we need it
agNode.setResourceGroupId(result.getId());
callback.onSuccess(result);
}
});
} else {
// backing group found
final ResourceGroup backingGroup = result.get(0);
// store a map entry from backingGroupId to AGTreeNode so we can easily
// get back to this node given the id of the backing group (from the viewpath)
autoGroupNodeMap.put(backingGroup.getId(), agNode);
// now that we know that backing group id, hold onto it in the node in case we need it
agNode.setResourceGroupId(backingGroup.getId());
// make sure the members are correct before rendering
resourceGroupService.setAssignedResources(backingGroup.getId(), childIds, false,
new AsyncCallback<Void>() {
public void onFailure(Throwable caught) {
CoreGUI.showBusy(false);
;
callback.onFailure(new RuntimeException(MSG.view_tree_common_loadFailed_update(),
caught));
}
public void onSuccess(Void result) {
CoreGUI.showBusy(false);
;
callback.onSuccess(backingGroup);
}
});
}
}
});
}
private void renderAutoGroup(ResourceGroup backingGroup) {
String viewPath = ResourceGroupDetailView.AUTO_GROUP_VIEW + "/" + backingGroup.getId();
String currentViewPath = History.getToken();
if (!currentViewPath.startsWith(viewPath)) {
CoreGUI.goToView(viewPath);
}
}
private void updateSelection() {
updateSelection(false);
}
private void updateSelection(boolean isRefresh) {
TreeNode selectedNode;
if (treeGrid != null && treeGrid.getTree() != null
&& (selectedNode = treeGrid.getTree().findById(selectedNodeId)) != null) {
TreeNode[] parents = treeGrid.getTree().getParents(selectedNode);
treeGrid.getTree().openFolders(parents);
treeGrid.getTree().openFolder(selectedNode);
if (!selectedNode.equals(treeGrid.getSelectedRecord())) {
treeGrid.deselectAllRecords();
treeGrid.selectRecord(selectedNode);
}
if (isRefresh) {
treeGrid.getTree().reloadChildren(selectedNode);
}
treeGrid.markForRedraw();
}
}
private void showContextMenu(final AutoGroupTreeNode agNode) {
getAutoGroupBackingGroup(agNode, new AsyncCallback<ResourceGroup>() {
public void onFailure(Throwable caught) {
CoreGUI.getErrorHandler().handleError(MSG.view_tree_common_loadFailed_selection(), caught);
}
public void onSuccess(ResourceGroup result) {
autoGroupContextMenu.showContextMenu(ResourceTreeView.this, treeGrid, agNode, result);
}
});
}
private void showContextMenu(final ResourceTreeNode node) {
final Resource resource = node.getResource();
final int resourceId = resource.getId();
// fetch the resource composite, we need resource permission info for enablement decisions
ResourceCriteria criteria = new ResourceCriteria();
criteria.addFilterId(resourceId);
criteria.fetchSchedules(true);
GWTServiceLookup.getResourceService().findResourceCompositesByCriteria(criteria,
new AsyncCallback<PageList<ResourceComposite>>() {
public void onFailure(Throwable caught) {
CoreGUI.getMessageCenter().notify(
new Message(MSG.view_inventory_resource_loadFailed(String.valueOf(resourceId)),
Message.Severity.Warning));
CoreGUI.goToView(InventoryView.VIEW_ID.getName());
}
public void onSuccess(PageList<ResourceComposite> result) {
if (result.isEmpty()) {
onFailure(new Exception(MSG.view_inventory_resource_loadFailed(String.valueOf(resourceId))));
} else {
final ResourceComposite resourceComposite = result.get(0);
// make sure we have all the Type information necessary to render the menus
ResourceType type = resource.getResourceType();
ResourceTypeRepository.Cache.getInstance().getResourceTypes(
type.getId(),
EnumSet.of(ResourceTypeRepository.MetadataType.operations,
ResourceTypeRepository.MetadataType.children,
ResourceTypeRepository.MetadataType.pluginConfigurationDefinition,
ResourceTypeRepository.MetadataType.resourceConfigurationDefinition,
ResourceTypeRepository.MetadataType.measurements),
new ResourceTypeRepository.TypeLoadedCallback() {
public void onTypesLoaded(ResourceType type) {
buildAndShowResourceContextMenu(node, resourceComposite, type);
}
});
}
}
});
}
private void buildAndShowResourceContextMenu(final ResourceTreeNode node,
final ResourceComposite resourceComposite, final ResourceType resourceType) {
final Resource resource = resourceComposite.getResource();
final ResourcePermission resourcePermission = resourceComposite.getResourcePermission();
showSubsystems = UserSessionManager.getUserPreferences().getShowUiSubsystems();
// resource name
resourceContextMenu.setItems(new MenuItem(resource.getName()));
// resource type name
resourceContextMenu.addItem(new MenuItem(MSG.view_tree_common_contextMenu_type_name_label(ResourceTypeUtility
.displayName(resourceType))));
// separator
resourceContextMenu.addItem(new MenuItemSeparator());
// refresh node
MenuItem refresh = new MenuItem(MSG.common_button_refresh());
refresh.addClickHandler(new ClickHandler() {
public void onClick(MenuItemClickEvent event) {
contextMenuRefresh(treeGrid, node, false);
}
});
resourceContextMenu.addItem(refresh);
// separator
resourceContextMenu.addItem(new MenuItemSeparator());
// child resources
MenuItem childResources = new MenuItem(MSG.view_tabs_common_child_resources());
boolean childResourcesEnabled = resourceType.getChildResourceTypes() != null
&& !resourceType.getChildResourceTypes().isEmpty();
childResources.setEnabled(childResourcesEnabled);
if (childResourcesEnabled) {
childResources.addClickHandler(new ClickHandler() {
public void onClick(MenuItemClickEvent event) {
CoreGUI.goToView(LinkManager.getResourceTabLink(resource.getId(), "Inventory", "Children"));
}
});
}
resourceContextMenu.addItem(childResources);
// plugin config
MenuItem pluginConfiguration = new MenuItem(MSG.view_tabs_common_connectionSettings());
boolean pluginConfigEnabled = resourceType.getPluginConfigurationDefinition() != null;
pluginConfiguration.setEnabled(pluginConfigEnabled);
if (pluginConfigEnabled) {
pluginConfiguration.addClickHandler(new ClickHandler() {
public void onClick(MenuItemClickEvent event) {
CoreGUI.goToView(LinkManager.getResourceTabLink(resource.getId(), "Inventory", "ConnectionSettings"));
}
});
}
resourceContextMenu.addItem(pluginConfiguration);
// resource config
if (showSubsystems.get(UiSubsystem.CONFIG)) {
MenuItem resourceConfiguration = new MenuItem(MSG.view_tree_common_contextMenu_resourceConfiguration());
boolean resourceConfigEnabled = resourcePermission.isConfigureRead()
&& resourceType.getResourceConfigurationDefinition() != null;
resourceConfiguration.setEnabled(resourceConfigEnabled);
if (resourceConfigEnabled) {
resourceConfiguration.addClickHandler(new ClickHandler() {
public void onClick(MenuItemClickEvent event) {
CoreGUI.goToView(LinkManager.getResourceTabLink(resource.getId(), "Configuration", "Current"));
}
});
}
resourceContextMenu.addItem(resourceConfiguration);
}
// separator
resourceContextMenu.addItem(new MenuItemSeparator());
// Operations Menu
if (showSubsystems.get(UiSubsystem.OPERATIONS)) {
MenuItem operations = new MenuItem(MSG.common_title_operations());
boolean operationsEnabled = (resourcePermission.isControl()
&& (resourceType.getOperationDefinitions() != null) && !resourceType.getOperationDefinitions()
.isEmpty());
operations.setEnabled(operationsEnabled);
if (operationsEnabled) {
Menu opSubMenu = new Menu();
//sort the display items alphabetically
TreeSet<String> ordered = new TreeSet<String>();
Map<String, OperationDefinition> definitionMap = new HashMap<String, OperationDefinition>();
for (OperationDefinition o : resourceType.getOperationDefinitions()) {
ordered.add(o.getDisplayName());
definitionMap.put(o.getDisplayName(), o);
}
for (String displayName : ordered) {
final OperationDefinition operationDefinition = definitionMap.get(displayName);
MenuItem operationItem = new MenuItem(operationDefinition.getDisplayName());
operationItem.addClickHandler(new ClickHandler() {
public void onClick(MenuItemClickEvent event) {
String viewPath = LinkManager.getResourceTabLink(resource.getId(),
ResourceDetailView.Tab.Operations.NAME, "Schedules")
+ "/0/"
+ operationDefinition.getId();
CoreGUI.goToView(viewPath);
}
});
opSubMenu.addItem(operationItem);
}
operations.setSubmenu(opSubMenu);
}
resourceContextMenu.addItem(operations);
}
// Create Child Menu and Manual Import Menu
final Set<ResourceType> creatableChildTypes = getCreatableChildTypes(resourceType);
final Set<ResourceType> importableChildTypes = getImportableChildTypes(resourceType);
final boolean hasCreatableTypes = !creatableChildTypes.isEmpty();
final boolean hasImportableTypes = !importableChildTypes.isEmpty();
boolean canCreate = resourcePermission.isCreateChildResources();
Integer[] singletonChildTypes = getSingletonChildTypes(resourceType);
// To properly filter Create Child and Import menus we need existing singleton child resources. If the
// user has created permission and the parent type has singleton child types and creatable or importable child
// types, perform an async call to fetch the singleton children.
if (canCreate && singletonChildTypes.length > 0 && (hasCreatableTypes || hasImportableTypes)) {
ResourceCriteria criteria = new ResourceCriteria();
criteria.addFilterParentResourceId(resource.getId());
criteria.addFilterResourceTypeIds(singletonChildTypes);
GWTServiceLookup.getResourceService().findResourcesByCriteria(criteria,
new AsyncCallback<PageList<Resource>>() {
@Override
public void onSuccess(PageList<Resource> singletonChildren) {
if (hasCreatableTypes) {
Map<String, ResourceType> displayNameMap = getDisplayNames(creatableChildTypes);
addMenu(MSG.common_button_create_child(), true, singletonChildren, resource,
displayNameMap, true);
}
if (hasImportableTypes) {
Map<String, ResourceType> displayNameMap = getDisplayNames(importableChildTypes);
addMenu(MSG.common_button_import(), true, singletonChildren, resource, displayNameMap,
false);
}
resourceContextMenu.showContextMenu();
}
@Override
public void onFailure(Throwable caught) {
Log.error("Error resources with parentId:" + resource.getId(), caught);
resourceContextMenu.showContextMenu();
}
});
} else if (canCreate && singletonChildTypes.length == 0 && (hasCreatableTypes || hasImportableTypes)) {
if (hasCreatableTypes) {
Map<String, ResourceType> displayNameMap = getDisplayNames(creatableChildTypes);
addMenu(MSG.common_button_create_child(), true, null, resource, displayNameMap, true);
}
if (hasImportableTypes) {
Map<String, ResourceType> displayNameMap = getDisplayNames(importableChildTypes);
addMenu(MSG.common_button_import(), true, null, resource, displayNameMap, false);
}
resourceContextMenu.showContextMenu();
} else {
if (!canCreate && hasCreatableTypes) {
addMenu(MSG.common_button_create_child(), false, null, null, null, true);
}
if (!canCreate && hasImportableTypes) {
addMenu(MSG.common_button_import(), false, null, null, null, false);
}
resourceContextMenu.showContextMenu();
}
}
private void addMenu(String name, boolean enabled, List<Resource> singletonChildren, Resource resource,
Map<String, ResourceType> displayNameMap, boolean isCreate) {
MenuItem menu = new MenuItem(name);
if (enabled) {
Menu subMenu = new Menu();
singletonChildren = (null == singletonChildren) ? new ArrayList<Resource>() : singletonChildren;
Menu filteredSubMenu = checkForSingletons(singletonChildren, resource, displayNameMap, subMenu, isCreate);
menu.setSubmenu(filteredSubMenu);
} else {
menu.setEnabled(false);
}
resourceContextMenu.addItem(menu);
}
private static Integer[] getSingletonChildTypes(ResourceType type) {
Set<Integer> results = new TreeSet<Integer>();
Set<ResourceType> childTypes = type.getChildResourceTypes();
for (ResourceType childType : childTypes) {
if (childType.isSingleton()) {
results.add(childType.getId());
}
}
return results.toArray(new Integer[results.size()]);
}
private Menu checkForSingletons(List<Resource> singletonChildren, final Resource resource,
Map<String, ResourceType> displayNameMap, Menu subMenu, final boolean isCreate) {
Set<String> displayNames = displayNameMap.keySet();
for (final String displayName : displayNames) {
MenuItem itemToAdd = new MenuItem(displayName);
final ResourceType type = displayNameMap.get(displayName);
boolean exists = false;
// disable the menu item for a singleton type that already has a singleton child resource
if (type.isSingleton()) {
for (Resource child : singletonChildren) {
exists = child.getResourceType().equals(displayNameMap.get(displayName));
if (exists) {
break;
}
}
}
// omit the type's menu item if the singleton already exists, otherwise add the necessary click handler.
// note: we omit as opposed to disable the menu item to match the behavior of the buttons in the Inventory
// -> Child Resources view, which has no facility to do the analogous disabling.
if (!exists) {
itemToAdd.addClickHandler(new ClickHandler() {
public void onClick(MenuItemClickEvent event) {
if (isCreate) {
ResourceFactoryCreateWizard.showCreateWizard(resource, type);
} else {
ResourceFactoryImportWizard.showImportWizard(resource, type);
}
}
});
subMenu.addItem(itemToAdd);
}
}
return subMenu;
}
public void refreshResource(Resource resource) {
// might happen if someone calls this very soon after creation when the tree didn't have enough time to load.
// In that case we might as well ignore this refresh, because we'd get the fresh data in the tree anyway
if (null == treeGrid || null == treeGrid.getTree()) {
return;
}
ResourceTreeNode resourceNode = (ResourceTreeNode) treeGrid.getTree().findById(
ResourceTreeNode.idOf(resource.getId()));
// no need to refresh a node that is not in the tree (not sure why that would be, it should actually be selected, I think)
if (null == resourceNode) {
return;
}
contextMenuRefresh(treeGrid, resourceNode, true);
}
/**
* Update the tree node (and all of its siblings, as reload is done from the parent). Also, refresh
* the detail view.
*
* @param treeGrid
* @param node
* @param treeOnly, if possible, don't refresh the entire view, limit refresh to the tree
*/
public void contextMenuRefresh(final TreeGrid treeGrid, TreeNode node, boolean treeOnly) {
// There are two cases to handle here:
// 1) The refresh node is an ancestor of the currently selected node.
// - We must re-navigate to the selected node because otherwise it will be lost as part of the subtree
// being refreshed.
// 2) The refresh node is not an ancestor of the current selected node (it is the node, is a descendant, or
// is in a different subtree.
// - In this case, reload the necessary portion of the tree, the selection should be maintained
// determine if the refresh node is an ancestor of the selected node.
boolean isAncestor = false;
ListGridRecord selectedRecord = treeGrid.getSelectedRecord();
if (null != selectedRecord) {
TreeNode ancestor = (TreeNode) selectedRecord;
while (null != (ancestor = treeGrid.getTree().getParent(ancestor))) {
if (node.equals(ancestor)) {
isAncestor = true;
break;
}
}
}
// Case 1: An ancestor
if (isAncestor) {
// prune the existing subtree rooted at the refresh node, it will be rebuilt when we re-navigate to the
// selected node.
treeGrid.getTree().remove(node);
if (selectedRecord instanceof ResourceTreeNode) {
setSelectedResource(((ResourceTreeNode) selectedRecord).getResource().getId(), false);
} else if (selectedRecord instanceof AutoGroupTreeNode) {
Integer backingGroupId = ((AutoGroupTreeNode) selectedRecord).getResourceGroupId();
if (null != backingGroupId) {
setSelectedAutoGroup(backingGroupId);
}
}
return;
}
// Case 2: Not an ancestor
// refresh the view. This won't refresh the tree since the resource hasn't changed, and
// we don't really want to refresh the whole tree anyway.
if (!treeOnly) {
CoreGUI.refresh();
}
// if this is the root just refresh from the top
Tree tree = treeGrid.getTree();
TreeNode refreshNode = tree.getParent(node);
if (null == refreshNode.getName()) {
tree.reloadChildren(node);
return;
}
// reloads are performed only on resource nodes. find the first parental resource node, traversing
// through autogroup and subcategory nodes as needed.
while (!(refreshNode instanceof ResourceTreeNode)) {
refreshNode = tree.getParent(refreshNode);
}
tree.reloadChildren(refreshNode);
}
private void setRootResource(Resource rootResource) {
this.rootResource = rootResource;
}
public void setSelectedResource(final int selectedResourceId, boolean isRefresh) {
selectedNodeId = ResourceTreeNode.idOf(selectedResourceId);
if (treeGrid != null && treeGrid.getTree() != null && (treeGrid.getTree().findById(selectedNodeId)) != null) {
// This is the case where the tree was previously loaded and we get fired to look at a different
// node in the same tree and just have to switch the selection
updateSelection(isRefresh);
} else {
// This is for cases where we have to load the tree fresh including down to the currently visible node
loadTree(selectedResourceId, true, null);
}
}
private void loadTree(final int selectedResourceId, final boolean updateSelection,
final AsyncCallback<Void> callback) {
if (updateSelection) {
selectedNodeId = ResourceTreeNode.idOf(selectedResourceId);
}
final ResourceGWTServiceAsync resourceService = GWTServiceLookup.getResourceService();
// This is an expensive call, but loads all nodes that are visible in the tree given a selected resource
CoreGUI.showBusy(true);
;
resourceService.getResourceLineageAndSiblings(selectedResourceId,
new AsyncCallback<List<ResourceLineageComposite>>() {
public void onFailure(Throwable caught) {
CoreGUI.showBusy(false);
;
boolean resourceDoesNotExist = caught.getMessage().contains("ResourceNotFoundException");
// If a Resource with the specified id does not exist, don't emit an error, since
// ResourceDetailView.loadSelectedItem() will take care of emitting one.
if (!resourceDoesNotExist) {
CoreGUI.getErrorHandler().handleError(MSG.view_tree_common_loadFailed_root(), caught);
}
}
public void onSuccess(List<ResourceLineageComposite> result) {
Resource root = result.get(0).getResource();
final List<Resource> lineage = new ArrayList<Resource>(result.size());
final List<Resource> lockedData = new ArrayList<Resource>();
for (ResourceLineageComposite r : result) {
lineage.add(r.getResource());
if (r.isLocked()) {
if (r.getResource().getId() == selectedResourceId) {
// The selected Resource itself is locked. This means the user doesn't have authz to be
// viewing this Resource period, so just abort loading of the tree.
return;
}
lockedData.add(r.getResource());
}
}
if (!root.equals(ResourceTreeView.this.rootResource)) {
if (treeGrid != null) {
treeGrid.destroy();
}
buildTree();
setRootResource(root);
// seed datasource with initial resource list and which ancestor resources are locked
ResourceTreeDatasource dataSource = new ResourceTreeDatasource(lineage, lockedData, treeGrid);
treeGrid.setDataSource(dataSource);
addMember(treeGrid);
treeGrid.fetchData(treeGrid.getCriteria(), new DSCallback() {
public void execute(DSResponse response, Object rawData, DSRequest request) {
Log.info("Done fetching data for tree.");
CoreGUI.showBusy(false);
;
if (updateSelection) {
updateSelection();
}
if (null != callback) {
callback.onSuccess(null);
}
}
});
// OK, there is no good reason for this to be here. But there are times when the
// callback above seems to get called prior to the treeGrid.getTree() having been
// updated with the fetched data. I think this is a smartgwt bug but it's hard to
// prove. Furthermore, given that the fetchData call is async there is really no
// reason why this should get called after the callback above. Having said all that,
// this seems to fix the issue as it 1) does currently get called after the callback
// and 2) the tree seems to be update immediately after the callback completes.
// So, for now use this, but TODO: find a better way.
if (updateSelection) {
updateSelection();
}
} else {
ResourceTypeRepository.Cache.getInstance().loadResourceTypes(lineage, null,
new ResourceTypeRepository.ResourceTypeLoadedCallback() {
public void onResourceTypeLoaded(List<Resource> result) {
treeGrid.getTree().linkNodes(
ResourceTreeDatasource.buildNodes(lineage, lockedData, treeGrid));
CoreGUI.showBusy(false);
;
if (updateSelection) {
TreeNode selectedNode = treeGrid.getTree().findById(selectedNodeId);
if (selectedNode != null && updateSelection) {
updateSelection();
} else {
CoreGUI.getMessageCenter().notify(
new Message(MSG.view_tree_common_loadFailed_selection(),
Message.Severity.Warning));
}
}
if (null != callback) {
callback.onSuccess(null);
}
}
});
}
}
});
}
public void setSelectedAutoGroup(final Integer selectedAutoGroupId) {
AutoGroupTreeNode selectedNode = autoGroupNodeMap.get(selectedAutoGroupId);
if (treeGrid != null && treeGrid.getTree() != null && selectedNode != null
&& null != treeGrid.getTree().findById(selectedNode.getID())) {
// This is the case where the tree was previously loaded and we get fired to look at a different
// node in the same tree and just have to switch the selection
this.selectedNodeId = selectedNode.getID();
updateSelection();
} else {
// This is for cases where we have to load the tree fresh including down to the currently visible node
final ResourceGroupGWTServiceAsync resourceGroupService = GWTServiceLookup.getResourceGroupService();
ResourceGroupCriteria criteria = new ResourceGroupCriteria();
criteria.addFilterId(selectedAutoGroupId);
criteria.addFilterVisible(false);
criteria.fetchResourceType(true);
CoreGUI.showBusy(true);
;
resourceGroupService.findResourceGroupsByCriteria(criteria, new AsyncCallback<PageList<ResourceGroup>>() {
public void onFailure(Throwable caught) {
CoreGUI.showBusy(false);
;
CoreGUI.getErrorHandler().handleError(MSG.view_tree_common_loadFailed_node(), caught);
}
public void onSuccess(PageList<ResourceGroup> result) {
final ResourceGroup backingGroup = result.get(0);
// load the tree up to the autogroup's parent resource. Don't select the resource node
// to avoid an unnecessary navigation to the resource, we just need the tree in place so
// we can navigate to the autogroup node.
loadTree(backingGroup.getAutoGroupParentResource().getId(), false, new AsyncCallback<Void>() {
public void onFailure(Throwable caught) {
CoreGUI.showBusy(false);
;
CoreGUI.getErrorHandler().handleError(MSG.view_tree_common_loadFailed_children(), caught);
}
public void onSuccess(Void arg) {
// get the node ID and use it to add a map entry, then call this again to finish up...
selectedNodeId = AutoGroupTreeNode.idOf(backingGroup.getAutoGroupParentResource(),
backingGroup.getResourceType());
AutoGroupTreeNode agNode = (AutoGroupTreeNode) treeGrid.getTree().findById(selectedNodeId);
autoGroupNodeMap.put(backingGroup.getId(), agNode);
updateSelection();
CoreGUI.showBusy(false);
;
}
});
}
});
}
}
public void renderView(ViewPath viewPath) {
ViewId currentViewId = viewPath.getCurrent();
String currentViewIdPath = currentViewId.getPath();
if ("AutoGroup".equals(currentViewIdPath)) {
// Move the currentViewId to the ID portion to play better with other code
currentViewId = viewPath.getNext();
String autoGroupIdString = currentViewId.getPath();
Integer autoGroupId = Integer.parseInt(autoGroupIdString);
setSelectedAutoGroup(autoGroupId);
} else {
String resourceIdString = currentViewId.getPath();
Integer resourceId = Integer.parseInt(resourceIdString);
setSelectedResource(resourceId, viewPath.isRefresh());
}
}
private static Set<ResourceType> getImportableChildTypes(ResourceType type) {
Set<ResourceType> results = new TreeSet<ResourceType>();
Set<ResourceType> childTypes = type.getChildResourceTypes();
for (ResourceType childType : childTypes) {
if (childType.isSupportsManualAdd()) {
results.add(childType);
}
}
return results;
}
private static Set<ResourceType> getCreatableChildTypes(ResourceType type) {
Set<ResourceType> results = new TreeSet<ResourceType>();
Set<ResourceType> childTypes = type.getChildResourceTypes();
for (ResourceType childType : childTypes) {
if (childType.isCreatable()) {
results.add(childType);
}
}
return results;
}
private static Map<String, ResourceType> getDisplayNames(Set<ResourceType> types) {
Set<String> allNames = new HashSet<String>();
Set<String> repeatedNames = new HashSet<String>();
for (ResourceType type : types) {
String typeName = type.getName();
if (allNames.contains(typeName)) {
repeatedNames.add(typeName);
} else {
allNames.add(typeName);
}
}
Map<String, ResourceType> results = new TreeMap<String, ResourceType>();
for (ResourceType type : types) {
String displayName = ResourceTypeUtility.displayName(type);
if (repeatedNames.contains(type.getName())) {
displayName += " (" + type.getPlugin() + " plugin)";
}
results.put(displayName, type);
}
return results;
}
}