/* * RHQ Management Platform * Copyright (C) 2010-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 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.alert; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.smartgwt.client.core.DataClass; import com.smartgwt.client.data.DSRequest; import com.smartgwt.client.data.DSResponse; import com.smartgwt.client.data.DataSourceField; import com.smartgwt.client.data.Record; import com.smartgwt.client.data.fields.DataSourceIntegerField; import com.smartgwt.client.rpc.RPCResponse; import com.smartgwt.client.types.Alignment; import com.smartgwt.client.types.ImageStyle; import com.smartgwt.client.types.ListGridFieldType; import com.smartgwt.client.widgets.Img; import com.smartgwt.client.widgets.grid.CellFormatter; import com.smartgwt.client.widgets.grid.HoverCustomizer; import com.smartgwt.client.widgets.grid.ListGridField; import com.smartgwt.client.widgets.grid.ListGridRecord; import org.rhq.core.domain.alert.Alert; import org.rhq.core.domain.alert.AlertCondition; import org.rhq.core.domain.alert.AlertConditionLog; import org.rhq.core.domain.alert.AlertDefinition; import org.rhq.core.domain.alert.AlertFilter; import org.rhq.core.domain.alert.AlertPriority; import org.rhq.core.domain.alert.notification.AlertNotificationLog; import org.rhq.core.domain.common.EntityContext; import org.rhq.core.domain.criteria.AlertCriteria; import org.rhq.core.domain.resource.Resource; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.domain.util.PageList; import org.rhq.coregui.client.CoreGUI; import org.rhq.coregui.client.ImageManager; import org.rhq.coregui.client.LinkManager; import org.rhq.coregui.client.admin.templates.AlertDefinitionTemplateTypeView; import org.rhq.coregui.client.components.form.DateFilterItem; import org.rhq.coregui.client.components.table.TimestampCellFormatter; import org.rhq.coregui.client.gwt.AlertGWTServiceAsync; import org.rhq.coregui.client.gwt.GWTServiceLookup; import org.rhq.coregui.client.inventory.resource.AncestryUtil; import org.rhq.coregui.client.inventory.resource.type.ResourceTypeRepository; import org.rhq.coregui.client.inventory.resource.type.ResourceTypeRepository.TypesLoadedCallback; import org.rhq.coregui.client.util.Log; import org.rhq.coregui.client.util.MeasurementConverterClient; import org.rhq.coregui.client.util.RPCDataSource; /** * @author Ian Springer * @author Joseph Marques * @author John Mazzitelli */ public class AlertDataSource extends RPCDataSource<Alert, AlertCriteria> { private static final String FIELD_PARENT = "parent"; // may be template or group alert def parent public static final String PRIORITY_ICON_HIGH = ImageManager.getAlertIcon(AlertPriority.HIGH); public static final String PRIORITY_ICON_MEDIUM = ImageManager.getAlertIcon(AlertPriority.MEDIUM); public static final String PRIORITY_ICON_LOW = ImageManager.getAlertIcon(AlertPriority.LOW); public static final String FILTER_PRIORITIES = "priorities"; public static final String FILTER_STATUS = "status"; public static final String FILTER_RESOURCE_IDS = "resourceIds"; public static final String FILTER_NAME = "nameF"; private AlertGWTServiceAsync alertService = GWTServiceLookup.getAlertService(); private EntityContext entityContext; public AlertDataSource() { this(EntityContext.forSubsystemView()); } public AlertDataSource(EntityContext context) { super(); this.entityContext = context; addDataSourceFields(); } @Override protected List<DataSourceField> addDataSourceFields() { List<DataSourceField> fields = super.addDataSourceFields(); DataSourceField idField = new DataSourceIntegerField("id", MSG.common_title_id(), 50); idField.setPrimaryKey(true); idField.setCanEdit(false); idField.setHidden(true); fields.add(idField); return fields; } public ArrayList<ListGridField> getListGridFields() { return getListGridFields(true); } /** * The view that contains the list grid which will display this datasource's data will call this * method to get the field information which is used to control the display of the data. * * @return list grid fields used to display the datasource data */ public ArrayList<ListGridField> getListGridFields(boolean showResourceAncestry) { ArrayList<ListGridField> fields = new ArrayList<ListGridField>(7); ListGridField ctimeField = new ListGridField(AlertCriteria.SORT_FIELD_CTIME, MSG.common_title_createTime()); ctimeField.setCellFormatter(new TimestampCellFormatter(TimestampCellFormatter.DATE_TIME_FORMAT_SHORT)); ctimeField.setShowHover(true); ctimeField.setHoverCustomizer(TimestampCellFormatter.getHoverCustomizer(AlertCriteria.SORT_FIELD_CTIME)); fields.add(ctimeField); ListGridField nameField = new ListGridField("name", MSG.common_title_name()); nameField.setCellFormatter(new CellFormatter() { public String format(Object o, ListGridRecord listGridRecord, int i, int i1) { Integer resourceId = listGridRecord.getAttributeAsInt(AncestryUtil.RESOURCE_ID); Integer defId = listGridRecord.getAttributeAsInt("definitionId"); String url = LinkManager.getSubsystemAlertDefinitionLink(resourceId, defId); return LinkManager.getHref(url, o.toString()); } }); fields.add(nameField); ListGridField conditionField = new ListGridField("conditionText", MSG.view_alerts_field_condition_text()); conditionField.setCanSortClientOnly(true); fields.add(conditionField); ListGridField conditionLogField = new ListGridField("conditionValue", MSG.view_alerts_field_condition_value()); conditionLogField.setCanSortClientOnly(true); fields.add(conditionLogField); ListGridField priorityField = new ListGridField("priority", MSG.view_alerts_field_priority()); priorityField.setType(ListGridFieldType.IMAGE); priorityField.setAlign(Alignment.CENTER); priorityField.setShowHover(true); priorityField.setHoverCustomizer(new HoverCustomizer() { @Override public String hoverHTML(Object value, ListGridRecord record, int rowNum, int colNum) { String prio = record.getAttribute("priority"); if (PRIORITY_ICON_HIGH.equals(prio)) { return MSG.common_alert_high(); } else if (PRIORITY_ICON_MEDIUM.equals(prio)) { return MSG.common_alert_medium(); } else if (PRIORITY_ICON_LOW.equals(prio)) { return MSG.common_alert_low(); } else { return ""; // will never get here } } }); fields.add(priorityField); ListGridField statusField = new ListGridField("acknowledgingSubject", MSG.common_title_acknowledged()); statusField.setCellFormatter(new CellFormatter() { public String format(Object o, ListGridRecord listGridRecord, int i, int i1) { String ackSubject = listGridRecord.getAttribute("acknowledgingSubject"); if (ackSubject == null) { return " "; } else { Img checkedImg = new Img(ImageManager.getAlertStatusCheckedIcon(),80,16); checkedImg.setImageType(ImageStyle.CENTER); return checkedImg.getInnerHTML(); } } }); statusField.setShowHover(true); statusField.setHoverCustomizer(new HoverCustomizer() { public String hoverHTML(Object value, ListGridRecord record, int rowNum, int colNum) { String ackSubject = record.getAttribute("acknowledgingSubject"); StringBuilder sb = new StringBuilder("<p"); if (ackSubject == null) { sb.append(" style='width:150px'>"); sb.append(MSG.view_alerts_field_ack_status_noAckHover()); } else { sb.append(" style='width:500px'>"); Date ackDateTime = record.getAttributeAsDate("acknowledgeTime"); String ackDateTimeString = TimestampCellFormatter.format(ackDateTime, TimestampCellFormatter.DATE_TIME_FORMAT_FULL); sb.append(MSG.view_alerts_field_ack_status_ackHover(ackSubject, ackDateTimeString)); } sb.append("</p>"); return sb.toString(); } }); fields.add(statusField); ListGridField recoveredField = new ListGridField("recovered", MSG.common_title_recovered()); recoveredField.setCellFormatter(new CellFormatter() { public String format(Object o, ListGridRecord listGridRecord, int i, int i1) { Long recovered = listGridRecord.getAttributeAsLong("recovered"); if(recovered != null && recovered.longValue() > 0) { Img checkedImg = new Img(ImageManager.getAlertStatusCheckedIcon(),80,16); checkedImg.setImageType(ImageStyle.CENTER); return checkedImg.getInnerHTML(); } else { return " "; } } }); recoveredField.setHoverCustomizer(new HoverCustomizer() { @Override public String hoverHTML(Object o, ListGridRecord listGridRecord, int i, int i2) { Long recovered = listGridRecord.getAttributeAsLong("recovered"); if(recovered != null && recovered.longValue() > 0) { Date recoveredTime = listGridRecord.getAttributeAsDate("recoveredTime"); String recoveredTimeString = TimestampCellFormatter.format(recoveredTime, TimestampCellFormatter.DATE_TIME_FORMAT_FULL); return new StringBuilder() .append("<p style='width:500px'>") .append(MSG.view_alerts_field_recovered_status_hover(recoveredTimeString)) .append("</p>").toString(); } return ""; } }); recoveredField.setShowHover(true); fields.add(recoveredField); if (this.entityContext.type != EntityContext.Type.Resource) { ListGridField resourceNameField = new ListGridField(AncestryUtil.RESOURCE_NAME, MSG.common_title_resource()); resourceNameField.setCellFormatter(new CellFormatter() { public String format(Object value, ListGridRecord record, int rowNum, int colNum) { String url = LinkManager.getResourceLink(record.getAttributeAsInt(AncestryUtil.RESOURCE_ID)); return LinkManager.getHref(url, value.toString()); } }); resourceNameField.setShowHover(true); resourceNameField.setHoverCustomizer(new HoverCustomizer() { public String hoverHTML(Object value, ListGridRecord listGridRecord, int rowNum, int colNum) { return AncestryUtil.getResourceHoverHTML(listGridRecord, 0); } }); fields.add(resourceNameField); if (showResourceAncestry) { ListGridField ancestryField = AncestryUtil.setupAncestryListGridField(); fields.add(ancestryField); ancestryField.setWidth("20%"); } ctimeField.setWidth(100); nameField.setWidth("15%"); conditionField.setWidth("15%"); conditionLogField.setWidth("25%"); priorityField.setWidth(50); statusField.setWidth(80); recoveredField.setWidth(80); resourceNameField.setWidth("20%"); } else { ctimeField.setWidth(200); nameField.setWidth("15%"); conditionField.setWidth("20%"); conditionLogField.setWidth("35%"); priorityField.setWidth(50); statusField.setWidth("25%"); recoveredField.setWidth(80); } return fields; } @Override protected void executeFetch(final DSRequest request, final DSResponse response, final AlertCriteria criteria) { if (criteria == null) { // the user selected no priorities in the filter - it makes sense from the UI perspective to show 0 rows response.setTotalRows(0); processResponse(request.getRequestId(), response); return; } final long start = System.currentTimeMillis(); this.alertService.findAlertsByCriteria(criteria, new AsyncCallback<PageList<Alert>>() { public void onFailure(Throwable caught) { CoreGUI.getErrorHandler().handleError(MSG.view_alerts_loadFailed(), caught); response.setStatus(RPCResponse.STATUS_FAILURE); processResponse(request.getRequestId(), response); } public void onSuccess(PageList<Alert> result) { long fetchTime = System.currentTimeMillis() - start; Log.info(result.size() + " alerts fetched in: " + fetchTime + "ms"); dataRetrieved(result, response, request); } }); } /** * Additional processing to support entity-specific or cross-resource views, and something that can be overridden. */ protected void dataRetrieved(final PageList<Alert> result, final DSResponse response, final DSRequest request) { switch (entityContext.type) { // no need to disambiguate, the alerts are for a single resource case Resource: response.setData(buildRecords(result)); setPagingInfo(response, result); processResponse(request.getRequestId(), response); break; // disambiguate as the results could be cross-resource default: Set<Integer> typesSet = new HashSet<Integer>(); Set<String> ancestries = new HashSet<String>(); for (Alert alert : result) { Resource resource = alert.getAlertDefinition().getResource(); typesSet.add(resource.getResourceType().getId()); ancestries.add(resource.getAncestry()); } // In addition to the types of the result resources, get the types of their ancestry typesSet.addAll(AncestryUtil.getAncestryTypeIds(ancestries)); ResourceTypeRepository typeRepo = ResourceTypeRepository.Cache.getInstance(); typeRepo.getResourceTypes(typesSet.toArray(new Integer[typesSet.size()]), new TypesLoadedCallback() { @Override public void onTypesLoaded(Map<Integer, ResourceType> types) { // Smartgwt has issues storing a Map as a ListGridRecord attribute. Wrap it in a pojo. AncestryUtil.MapWrapper typesWrapper = new AncestryUtil.MapWrapper(types); Record[] records = buildRecords(result); for (Record record : records) { // To avoid a lot of unnecessary String construction, be lazy about building ancestry hover text. // Store the types map off the records so we can build a detailed hover string as needed. record.setAttribute(AncestryUtil.RESOURCE_ANCESTRY_TYPES, typesWrapper); // Build the decoded ancestry Strings now for display record.setAttribute(AncestryUtil.RESOURCE_ANCESTRY_VALUE, AncestryUtil.getAncestryValue(record)); } response.setData(records); // for paging to work we have to specify size of full result set setPagingInfo(response, result); processResponse(request.getRequestId(), response); } }); } } /** * Sub-classes can override this to add fine-grained control over the result set size. By default the * total rows are set to the total result set for the query, allowing proper paging. But some views (portlets) * may want to limit results to a small set (like most recent). * @param result * @param response * @param request * * @return should not exceed result.getTotalSize(). */ protected int getTotalRows(final PageList<Alert> result, final DSResponse response, final DSRequest request) { return result.getTotalSize(); } @Override protected AlertCriteria getFetchCriteria(DSRequest request) { AlertPriority[] prioritiesFilter = getArrayFilter(request, FILTER_PRIORITIES, AlertPriority.class); if (prioritiesFilter == null || prioritiesFilter.length == 0) { return null; // user didn't select any priorities - return null to indicate no data should be displayed } AlertCriteria criteria = new AlertCriteria(); // There's no need to add a priorities filter to the criteria if the user specified all priorities. if (prioritiesFilter.length != AlertPriority.values().length) { criteria.addFilterPriorities(prioritiesFilter); } Date startDateFilter = getFilter(request, DateFilterItem.START_DATE_FILTER, Date.class); if(startDateFilter != null){ Date startOfDay = DateFilterItem.adjustTimeToStartOfDay(startDateFilter); criteria.addFilterStartTime(startOfDay.getTime()); } Date endDateFilter = getFilter(request, DateFilterItem.END_DATE_FILTER, Date.class); if( endDateFilter!= null){ Date endOfDay = DateFilterItem.adjustTimeToEndOfDay(endDateFilter); criteria.addFilterEndTime(endOfDay.getTime()); } criteria.addFilterResourceIds(getArrayFilter(request, FILTER_RESOURCE_IDS, Integer.class)); criteria.addFilterEntityContext(entityContext); criteria.fetchConditionLogs(true); // criteria.fetchGroupAlertDefinition(true); AlertFilter[] alertFilters = getArrayFilter(request, FILTER_STATUS, AlertFilter.class); if(alertFilters != null && alertFilters.length > 0) { // This feels duplicate from AlertsPortletDataSource.. for(AlertFilter filter : alertFilters) { if(filter.equals(AlertFilter.ACKNOWLEDGED_STATUS)) { criteria.addFilterUnacknowledgedOnly(true); } else if(filter.equals(AlertFilter.RECOVERED_STATUS)) { criteria.addFilterRecovered(true); } else if(filter.equals(AlertFilter.RECOVERY_TYPE)) { criteria.addFilterRecoveryIds(Integer.valueOf(0)); // Filter all alerts with recoveryId = 0 } } } String nameFilter = getFilter(request, FILTER_NAME, String.class); if(nameFilter != null && nameFilter.length() > 0) { criteria.addFilterName(nameFilter); } return criteria; } @Override protected String getSortFieldForColumn(String columnName) { String sortField; if (AncestryUtil.RESOURCE_ANCESTRY.equals(columnName)) { sortField = "alertDefinition.resource.ancestry"; } else if ("status".equals(columnName)) { sortField = "acknowledgeTime"; } else { sortField = super.getSortFieldForColumn(columnName); } return sortField; } @Override public Alert copyValues(Record from) { return null; // TODO: Implement this method. } @Override public ListGridRecord copyValues(Alert from) { return convert(from); } public static ListGridRecord convert(Alert from) { ListGridRecord record = new ListGridRecord(); record.setAttribute("id", from.getId()); record.setAttribute("ctime", new Date(from.getCtime())); if (from.getAcknowledgeTime() != null && from.getAcknowledgeTime().longValue() > 0) { record.setAttribute("acknowledgeTime", new Date(from.getAcknowledgeTime().longValue())); } record.setAttribute("acknowledgingSubject", from.getAcknowledgingSubject()); if(from.getRecoveryTime() != null && from.getRecoveryTime().longValue() > 0) { record.setAttribute("recovered", from.getRecoveryTime()); record.setAttribute("recoveredTime", new Date(from.getRecoveryTime().longValue())); } AlertDefinition alertDefinition = from.getAlertDefinition(); record.setAttribute("definitionId", alertDefinition.getId()); Resource resource = alertDefinition.getResource(); record.setAttribute("name", alertDefinition.getName()); record.setAttribute("description", alertDefinition.getDescription()); record.setAttribute("priority", ImageManager.getAlertIcon(alertDefinition.getPriority())); // for ancestry handling record.setAttribute(AncestryUtil.RESOURCE_ID, resource.getId()); record.setAttribute(AncestryUtil.RESOURCE_NAME, resource.getName()); record.setAttribute(AncestryUtil.RESOURCE_ANCESTRY, resource.getAncestry()); record.setAttribute(AncestryUtil.RESOURCE_TYPE_ID, resource.getResourceType().getId()); AlertDefinition groupAlertDefinition = alertDefinition.getGroupAlertDefinition(); Integer parentId = alertDefinition.getParentId(); if (groupAlertDefinition != null && groupAlertDefinition.getGroup() != null) { boolean isAutogroup = groupAlertDefinition.getGroup().getAutoGroupParentResource() != null; record.setAttribute(FIELD_PARENT, (isAutogroup ? "#Resource/AutoGroup/" : "#ResourceGroup/") + groupAlertDefinition.getGroup().getId() + "/Alerts/Definitions/" + groupAlertDefinition.getId()); record.setLinkText(MSG.view_alert_definition_for_group()); } else if (parentId != null && parentId.intValue() != 0) { record.setAttribute( FIELD_PARENT, LinkManager.getAdminTemplatesEditLink(AlertDefinitionTemplateTypeView.VIEW_ID.getName(), resource .getResourceType().getId()) + "/" + parentId); record.setLinkText(MSG.view_alert_definition_for_type()); } Set<AlertConditionLog> conditionLogs = from.getConditionLogs(); String conditionText; String conditionValue; if (conditionLogs.size() > 1) { conditionText = MSG.view_alerts_field_condition_text_many(); conditionValue = "--"; } else if (conditionLogs.size() == 1) { AlertConditionLog conditionLog = conditionLogs.iterator().next(); AlertCondition condition = conditionLog.getCondition(); conditionText = AlertFormatUtility.formatAlertConditionForDisplay(condition); conditionValue = conditionLog.getValue(); if (condition.getMeasurementDefinition() != null) { try { conditionValue = MeasurementConverterClient.format(Double.valueOf(conditionLog.getValue()), condition.getMeasurementDefinition().getUnits(), true); } catch (Exception e) { // the condition log value was probably not a number (most likely a trait). Ignore this exception. // even if any other errors occur trying to format the value, ignore this and just use the raw value string } } } else { conditionText = MSG.view_alerts_field_condition_text_none(); conditionValue = "--"; } record.setAttribute("conditionText", conditionText); if (conditionValue.contains("extraInfo=")) { conditionValue = conditionValue.replaceFirst("extraInfo=\\[",""); conditionValue = conditionValue.substring(0,conditionValue.length()-1); } record.setAttribute("conditionValue", conditionValue); // We also need the'raw' notification data to show in details DataClass[] conditions = new DataClass[from.getConditionLogs().size()]; int i = 0; for (AlertConditionLog log : from.getConditionLogs()) { AlertCondition condition = log.getCondition(); DataClass dc = new DataClass(); dc.setAttribute("text", AlertFormatUtility.formatAlertConditionForDisplay(condition)); String value = log.getValue(); if (condition.getMeasurementDefinition() != null) { try { value = MeasurementConverterClient.format(Double.valueOf(log.getValue()), condition .getMeasurementDefinition().getUnits(), true); } catch (Exception e) { // the condition log value was probably not a number (most likely a trait). Ignore this exception. // even if any other errors occur trying to format the value, ignore this and just use the raw value string } } // Remove the extraInfo=[ ] that is added when storing the raw event data in the data base if (value.contains("extraInfo=")) { value = value.replaceFirst("extraInfo=\\[",""); value = value.substring(0,value.length()-1); } dc.setAttribute("value", value); conditions[i++] = dc; } record.setAttribute("conditionLogs", conditions); record.setAttribute("conditionExpression", alertDefinition.getConditionExpression()); String recoveryInfo = AlertFormatUtility.getAlertRecoveryInfo(from); record.setAttribute("recoveryInfo", recoveryInfo); // Alert notification logs DataClass[] notifications = new DataClass[from.getAlertNotificationLogs().size()]; i = 0; for (AlertNotificationLog log : from.getAlertNotificationLogs()) { DataClass dc = new DataClass(); dc.setAttribute("sender", log.getSender()); dc.setAttribute("status", log.getResultState().name()); dc.setAttribute("message", log.getMessage()); notifications[i++] = dc; } record.setAttribute("notificationLogs", notifications); return record; } protected void executeRemove(Record recordToRemove, final DSRequest request, final DSResponse response) { // TODO Window.alert(String.valueOf(recordToRemove.getAttributeAsInt("id"))); } public AlertGWTServiceAsync getAlertService() { return alertService; } protected EntityContext getEntityContext() { return entityContext; } protected void setEntityContext(EntityContext entityContext) { this.entityContext = entityContext; } }