/* * 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.groups.detail; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.user.client.History; import com.google.gwt.user.client.rpc.AsyncCallback; import com.smartgwt.client.types.SelectionStyle; import com.smartgwt.client.widgets.grid.HoverCustomizer; 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.tree.Tree; import com.smartgwt.client.widgets.tree.TreeGrid; import com.smartgwt.client.widgets.tree.TreeNode; import com.smartgwt.client.widgets.tree.events.NodeContextClickEvent; import com.smartgwt.client.widgets.tree.events.NodeContextClickHandler; import org.rhq.core.domain.criteria.ResourceGroupCriteria; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.domain.resource.group.ClusterKey; import org.rhq.core.domain.resource.group.GroupCategory; import org.rhq.core.domain.resource.group.ResourceGroup; import org.rhq.core.domain.resource.group.composite.ClusterFlyweight; import org.rhq.core.domain.resource.group.composite.ClusterKeyFlyweight; import org.rhq.core.domain.util.PageList; import org.rhq.core.domain.util.ResourceTypeUtility; import org.rhq.coregui.client.BookmarkableView; import org.rhq.coregui.client.CoreGUI; import org.rhq.coregui.client.ImageManager; import org.rhq.coregui.client.LinkManager; 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.inventory.resource.type.ResourceTypeRepository; import org.rhq.coregui.client.util.Log; import org.rhq.coregui.client.util.StringUtility; import org.rhq.coregui.client.util.enhanced.EnhancedUtility; import org.rhq.coregui.client.util.enhanced.EnhancedVLayout; /** * This is the view that renders the left hand tree for groups. * There are three main types of nodes in the group tree: * 1. Cluster Node - This represents a single aggregate resource that is of a specific resource type. * If a group has members with one or more resources with an identical resource key and type, * those identical resources are represented with one cluster node. Each cluster node * is associated with a resource type and an unique cluster key. * 2. Auto Type Group Node - This is a folder node whose children are all of a specific resource type. * The children are typically cluster nodes. An example of this kind of node is * "WARs", where there can be many different WARs deployed on an individual member resource * but a WAR can be clustered (copied) across many member resources. Each auto type group node * is associated with a resource type but they do not have cluster keys. * 3. Subcategory Node these are simply nodes that group other kinds of nodes. Plugin developers define subcategories * in plugin descriptors to organize resource types. Subcategories are not associated with any * particular resource type and do not have cluster keys. * * @author Greg Hinkle * @author Ian Springer * @author John Mazzitelli */ public class ResourceGroupTreeView extends EnhancedVLayout implements BookmarkableView { private static final String FAKE_ROOT_ID = "__fakeRoot__"; // id of the parent node of our real root node private TreeGrid treeGrid; private ViewId currentViewId; private Map<Integer, ResourceType> typeMap; private ResourceGroupTreeContextMenu contextMenu; // the root (top of tree) compat or mixed group private ResourceGroup rootResourceGroup; // the root (top of tree) compat or mixed group id (may bet set prior to rootResourceGroup fetch) private int rootGroupId; // the currently selected tree node private String currentNodeId; // the currently selected group backing the tree node private ResourceGroup currentGroup; private Comparator<ResourceGroupEnhancedTreeNode> treeNodeComparator = new Comparator<ResourceGroupEnhancedTreeNode>() { @Override public int compare(ResourceGroupEnhancedTreeNode o1, ResourceGroupEnhancedTreeNode o2) { // folders always come before leaf nodes boolean o1IsFolder = o1.isFolderNode(); boolean o2IsFolder = o2.isFolderNode(); if (o1IsFolder != o2IsFolder) { return o1IsFolder ? -1 : 1; } else if (o1IsFolder) { // subcategory and autoTypeGroup nodes (i.e. "folder icons") come before cluster type group nodes boolean o1IsClusterNode = o1.getClusterKey() != null; boolean o2IsClusterNode = o2.getClusterKey() != null; if (o1IsClusterNode != o2IsClusterNode) { return !o1IsClusterNode ? -1 : 1; } } // the two nodes are either both leaves or both folders; sort by name String s1 = o1.getName(); String s2 = o2.getName(); return s1.compareToIgnoreCase(s2); } }; public ResourceGroupTreeView() { super(); setWidth(250); setHeight100(); setShowResizeBar(true); } @Override protected void onInit() { super.onInit(); treeGrid = new CustomResourceGroupTreeGrid(); treeGrid.setWidth100(); treeGrid.setHeight100(); treeGrid.setAnimateFolders(false); treeGrid.setSelectionType(SelectionStyle.SINGLE); treeGrid.setShowRollOver(false); treeGrid.setShowHeader(false); treeGrid.setLeaveScrollbarGap(false); treeGrid.setOpenerImage("resources/dir.png"); treeGrid.setOpenerIconSize(16); treeGrid.setCanHover(true); treeGrid.setShowHover(true); treeGrid.setHoverWidth(250); treeGrid.setHoverWrap(true); treeGrid.setHoverCustomizer(new HoverCustomizer() { @Override public String hoverHTML(Object value, ListGridRecord record, int rowNum, int colNum) { String tooltip = record.getAttribute(ResourceGroupEnhancedTreeNode.TOOLTIP_KEY); return tooltip; } }); contextMenu = new ResourceGroupTreeContextMenu(); treeGrid.setContextMenu(contextMenu); 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, and // re-select the current node if necessary ResourceGroupEnhancedTreeNode contextNode = (ResourceGroupEnhancedTreeNode) event.getNode(); if (null != currentNodeId) { TreeNode currentNode = treeGrid.getTree().findById(currentNodeId); if (!contextNode.equals(currentNode)) { treeGrid.deselectRecord(contextNode); treeGrid.selectRecord(currentNode); } } // only show the context menu for cluster nodes and our top root node if (contextNode.isCompatibleGroupTopNode() || contextNode.isAutoClusterNode()) { contextMenu.showContextMenu(ResourceGroupTreeView.this, treeGrid, contextNode); } } }); treeGrid.addSelectionChangedHandler(new SelectionChangedHandler() { public void onSelectionChanged(SelectionEvent selectionEvent) { if (!selectionEvent.isRightButtonDown() && selectionEvent.getState()) { ResourceGroupEnhancedTreeNode newNode = (ResourceGroupEnhancedTreeNode) selectionEvent.getRecord(); TreeNode currentNode = (null != currentNodeId) ? treeGrid.getTree().findById(currentNodeId) : null; // if re-selecting the current node just return, otherwise deselect the currently selected node if (null != currentNode) { if (newNode.equals(currentNode)) { return; } else { treeGrid.deselectRecord(currentNode); } } Log.debug("Node selected in tree: " + newNode); if (newNode.isCompatibleGroupTopNode()) { currentNodeId = newNode.getID(); Log.debug("Selecting compatible group [" + currentNodeId + "]..."); String viewPath = LinkManager.getResourceGroupLink(Integer.parseInt(currentNodeId)); String currentViewPath = History.getToken(); if (!currentViewPath.startsWith(viewPath.substring(1))) { CoreGUI.goToView(viewPath, true); } else { // this should not be necessary but for otherwise the top node does not always show selected treeGrid.markForRedraw(); } } else if (newNode.isAutoClusterNode()) { ClusterKey key = newNode.getClusterKey(); Log.debug("Selecting autocluster group [" + key + "]..."); currentNodeId = newNode.getID(); // the user selected a cluster node - let's switch to that cluster group view selectClusterGroup(key); } else if (newNode.isMixedGroupTopNode()) { currentNodeId = newNode.getID(); Log.debug("Selecting mixed group [" + currentNodeId + "]..."); } else { // a subcategory node, deselect and reselect the current node treeGrid.deselectRecord(newNode); if (null != currentNode) { treeGrid.selectRecord(currentNode); } } } return; } }); addMember(this.treeGrid); } public void setSelectedGroup(final int groupId, boolean isAutoCluster) { ResourceGroupCriteria criteria = new ResourceGroupCriteria(); criteria.addFilterId(groupId); criteria.addFilterVisible(!isAutoCluster); criteria.fetchResourceType(true); GWTServiceLookup.getResourceGroupService().findResourceGroupsByCriteria(criteria, new AsyncCallback<PageList<ResourceGroup>>() { public void onFailure(Throwable caught) { CoreGUI.getErrorHandler().handleError( MSG.view_tree_common_loadFailed_group(String.valueOf(groupId)), caught); } public void onSuccess(PageList<ResourceGroup> result) { if (result.isEmpty()) { // This means either that a group with the specified id does not exist or that the user does is // not authorized to view the group. In either case, just return, and let // ResourceGroupDetailView.loadSelectedItem() handle emitting an error message. return; } ResourceGroup group = result.get(0); ResourceGroupTreeView.this.currentGroup = group; GroupCategory groupCategory = group.getGroupCategory(); switch (groupCategory) { case MIXED: // For mixed groups, there will only ever be one item in the tree, even if the group is recursive. // This is because mixed groups don't normally have clustered/identical resources across members // so there is no attempt here to build auto cluster nodes. ResourceGroupTreeView.this.rootResourceGroup = group; ResourceGroupTreeView.this.rootGroupId = rootResourceGroup.getId(); ResourceGroupEnhancedTreeNode fakeRoot = new ResourceGroupEnhancedTreeNode("fakeRootNode"); String groupName = group.getName(); String escapedGroupName = StringUtility.escapeHtml(groupName); ResourceGroupEnhancedTreeNode rootNode = new ResourceGroupEnhancedTreeNode(escapedGroupName); String icon = ImageManager.getGroupIcon(GroupCategory.MIXED); rootNode.setIcon(icon); rootNode.setID(String.valueOf(rootResourceGroup.getId())); fakeRoot.setID(FAKE_ROOT_ID); rootNode.setParentID(fakeRoot.getID()); fakeRoot.setChildren(new ResourceGroupEnhancedTreeNode[] { rootNode }); Tree tree = new Tree(); tree.setRoot(fakeRoot); treeGrid.setData(tree); treeGrid.selectRecord(rootNode); break; case COMPATIBLE: if (group.getClusterResourceGroup() == null) { // This is a straight up compatible group. ResourceGroupTreeView.this.rootResourceGroup = group; } else { // This is a cluster group beneath a real recursive compatible group. ResourceGroupTreeView.this.rootResourceGroup = group.getClusterResourceGroup(); } loadGroup(ResourceGroupTreeView.this.rootResourceGroup.getId()); break; } } }); } private void loadGroup(int groupId) { if (groupId == this.rootGroupId) { // Still looking at the same compat-recursive tree ResourceGroupEnhancedTreeNode selectedNode; if (this.currentGroup.getClusterKey() != null) { // a child cluster node leaf was selected selectedNode = (ResourceGroupEnhancedTreeNode) treeGrid.getTree().find( ResourceGroupEnhancedTreeNode.CLUSTER_KEY, this.currentGroup.getClusterKey()); } else { // the top root node, representing the group itself, was selected selectedNode = (ResourceGroupEnhancedTreeNode) treeGrid.getTree().findById( String.valueOf(this.currentGroup.getId())); } if (selectedNode != null) { TreeNode[] parents = treeGrid.getTree().getParents(selectedNode); treeGrid.getTree().openFolders(parents); treeGrid.getTree().openFolder(selectedNode); treeGrid.selectRecord(selectedNode); } } else { this.rootGroupId = groupId; GWTServiceLookup.getClusterService().getClusterTree(groupId, new AsyncCallback<ClusterFlyweight>() { public void onFailure(Throwable caught) { CoreGUI.getErrorHandler().handleError(MSG.view_tree_common_loadFailed_groupTree(), caught); } public void onSuccess(ClusterFlyweight result) { loadTreeTypes(result); } }); } } private void loadTreeTypes(final ClusterFlyweight root) { Set<Integer> typeIds = new HashSet<Integer>(); typeIds.add(this.rootResourceGroup.getResourceType().getId()); getTreeTypes(root, typeIds); ResourceTypeRepository.Cache.getInstance().getResourceTypes(typeIds.toArray(new Integer[typeIds.size()]), null, new ResourceTypeRepository.TypesLoadedCallback() { public void onTypesLoaded(Map<Integer, ResourceType> types) { ResourceGroupTreeView.this.typeMap = types; ResourceGroupEnhancedTreeNode rootNode = loadTree(root); ResourceGroupEnhancedTreeNode selectedNode = null; if (currentGroup != null) { if (currentGroup.getClusterKey() != null) { // a child cluster node leaf was selected selectedNode = (ResourceGroupEnhancedTreeNode) treeGrid.getTree().find( ResourceGroupEnhancedTreeNode.CLUSTER_KEY, currentGroup.getClusterKey()); } else { // the top root node, representing the group itself, was selected selectedNode = (ResourceGroupEnhancedTreeNode) treeGrid.getTree().findById( String.valueOf(currentGroup.getId())); } } if (selectedNode != null) { TreeNode[] parents = treeGrid.getTree().getParents(selectedNode); treeGrid.getTree().openFolders(parents); treeGrid.getTree().openFolder(selectedNode); treeGrid.selectRecord(selectedNode); } else { treeGrid.getTree().openFolder(rootNode); treeGrid.selectRecord(rootNode); } } }); } private void selectClusterGroup(final ClusterKey key) { GWTServiceLookup.getClusterService().createAutoClusterBackingGroup(key, true, new AsyncCallback<ResourceGroup>() { public void onFailure(Throwable caught) { if (caught.getMessage().contains("IllegalStateException")) { CoreGUI.goToView(LinkManager.getResourceGroupLink(key.getClusterGroupId())); } else { CoreGUI.getErrorHandler().handleError(MSG.view_tree_common_createFailed_autoCluster(), caught); } } public void onSuccess(ResourceGroup result) { renderAutoCluster(result); } }); } private void renderAutoCluster(ResourceGroup backingGroup) { String viewPath = ResourceGroupDetailView.AUTO_CLUSTER_VIEW + "/" + backingGroup.getId(); String currentViewPath = History.getToken(); if (!currentViewPath.startsWith(viewPath)) { CoreGUI.goToView(viewPath); } } private ResourceGroupEnhancedTreeNode loadTree(ClusterFlyweight root) { ClusterKey rootKey = new ClusterKey(root.getGroupId()); ResourceGroupEnhancedTreeNode fakeRoot = new ResourceGroupEnhancedTreeNode("fakeRootNode"); fakeRoot.setID(FAKE_ROOT_ID); String groupName = rootResourceGroup.getName(); String escapedGroupName = StringUtility.escapeHtml(groupName); ResourceGroupEnhancedTreeNode rootNode = new ResourceGroupEnhancedTreeNode(escapedGroupName); rootNode.setID(rootKey.getKey()); rootNode.setParentID(fakeRoot.getID()); ResourceType rootResourceType = typeMap.get(rootResourceGroup.getResourceType().getId()); rootNode.setResourceType(rootResourceType); String icon = ImageManager.getClusteredResourceIcon(rootResourceType.getCategory()); rootNode.setIcon(icon); fakeRoot.setChildren(new ResourceGroupEnhancedTreeNode[] { rootNode }); loadTree(rootNode, root, rootKey); Tree tree = new Tree(); tree.setRoot(fakeRoot); org.rhq.coregui.client.util.TreeUtility.printTree(tree); treeGrid.setData(tree); return rootNode; } public void loadTree(ResourceGroupEnhancedTreeNode parentNode, ClusterFlyweight parentClusterGroup, ClusterKey parentKey) { if (!parentClusterGroup.getChildren().isEmpty()) { // First pass - group the children by type. Map<ResourceType, List<ClusterFlyweight>> childrenByType = new HashMap<ResourceType, List<ClusterFlyweight>>(); for (ClusterFlyweight child : parentClusterGroup.getChildren()) { ClusterKeyFlyweight keyFlyweight = child.getClusterKey(); ResourceType type = this.typeMap.get(keyFlyweight.getResourceTypeId()); List<ClusterFlyweight> children = childrenByType.get(type); if (children == null) { children = new ArrayList<ClusterFlyweight>(); childrenByType.put(type, children); } children.add(child); } // Second pass - process each of the sets of like-typed children created in the first pass. List<ResourceGroupEnhancedTreeNode> childNodes = new ArrayList<ResourceGroupEnhancedTreeNode>(); Map<String, SubcategoryNode> subcategoryTreeRoots = new HashMap<String, SubcategoryNode>(); for (ResourceType childType : childrenByType.keySet()) { List<ClusterFlyweight> children = childrenByType.get(childType); List<ResourceGroupEnhancedTreeNode> nodesByType = new ArrayList<ResourceGroupEnhancedTreeNode>(); for (ClusterFlyweight child : children) { ResourceGroupEnhancedTreeNode node = createClusterGroupNode(parentKey, childType, child); nodesByType.add(node); if (!child.getChildren().isEmpty()) { ClusterKey key = node.getClusterKey(); loadTree(node, child, key); // recurse } } // Insert an autoTypeGroup node if the type is not a singleton. if (!childType.isSingleton()) { // This will override the parent IDs of all nodesByType nodes with the auto group node ID that is being created ResourceGroupEnhancedTreeNode autoTypeGroupNode = createAutoTypeGroupNode(parentKey, childType, nodesByType); nodesByType.clear(); nodesByType.add(autoTypeGroupNode); } // Insert subcategory node(s) if the type has a subcategory. if (childType.getSubCategory() != null) { SubcategoryNode parentSubcategoryNode = null; for (String currentSubcategoryName : childType.getSubCategory().split("\\|")) { SubcategoryNode currentSubcategoryNode = null; if (parentSubcategoryNode == null) { currentSubcategoryNode = subcategoryTreeRoots.get(currentSubcategoryName); } else { currentSubcategoryNode = parentSubcategoryNode.subcategoryChildren .get(currentSubcategoryName); } if (currentSubcategoryNode == null) { // This node represents a subcategory. It is not associated with any specific resource type // or cluster key - it is merely a way plugin developers organize different resource types into groups. ResourceGroupEnhancedTreeNode subcategoryResourceTreeNode = new ResourceGroupEnhancedTreeNode( currentSubcategoryName); subcategoryResourceTreeNode.setTitle(currentSubcategoryName); subcategoryResourceTreeNode.setIsFolder(true); subcategoryResourceTreeNode.setID("cat" + currentSubcategoryName); currentSubcategoryNode = new SubcategoryNode(currentSubcategoryName, subcategoryResourceTreeNode); if (parentSubcategoryNode == null) { subcategoryResourceTreeNode.setParentID(parentKey.getKey()); subcategoryTreeRoots.put(currentSubcategoryName, currentSubcategoryNode); childNodes.add(subcategoryResourceTreeNode); } else { subcategoryResourceTreeNode.setParentID(parentSubcategoryNode.resource.getID()); parentSubcategoryNode.subcategoryChildren.put(currentSubcategoryName, currentSubcategoryNode); parentSubcategoryNode.allChildren.add(subcategoryResourceTreeNode); } } parentSubcategoryNode = currentSubcategoryNode; } parentSubcategoryNode.allChildren.addAll(nodesByType); } else { childNodes.addAll(nodesByType); } } Stack<SubcategoryNode> unvisitedNodes = new Stack<SubcategoryNode>(); unvisitedNodes.addAll(subcategoryTreeRoots.values()); while (!unvisitedNodes.empty()) { SubcategoryNode node = unvisitedNodes.pop(); node.resource.setChildren(createSortedArray(node.allChildren)); unvisitedNodes.addAll(node.subcategoryChildren.values()); } parentNode.setChildren(createSortedArray(childNodes)); } } public class SubcategoryNode { public String name; public ResourceGroupEnhancedTreeNode resource; public HashMap<String, SubcategoryNode> subcategoryChildren; public ArrayList<ResourceGroupEnhancedTreeNode> allChildren; public SubcategoryNode(String name, ResourceGroupEnhancedTreeNode resource) { this.name = name; this.resource = resource; this.subcategoryChildren = new HashMap<String, SubcategoryNode>(); this.allChildren = new ArrayList<ResourceGroupEnhancedTreeNode>(); } } private ResourceGroupEnhancedTreeNode[] createSortedArray(List<ResourceGroupEnhancedTreeNode> list) { Collections.sort(list, this.treeNodeComparator); return list.toArray(new ResourceGroupEnhancedTreeNode[list.size()]); } private ResourceGroupEnhancedTreeNode createClusterGroupNode(ClusterKey parentKey, ResourceType type, ClusterFlyweight child) { // This node represents one type of resource that has 1 or more individual resources as members in the group. // It will be associated with both a resource type and a cluster key. // If an autoCluster contains disparate resource names the server returns only "..." because it doesn't // know what to name the node. This typically happens if the cluster group (i.e. root group) members are // themselves disparate. In general this is not the case, because recursive compat groups are typically // used specifically for groups of logically equivalent resources, like cloned AS instances. String childName = child.getName(); if ("...".equals(childName)) { childName = MSG.group_tree_groupOfResourceType(ResourceTypeUtility.displayName(type)); } ResourceGroupEnhancedTreeNode node = new ResourceGroupEnhancedTreeNode(childName); ClusterKeyFlyweight keyFlyweight = child.getClusterKey(); ClusterKey key = new ClusterKey(parentKey, keyFlyweight.getResourceTypeId(), keyFlyweight.getResourceKey()); String id = key.getKey(); String parentId = parentKey.getKey(); node.setID(id); node.setParentID(parentId); node.setClusterKey(key); node.setResourceType(type); node.setIsFolder(!child.getChildren().isEmpty()); int memberCount = child.getMembers(); int clusterSize = child.getClusterSize(); if (memberCount < clusterSize) { // it appears one or more individual group members doesn't have a resource with the given cluster key // label the tree node so the user knows this cluster node is not representative of the entire group membership double percentage = (double) memberCount / (double) clusterSize; String percentageStr = NumberFormat.getFormat("0%").format(percentage); String title = childName + " <span style=\"color: red; font-style: italic\">(" + percentageStr + ")</span>"; node.setTitle(title); // "1 out of 2 group members have "foo" child resources" node.setTooltip(MSG.group_tree_partialClusterTooltip(String.valueOf(memberCount), String.valueOf(clusterSize), childName)); } return node; } private ResourceGroupEnhancedTreeNode createAutoTypeGroupNode(ClusterKey parentKey, ResourceType type, List<ResourceGroupEnhancedTreeNode> memberNodes) { // This node represents a group of resources all of the same type but have different resource keys - // in other words, individual members of the group have multiple resources of this type (for example, // an auto type group node of type WAR means our group members each have multiple WARs deployed to them, // so this node represents the parent to all the different WARs cluster nodes). // This node will be associated with only a resource type (not a cluster key) String name = StringUtility.pluralize(ResourceTypeUtility.displayName(type)); ResourceGroupEnhancedTreeNode autoTypeGroupNode = new ResourceGroupEnhancedTreeNode(name); String parentId = parentKey.getKey(); String autoTypeGroupNodeId = "rt" + String.valueOf(type.getId()); autoTypeGroupNode.setID(autoTypeGroupNodeId); autoTypeGroupNode.setParentID(parentId); autoTypeGroupNode.setResourceType(type); // notice this node has a resource type, but not a cluster key autoTypeGroupNode.setIsFolder(true); for (ResourceGroupEnhancedTreeNode memberNode : memberNodes) { memberNode.setParentID(autoTypeGroupNodeId); } autoTypeGroupNode.setChildren(createSortedArray(memberNodes)); return autoTypeGroupNode; } public void renderView(ViewPath viewPath) { currentViewId = viewPath.getCurrent(); if (this.currentViewId != null) { String currentViewIdPath = currentViewId.getPath(); if ("AutoCluster".equals(currentViewIdPath)) { // Move the currentViewId to the ID portion to play better with other code currentViewId = viewPath.getNext(); String clusterGroupIdString = currentViewId.getPath(); Integer clusterGroupId = Integer.parseInt(clusterGroupIdString); setSelectedGroup(clusterGroupId, true); } else { String groupIdString = currentViewId.getPath(); int groupId = Integer.parseInt(groupIdString); setSelectedGroup(groupId, false); } } } private void getTreeTypes(ClusterFlyweight clusterFlyweight, Set<Integer> typeIds) { if (clusterFlyweight.getClusterKey() != null) { typeIds.add(clusterFlyweight.getClusterKey().getResourceTypeId()); } for (ClusterFlyweight child : clusterFlyweight.getChildren()) { getTreeTypes(child, typeIds); } } class ResourceGroupEnhancedTreeNode extends EnhancedTreeNode { private static final String TOOLTIP_KEY = "tooltip"; private static final String CLUSTER_KEY = "key"; private static final String RESOURCE_TYPE = "resourceType"; public ResourceGroupEnhancedTreeNode(String name) { super(name); } public String getTooltip() { return getAttribute(TOOLTIP_KEY); } public void setTooltip(String tooltip) { setAttribute(TOOLTIP_KEY, tooltip); } public ClusterKey getClusterKey() { return (ClusterKey) getAttributeAsObject(CLUSTER_KEY); } public void setClusterKey(ClusterKey key) { setAttribute(CLUSTER_KEY, key); } @Override public void setID(String id) { super.setID(EnhancedUtility.getSafeId(id)); } @Override public void setParentID(String parentID) { super.setParentID(EnhancedUtility.getSafeId(parentID)); } public ResourceType getResourceType() { return (ResourceType) getAttributeAsObject(RESOURCE_TYPE); } public void setResourceType(ResourceType rt) { setAttribute(RESOURCE_TYPE, rt); } public boolean isTopNode() { return getParentID().equals(FAKE_ROOT_ID); } public boolean isMixedGroupTopNode() { return isTopNode() && (null == getResourceType()); } public boolean isCompatibleGroupTopNode() { return isTopNode() && (null != getResourceType()) && (null == getClusterKey()); } public boolean isAutoGroupNode() { return !isTopNode() && (null != getResourceType()) && (null == getClusterKey()); } public boolean isAutoClusterNode() { return (null != getResourceType()) && (null != getClusterKey()) && !isTopNode(); } public boolean isSubCategoryNode() { return (null == getResourceType()) && !isMixedGroupTopNode(); } } }