/* * RHQ Management Platform * Copyright (C) 2005-2008 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 as published by * the Free Software Foundation version 2 of the License. * * 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.gui.navigation.group; import java.io.IOException; import java.util.HashSet; import java.util.Set; import javax.faces.application.FacesMessage; import javax.faces.context.FacesContext; import javax.servlet.http.HttpServletResponse; import org.richfaces.component.UITree; import org.richfaces.component.html.HtmlTree; import org.richfaces.component.state.TreeState; import org.richfaces.component.state.TreeStateAdvisor; import org.richfaces.model.TreeRowKey; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.resource.flyweight.AutoGroupCompositeFlyweight; import org.rhq.core.domain.resource.group.ClusterKey; import org.rhq.core.domain.resource.group.ResourceGroup; import org.rhq.core.gui.util.FacesContextUtility; import org.rhq.enterprise.gui.util.EnterpriseFacesContextUtility; import org.rhq.enterprise.server.resource.cluster.ClusterManagerLocal; import org.rhq.enterprise.server.resource.group.ResourceGroupManagerLocal; import org.rhq.enterprise.server.util.LookupUtil; /** * @author Greg Hinkle */ public class ResourceGroupTreeStateAdvisor implements TreeStateAdvisor { private static class CurrentSelection { public ResourceGroup resourceGroup; public ClusterKey clusterKey; } private CurrentSelection currentSelection; private Set<ResourceGroupTreeNode> openNodes = new HashSet<ResourceGroupTreeNode>(); private CurrentSelection getCurrentSelection() { int selectedGroupId = getSelectedGroupId(); if (currentSelection == null || currentSelection.resourceGroup.getId() != selectedGroupId) { ResourceGroupManagerLocal groupManager = LookupUtil.getResourceGroupManager(); currentSelection = new CurrentSelection(); currentSelection.resourceGroup = groupManager.getResourceGroupById(EnterpriseFacesContextUtility.getSubject(), selectedGroupId, null); if (!currentSelection.resourceGroup.isVisible()) { currentSelection.clusterKey = ClusterKey.valueOf(currentSelection.resourceGroup.getClusterKey()); } } return currentSelection; } private static int getSelectedGroupId() { String groupId = FacesContextUtility.getOptionalRequestParameter("groupId"); return Integer.parseInt(groupId); } public void changeExpandListener(org.richfaces.event.NodeExpandedEvent e) { HtmlTree tree = (HtmlTree) e.getComponent(); TreeState state = (TreeState) tree.getComponentState(); //check if we're collapsing a parent of currently selected node. //if we do, change the focus to the parent if (state.getSelectedNode() != null) { boolean closingParent = false; TreeRowKey<?> key = (TreeRowKey<?>) tree.getRowKey(); ResourceGroupTreeNode node = (ResourceGroupTreeNode) tree.getRowData(key); ResourceGroupTreeNode selectedNode = (ResourceGroupTreeNode) tree.getRowData(state.getSelectedNode()); ResourceGroupTreeNode traverseCheckNode = selectedNode.getParent(); while (traverseCheckNode != null) { if (node.equals(traverseCheckNode)) { closingParent = true; break; } traverseCheckNode = traverseCheckNode.getParent(); } if (closingParent) { if (redirectTo(node)) { state.setSelected(key); openNodes.remove(node); //this is nasty hack. We need some kind of flag that would persist only for the remainder //of this request to advertise that no more open/closed states should be made in this request. //The tree is request scoped, so setting this flag will not persist to the next request. //In the case of the tree, setting this to true in this listener has no side-effects. tree.setBypassUpdates(true); } else if (!redirectTo(selectedNode)) { FacesContext.getCurrentInstance().addMessage("leftNavTreeForm:leftNavTree", new FacesMessage(FacesMessage.SEVERITY_WARN, "Failed to re-expand node that shouldn't be collapsed.", null)); } } else { if (openNodes.contains(node)) { openNodes.remove(node); } else { openNodes.add(node); } } } } public Boolean adviseNodeOpened(UITree tree) { TreeRowKey<?> key = (TreeRowKey<?>) tree.getRowKey(); if (key != null) { ResourceGroupTreeNode node = (ResourceGroupTreeNode) tree.getRowData(key); if (node.getParent() == null) { return true; } CurrentSelection currentSelection = getCurrentSelection(); //only update the state of open nodes in the preopen check //if we're not finishing the request in which a parent //of currently selected node was requested to close. //If we did update the open node states in the "remainder" //of such request the redirect that results from it would //get wrong information and the parent wouldn't appear closed //(because it'd had been re-opened in the below preopen call). //@see changeExpandListener for more nasty details. boolean setOpenStates = !tree.isBypassUpdates(); if (preopen(node, currentSelection, setOpenStates)) { return true; } return openNodes.contains(node); } return null; } private boolean preopen(ResourceGroupTreeNode resourceTreeNode, CurrentSelection currentSelection, boolean setOpenState) { ResourceGroup currentGroup = currentSelection.resourceGroup; ClusterKey selectedClusterKey = currentSelection.clusterKey; boolean ret = false; for (ResourceGroupTreeNode child : resourceTreeNode.getChildren()) { if (child.getData() instanceof ClusterKey) { if (((ClusterKey) child.getData()).equals(selectedClusterKey)) { ret = true; break; } } else if (child.getData() instanceof ResourceGroup) { if (currentGroup.getId() == ((ResourceGroup) child.getData()).getId()) { ret = true; break; } } if (preopen(child, currentSelection, setOpenState)) { ret = true; break; } } if (setOpenState && ret) { openNodes.add(resourceTreeNode); } return ret; } public void nodeSelectListener(org.richfaces.event.NodeSelectedEvent e) { HtmlTree tree = (HtmlTree) e.getComponent(); TreeState state = (TreeState) ((HtmlTree) tree).getComponentState(); TreeRowKey<?> key = (TreeRowKey<?>) tree.getRowKey(); ResourceGroupTreeNode node = (ResourceGroupTreeNode) tree.getRowData(key); if (node != null && !redirectTo(node)) { state.setSelected(e.getOldSelection()); } } public Boolean adviseNodeSelected(UITree tree) { CurrentSelection currentSelection = getCurrentSelection(); ResourceGroupTreeNode node = (ResourceGroupTreeNode) tree.getRowData(tree.getRowKey()); if (node.getData() instanceof ResourceGroup) { return (currentSelection.resourceGroup.getId() == ((ResourceGroup) node.getData()).getId()); } else if (node.getData() instanceof ClusterKey) { ClusterKey key = (ClusterKey) node.getData(); if (currentSelection.clusterKey != null && currentSelection.clusterKey.equals(key)) { return true; } } return false; } /** * @return false if there was an error redirecting to the target location */ private boolean redirectTo(ResourceGroupTreeNode node) { HttpServletResponse response = (HttpServletResponse) FacesContextUtility.getFacesContext().getExternalContext() .getResponse(); Subject subject = EnterpriseFacesContextUtility.getSubject(); String path = ""; if (node.getData() instanceof ClusterKey) { ClusterManagerLocal clusterManager = LookupUtil.getClusterManager(); ResourceGroup group = clusterManager.createAutoClusterBackingGroup(subject, (ClusterKey) node.getData(), true); path = "/rhq/group/inventory/view.xhtml"; path += ("?groupId=" + group.getId() + "&parentGroupId=" + ((ClusterKey) node.getData()) .getClusterGroupId()); } else if (node.getData() instanceof AutoGroupCompositeFlyweight) { FacesContext.getCurrentInstance().addMessage("leftNavGroupTreeForm:leftNavGroupTree", new FacesMessage(FacesMessage.SEVERITY_WARN, "No cluster autogroup views available", null)); return false; } else if (node.getData() instanceof ResourceGroup) { path = "/rhq/group/inventory/view.xhtml"; path += ("?groupId=" + ((ResourceGroup) node.getData()).getId()); } try { response.sendRedirect(path); return true; // all is well in the land } catch (IOException ioe) { FacesContext.getCurrentInstance().addMessage( "leftNavGroupTreeForm:leftNavGroupTree", new FacesMessage(FacesMessage.SEVERITY_WARN, "Unable to browse to selected group view: " + ioe.getMessage(), null)); } return false; // IO error from redirect } }