/*
* RHQ Management Platform
* Copyright (C) 2005-2011 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.coregui.client.inventory.resource;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.widgets.grid.CellFormatter;
import com.smartgwt.client.widgets.grid.HoverCustomizer;
import com.smartgwt.client.widgets.grid.ListGrid;
import com.smartgwt.client.widgets.grid.ListGridField;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.domain.util.ResourceTypeUtility;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.LinkManager;
import org.rhq.coregui.client.util.StringUtility;
/**
* A utility class for working with Resource ancestry values.
*
* @author Jay Shaughnessy
*/
public abstract class AncestryUtil {
// ListGrid Record attribute names expected to be set on records processed by the utility
public static final String RESOURCE_ANCESTRY = "resourceAncestry";
public static final String RESOURCE_ANCESTRY_VALUE = "resourceAncestryDecoded";
public static final String RESOURCE_ANCESTRY_HOVER = "resourceAncestryHover";
public static final String RESOURCE_ANCESTRY_TYPES = "resourceAncestryTypes";
public static final String RESOURCE_HOVER = "resourceHover";
public static final String RESOURCE_ID = "resourceId";
public static final String RESOURCE_NAME = "resourceName";
public static final String RESOURCE_TYPE_ID = "resourceTypeId";
private static final String TITLE_ANCESTRY = CoreGUI.getMessages().util_ancestry_parentAncestry() + " ";
private static final String TITLE_PLATFORM = CoreGUI.getMessages().common_title_platform() + ": ";
/**
* Convienence method that creates a standard ancestry ListGridField.
* @return ancestry field
*/
public static ListGridField setupAncestryListGridField() {
ListGridField ancestryField;
ancestryField = new ListGridField(AncestryUtil.RESOURCE_ANCESTRY, CoreGUI.getMessages().common_title_ancestry());
ancestryField.setAlign(Alignment.LEFT);
ancestryField.setCellAlign(Alignment.LEFT);
// sorting on the encoded db value is not useful, limit to client sorting when we have all of the values
ancestryField.setCanSortClientOnly(true);
setupAncestryListGridFieldCellFormatter(ancestryField);
setupAncestryListGridFieldHover(ancestryField);
return ancestryField;
}
/**
* Convenience method that configures a standard ancestry ListGridField where the field already exists
* in the given list grid.
*
* @param listGrid the list grid containing the standard ancestry field to be configured
*
* @return ancestry field
*/
public static ListGridField setupAncestryListGridField(ListGrid listGrid) {
ListGridField ancestryField = listGrid.getField(AncestryUtil.RESOURCE_ANCESTRY);
ancestryField.setAlign(Alignment.LEFT);
ancestryField.setCellAlign(Alignment.LEFT);
setupAncestryListGridFieldCellFormatter(ancestryField);
setupAncestryListGridFieldHover(ancestryField);
return ancestryField;
}
public static void setupAncestryListGridFieldHover(ListGridField ancestryField) {
ancestryField.setShowHover(true);
ancestryField.setHoverCustomizer(new HoverCustomizer() {
public String hoverHTML(Object value, ListGridRecord listGridRecord, int rowNum, int colNum) {
return AncestryUtil.getAncestryHoverHTML(listGridRecord, 0);
}
});
}
public static void setupAncestryListGridFieldCellFormatter(ListGridField ancestryField) {
ancestryField.setCellFormatter(new CellFormatter() {
public String format(Object o, ListGridRecord listGridRecord, int rowNum, int colNum) {
return listGridRecord.getAttributeAsString(AncestryUtil.RESOURCE_ANCESTRY_VALUE);
}
});
}
/**
* Get the complete set of resource types in the ancestries provided. This is useful for
* being able to load all the types in advance of generating decoded values.
*
* @return the complete set of resource types in the ancestries provided
*/
public static HashSet<Integer> getAncestryTypeIds(Collection<String> ancestries) {
HashSet<Integer> result = new HashSet<Integer>();
for (String ancestry : ancestries) {
if (null == ancestry) {
continue;
}
String[] ancestryEntries = ancestry.split(Resource.ANCESTRY_DELIM);
for (String ancestryEntry : ancestryEntries) {
String[] entryTokens = ancestryEntry.split(Resource.ANCESTRY_ENTRY_DELIM);
int rtId = Integer.valueOf(entryTokens[0]);
result.add(rtId);
}
}
return result;
}
/**
* Decode the provided ancestry into display values.
*
* @param resourceId
* @param ancestry The encoded ancestry for the resource
* @param types if provided, must contain all of the resource types found in the ancestry.
* @return Array of length 2. result[0] is the resource ancestry, complete with hrefs. result[1] is
* the type ancestry, or null if types were not provided.
*/
public static String[] decodeAncestry(int resourceId, String ancestry, Map<Integer, ResourceType> types) {
String[] result = new String[2];
if (null == ancestry) {
return result;
}
StringBuilder sbResources = new StringBuilder();
StringBuilder sbTypes = (null != types) ? new StringBuilder("Parent Ancestry<hr/>") : null;
String[] ancestryEntries = ancestry.split(Resource.ANCESTRY_DELIM);
for (int i = 0; i < ancestryEntries.length; ++i) {
String[] entryTokens = ancestryEntries[i].split(Resource.ANCESTRY_ENTRY_DELIM);
int ancestorTypeId = Integer.valueOf(entryTokens[0]);
int ancestorResourceId = Integer.valueOf(entryTokens[1]);
String ancestorName = StringUtility.escapeHtml(entryTokens[2]);
sbResources.append((i > 0) ? " < " : "");
//sbResources.append(" < ");
String url = LinkManager.getResourceLink(ancestorResourceId);
String href = LinkManager.getHref(url, ancestorName);
sbResources.append(href);
if (null != sbTypes) {
if (i > 0) {
sbTypes.append("<br/>");
for (int j = 0; j < i; ++j) {
sbTypes.append(" ");
}
}
ResourceType rt = types.get(ancestorTypeId);
sbTypes.append(href);
addFormattedType(sbTypes, rt);
}
}
result[0] = sbResources.toString();
if (null != sbTypes) {
result[1] = sbTypes.toString();
}
return result;
}
public static String getFormattedType(ResourceType type) {
return addFormattedType(new StringBuilder(), type).toString();
}
private static StringBuilder addFormattedType(StringBuilder sb, ResourceType type) {
sb.append(" [<i>");
sb.append(type.getPlugin());
sb.append("</i>, ");
sb.append(ResourceTypeUtility.displayName(type));
sb.append("]");
return sb;
}
public static String getAncestryValue(Record record) {
return getAncestryValue(record, true);
}
public static String getAncestryValue(Record record, boolean generateLinks) {
String ancestry = record.getAttributeAsString(RESOURCE_ANCESTRY);
if (null == ancestry) {
return "";
}
Integer resourceId = getResourceId(record);
StringBuilder sbResources = new StringBuilder();
String[] ancestryEntries = ancestry.split(Resource.ANCESTRY_DELIM);
for (int i = 0; i < ancestryEntries.length; ++i) {
String[] entryTokens = ancestryEntries[i].split(Resource.ANCESTRY_ENTRY_DELIM);
int ancestorResourceId = Integer.valueOf(entryTokens[1]);
String ancestorName = StringUtility.escapeHtml(entryTokens[2]);
sbResources.append((i > 0) ? " < " : "");
if (generateLinks) {
String url = LinkManager.getResourceLink(ancestorResourceId);
String href = LinkManager.getHref(url, ancestorName);
sbResources.append(href);
} else {
sbResources.append(ancestorName);
}
}
return sbResources.toString();
}
public static String getAncestryHoverHTML(ListGridRecord record, int width) {
// ListGrid sometimes passes in null if it's in the middle of fetching new data. In this case, return null to
// tell the ListGrid to not display any tooltip.
if (record == null) {
return null;
}
// See if we cached the HTML as an attribute on the record on a previous call to this method.
String ancestryHover = record.getAttributeAsString(RESOURCE_ANCESTRY_HOVER);
if (ancestryHover != null) {
return ancestryHover;
}
String resourceName = getResourceName(record);
MapWrapper typesWrapper = (MapWrapper) record.getAttributeAsObject(RESOURCE_ANCESTRY_TYPES);
if (typesWrapper == null) {
// This means the record hasn't been fully loaded yet, so return null to tell the ListGrid to not display
// any tooltip.
return null;
}
Map<Integer, ResourceType> types = typesWrapper.getMap();
Integer resourceTypeId = record.getAttributeAsInt(RESOURCE_TYPE_ID);
String ancestry = record.getAttributeAsString(RESOURCE_ANCESTRY);
String result = getAncestryHoverHTMLString(resourceName, ancestry, resourceTypeId, types, width);
// Cache the HTML as an attribute on the record.
record.setAttribute(RESOURCE_ANCESTRY_HOVER, result);
return result;
}
public static String getAncestryHoverHTMLForResource(Resource resource, Map<Integer, ResourceType> types, int width) {
return getAncestryHoverHTMLString(resource.getName(), resource.getAncestry(), resource.getResourceType()
.getId(), types, width);
}
private static String getAncestryHoverHTMLString(String resourceName, String ancestry, int resourceTypeId,
Map<Integer, ResourceType> types, int width) {
ResourceType type = types.get(resourceTypeId);
String resourceLongName = getResourceLongName(resourceName, type);
// note: if width is negative, we do not explicitly define a width in the HTML, thus enabling auto-resizing.
Integer widthObj = null;
if (width >= 0) {
widthObj = (width == 0) ? Integer.valueOf(500) : Integer.valueOf(width);
}
// decode ancestry
StringBuilder sb = new StringBuilder("<p");
if (widthObj != null) {
sb.append(" style='width:");
sb.append(widthObj.toString());
sb.append("px'");
}
sb.append(">");
String title = (null != ancestry) ? TITLE_ANCESTRY : TITLE_PLATFORM;
sb.append(title);
sb.append(resourceLongName);
if (null != ancestry) {
sb.append("<hr/>");
String[] ancestryEntries = ancestry.split(Resource.ANCESTRY_DELIM);
for (int i = ancestryEntries.length - 1, j = 0; i >= 0; --i, ++j) {
String[] entryTokens = ancestryEntries[i].split(Resource.ANCESTRY_ENTRY_DELIM);
int ancestorTypeId = Integer.valueOf(entryTokens[0]);
String ancestorName = StringUtility.escapeHtml(entryTokens[2]);
// indent with spaces
if (j > 0) {
sb.append("<br/>");
for (int k = 0; k < j; ++k) {
sb.append(" ");
}
}
type = types.get(ancestorTypeId);
sb.append(getResourceLongName(ancestorName, type));
}
// add target resource, indent with spaces
sb.append("<br/>");
for (int k = 0; k <= ancestryEntries.length; ++k) {
sb.append(" ");
}
sb.append(resourceLongName);
}
sb.append("</p>");
return sb.toString();
}
private static String getResourceLongName(String resourceName, ResourceType type) {
StringBuilder sb = new StringBuilder("<b>");
sb.append(resourceName);
sb.append("</b>");
if (type != null) {
addFormattedType(sb, type);
}
return sb.toString();
}
/**
* Get a resource name that combines type and resource information. This is useful for when we want to
* use a single column for display of the resource "name". The name is wrapped in a navigable link.
*
* @param record the resource record
* @param width the width, in pixels, of the returned HTML <code>p</code> tag
*
* @return the long name for the resource
*/
public static String getResourceHoverHTML(Record record, int width) {
// ListGrid sometimes passes in null if it's in the middle of fetching new data. In this case, return null to
// tell the ListGrid to not display any tooltip.
if (record == null) {
return null;
}
// See if we cached the HTML as an attribute on the record on a previous call to this method.
String resourceHover = record.getAttributeAsString(RESOURCE_HOVER);
if (resourceHover != null) {
return resourceHover;
}
String resourceName = getResourceName(record);
MapWrapper typesWrapper = (MapWrapper) record.getAttributeAsObject(RESOURCE_ANCESTRY_TYPES);
if (typesWrapper == null) {
// This means the record hasn't been loaded yet, so return null to tell the ListGrid to not display any
// tooltip.
return null;
}
Map<Integer, ResourceType> types = typesWrapper.getMap();
Integer resourceTypeId = record.getAttributeAsInt(RESOURCE_TYPE_ID);
ResourceType type = types.get(resourceTypeId);
// note: if width is negative, we do not explicitly define a width in the HTML, thus enabling auto-resizing.
Integer widthObj = null;
if (width >= 0) {
widthObj = (width == 0) ? Integer.valueOf(500) : Integer.valueOf(width);
}
StringBuilder sb = new StringBuilder("<p");
if (widthObj != null) {
sb.append(" style='width:");
sb.append(widthObj);
sb.append("px'");
}
sb.append(">");
sb.append(getResourceLongName(resourceName, type));
sb.append("</p>");
String result = sb.toString();
// Cache the HTML as an attribute on the record.
record.setAttribute(RESOURCE_HOVER, result);
return result;
}
private static Integer getResourceId(Record record) {
Integer resourceId = record.getAttributeAsInt(RESOURCE_ID);
// if not set assume the standard "id" attr is a resourceId
resourceId = (null != resourceId) ? resourceId : record.getAttributeAsInt("id");
return resourceId;
}
private static String getResourceName(Record record) {
String resourceName = record.getAttribute(RESOURCE_NAME);
// if not set assume the standard "name" attr is a resourceName
resourceName = (null != resourceName) ? resourceName : record.getAttribute("name");
resourceName = StringUtility.escapeHtml(resourceName);
return resourceName;
}
// We do not want SmartGWT to see we are storing our map into an attribute because it barfs on our key/value pairs
// so instead we have to wrap it in a non-Map POJO Object so SmartGWT just handles it as a java.lang.Object.
public static class MapWrapper {
private Map<Integer, ResourceType> map;
public MapWrapper(Map<Integer, ResourceType> map) {
this.map = map;
}
public Map<Integer, ResourceType> getMap() {
return this.map;
}
}
}