/* * 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.resource; 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.resource.composite.ResourceFacets; import org.rhq.core.domain.resource.flyweight.AutoGroupCompositeFlyweight; import org.rhq.core.domain.resource.flyweight.ResourceFlyweight; import org.rhq.core.gui.util.FacesContextUtility; import org.rhq.enterprise.gui.common.tag.FunctionTagLibrary; import org.rhq.enterprise.server.resource.ResourceTypeManagerLocal; import org.rhq.enterprise.server.util.LookupUtil; /** * Manages the tree selection and node openess for the left nav resource tree * * @author Greg Hinkle * @author Lukas Krejci */ public class ResourceTreeStateAdvisor implements TreeStateAdvisor { private ResourceTypeManagerLocal resourceTypeManager = LookupUtil.getResourceTypeManager(); Set<ResourceTreeNode> openNodes = new HashSet<ResourceTreeNode>(); 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(); ResourceTreeNode node = (ResourceTreeNode) tree.getRowData(key); ResourceTreeNode selectedNode = (ResourceTreeNode) tree.getRowData(state.getSelectedNode()); ResourceTreeNode 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 void nodeSelectListener(org.richfaces.event.NodeSelectedEvent e) { HtmlTree tree = (HtmlTree) e.getComponent(); TreeState state = (TreeState) ((HtmlTree) tree).getComponentState(); ResourceTreeNode node = (ResourceTreeNode) tree.getRowData(tree.getRowKey()); if (node != null && !redirectTo(node)) { state.setSelected(e.getOldSelection()); } } public Boolean adviseNodeOpened(UITree tree) { TreeRowKey<?> key = (TreeRowKey<?>) tree.getRowKey(); //don't bother continuing if there's nothing to check against... if (key == null) { return null; } ResourceTreeNode node = (ResourceTreeNode) tree.getRowData(key); //always expand root if (node.getParent() == null) { return true; } //make sure that the node refered to in the request parameters is visible int selectedId = 0; String typeId = FacesContextUtility.getOptionalRequestParameter("type"); int selecteAGTypeId = ((typeId == null || typeId.length() == 0) ? 0 : Integer.parseInt(typeId)); if (typeId != null) { String id = FacesContextUtility.getOptionalRequestParameter("parent"); if (id != null && id.length() != 0) { selectedId = Integer.parseInt(id); } } else { String id = FacesContextUtility.getOptionalRequestParameter("id"); if (id != null && id.length() != 0) { selectedId = Integer.parseInt(id); } } //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, selectedId, selecteAGTypeId, setOpenStates)) { return true; } //ok, in this case return whatever the last open state was return openNodes.contains(node); } private boolean preopen(ResourceTreeNode resourceTreeNode, int selectedResourceId, int selectedAGTypeId, boolean setOpenStates) { boolean ret = false; for (ResourceTreeNode child : resourceTreeNode.getChildren()) { if (child.getData() instanceof ResourceFlyweight && selectedAGTypeId == 0) { if (((ResourceFlyweight) child.getData()).getId() == selectedResourceId) { ret = true; break; } } else if (child.getData() instanceof AutoGroupCompositeFlyweight) { AutoGroupCompositeFlyweight ag = (AutoGroupCompositeFlyweight) child.getData(); if (ag.getParentResource().getId() == selectedResourceId && ag.getResourceType() != null && ag.getResourceType().getId() == selectedAGTypeId) { ret = true; break; } } if (preopen(child, selectedResourceId, selectedAGTypeId, setOpenStates)) { ret = true; break; } } if (setOpenStates && ret) { openNodes.add(resourceTreeNode); } return ret; } public Boolean adviseNodeSelected(UITree tree) { TreeState state = (TreeState) ((HtmlTree) tree).getComponentState(); String id = FacesContextUtility.getOptionalRequestParameter("id"); String parent = FacesContextUtility.getOptionalRequestParameter("parent"); String type = FacesContextUtility.getOptionalRequestParameter("type"); ResourceTreeNode node = (ResourceTreeNode) tree.getRowData(tree.getRowKey()); if (node.getData() instanceof AutoGroupCompositeFlyweight) { AutoGroupCompositeFlyweight ag = (AutoGroupCompositeFlyweight) node.getData(); if (ag.getParentResource() != null && ag.getResourceType() != null && String.valueOf(ag.getParentResource().getId()).equals(parent) && String.valueOf(ag.getResourceType().getId()).equals(type)) { return true; } } else if (node.getData() instanceof ResourceFlyweight) { if (String.valueOf(((ResourceFlyweight) node.getData()).getId()).equals(id)) { return Boolean.TRUE; } } return tree.getRowKey().equals(state.getSelectedNode()); } public boolean getHasMessages() { return FacesContext.getCurrentInstance().getMessages("leftNavTreeForm:leftNavTree").hasNext(); } /** * @return false if there was an error redirecting to the target location */ private boolean redirectTo(ResourceTreeNode node) { HttpServletResponse response = (HttpServletResponse) FacesContextUtility.getFacesContext().getExternalContext() .getResponse(); String path = ""; if (node.getData() instanceof ResourceFlyweight) { ResourceFlyweight flyweight = (ResourceFlyweight) node.getData(); if (flyweight.isLocked()) { FacesContext.getCurrentInstance().addMessage( "leftNavTreeForm:leftNavTree", new FacesMessage(FacesMessage.SEVERITY_WARN, "You have not been granted view access to this resource", null)); return false; } else { path = FacesContextUtility.getRequest().getRequestURI(); //Resource resource = this.resourceManager.getResourceById(subject, ((Resource) node.getData()).getId()); ResourceFacets facets = this.resourceTypeManager.getResourceFacets(flyweight.getResourceType() .getId()); String fallbackPath = FunctionTagLibrary.getDefaultResourceTabURL(); // Switching from a auto group view... default to monitor page if (!path.startsWith("/rhq/resource")) { path = fallbackPath; } else { if ((path.startsWith("/rhq/resource/configuration/") && !facets.isConfiguration()) || (path.startsWith("/rhq/resource/content/") && !facets.isContent()) || (path.startsWith("/rhq/resource/operation") && !facets.isOperation()) || (path.startsWith("/rhq/resource/events") && !facets.isEvent())) { // This resource doesn't support those facets path = fallbackPath; } else if ((path.startsWith("/rhq/resource/configuration/view-map.xhtml") || path.startsWith("/rhq/resource/configuration/edit-map.xhtml") || path.startsWith("/rhq/resource/configuration/add-map.xhtml") || path .startsWith("/rhq/resource/configuration/edit.xhtml") && facets.isConfiguration())) { path = "/rhq/resource/configuration/view.xhtml"; } else if (!path.startsWith("/rhq/resource/content/view.xhtml") && path.startsWith("/rhq/resource/content/") && facets.isContent()) { path = "/rhq/resource/content/view.xhtml"; } else if (path.startsWith("/rhq/resource/inventory/") && !(path.startsWith("/rhq/resource/inventory/view.xhtml") || (facets.isPluginConfiguration() && path .startsWith("/rhq/resource/inventory/view-connection.xhtml")) || path .startsWith("/rhq/resource/inventory/view-agent.xhtml"))) { path = "/rhq/resource/inventory/view.xhtml"; } else if (path.startsWith("/rhq/resource/operation/resourceOperationHistoryDetails.xhtml")) { path = "/rhq/resource/operation/resourceOperationHistory.xhtml"; } else if (path.startsWith("/rhq/resource/operation/resourceOperationScheduleDetails.xhtml")) { path = "/rhq/resource/operation/resourceOperationSchedules.xhtml"; } else if (path.startsWith("/rhq/resource/monitor/response.xhtml") && !facets.isCallTime()) { path = fallbackPath; } } path += ("?id=" + flyweight.getId()); } } else if (node.getData() instanceof AutoGroupCompositeFlyweight) { AutoGroupCompositeFlyweight ag = (AutoGroupCompositeFlyweight) node.getData(); if (ag.getResourceType() == null) { //XXX this is a temporary measure. The subcategories will get the content page in the end. FacesContext.getCurrentInstance().addMessage("leftNavTreeForm:leftNavTree", new FacesMessage(FacesMessage.SEVERITY_WARN, "No subcategory page exists.", null)); return false; } else if (ag.getMemberCount() != node.getChildren().size()) { // you don't have access to every autogroup resource FacesContext.getCurrentInstance().addMessage( "leftNavTreeForm:leftNavTree", new FacesMessage(FacesMessage.SEVERITY_WARN, "You must have view access to all resources in an autogroup to view it", null)); return false; } else { path = "/rhq/autogroup/monitor/graphs.xhtml?parent=" + ag.getParentResource().getId() + "&type=" + ag.getResourceType().getId(); } } try { response.sendRedirect(path); return true; // all is well in the land } catch (IOException ioe) { FacesContext.getCurrentInstance().addMessage( "leftNavTreeForm:leftNavTree", new FacesMessage(FacesMessage.SEVERITY_WARN, "Unable to browse to selected resource view: " + ioe.getMessage(), null)); } return false; // IO errors from redirect } }