/*
* 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.drift;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.smartgwt.client.data.Criteria;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.Window;
import com.smartgwt.client.widgets.form.fields.SelectItem;
import com.smartgwt.client.widgets.form.fields.TextItem;
import org.rhq.core.domain.common.EntityContext;
import org.rhq.core.domain.criteria.DriftDefinitionCriteria;
import org.rhq.core.domain.criteria.GenericDriftChangeSetCriteria;
import org.rhq.core.domain.drift.DriftCategory;
import org.rhq.core.domain.drift.DriftChangeSet;
import org.rhq.core.domain.drift.DriftDefinition;
import org.rhq.core.domain.drift.FileDiffReport;
import org.rhq.core.domain.util.PageList;
import org.rhq.core.domain.util.PageOrdering;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.DetailsView;
import org.rhq.coregui.client.ImageManager;
import org.rhq.coregui.client.LinkManager;
import org.rhq.coregui.client.ViewPath;
import org.rhq.coregui.client.components.buttons.BackButton;
import org.rhq.coregui.client.components.carousel.BookmarkableCarousel;
import org.rhq.coregui.client.components.form.EnumSelectItem;
import org.rhq.coregui.client.drift.DriftCarouselMemberView.DriftSelectionListener;
import org.rhq.coregui.client.drift.util.DiffUtility;
import org.rhq.coregui.client.gwt.DriftGWTServiceAsync;
import org.rhq.coregui.client.gwt.GWTServiceLookup;
import org.rhq.coregui.client.util.RPCDataSource;
/**
* A carousel view used for display of Drift Definition detail. Each carousel member is a snapshot delta
* view. This view in turn serves as a "Master" view for snapshot and drift "Detail" views.
*
* @author Jay Shaughnessy
*/
public class DriftCarouselView extends BookmarkableCarousel implements DetailsView {
private static final int CAROUSEL_DEFAULT_SIZE = 4;
private static final String CAROUSEL_MEMBER_FIXED_WIDTH = "250px";
private int driftDefId;
private EntityContext context;
private boolean hasWriteAccess;
private Integer maxSnapshotVersion;
private ArrayList<Record> selectedRecords = new ArrayList<Record>();
private boolean useDriftDetailsView;
private DriftGWTServiceAsync driftService = GWTServiceLookup.getDriftService();
public DriftCarouselView(EntityContext entityContext, int driftDefId, boolean hasWriteAccess) {
super();
this.context = entityContext;
this.driftDefId = driftDefId;
this.hasWriteAccess = hasWriteAccess;
}
@Override
protected void onDraw() {
DriftDefinitionCriteria defCriteria = new DriftDefinitionCriteria();
defCriteria.addFilterId(driftDefId);
defCriteria.fetchConfiguration(true);
driftService.findDriftDefinitionsByCriteria(defCriteria, new AsyncCallback<PageList<DriftDefinition>>() {
public void onFailure(Throwable caught) {
CoreGUI.getErrorHandler().handleError(MSG.view_drift_failure_load(), caught);
}
public void onSuccess(PageList<DriftDefinition> result) {
// Create and add the details canvas for the def
buildTitle(result.get(0));
buildCarousel(false);
}
});
}
@Override
@SuppressWarnings("unchecked")
protected void buildCarousel(final boolean isRefresh) {
super.buildCarousel(isRefresh);
// clear our list of currently selected records because we're recreating the carousel members
selectedRecords.clear();
// Fetch the ChangeSets ("header" info only, these will be held in memory)
GenericDriftChangeSetCriteria changeSetCriteria = new GenericDriftChangeSetCriteria();
// Limit to change sets for the relevant drift detection definition
changeSetCriteria.addFilterDriftDefinitionId(driftDefId);
// Never include the initial snapshot, limit to drift instances only
changeSetCriteria.addFilterStartVersion("1");
// Limit to change sets meeting any current carousel filtering criteria
if (isRefresh) {
addCarouselCriteria(changeSetCriteria);
}
// return most recent first
changeSetCriteria.addSortVersion(PageOrdering.DESC);
driftService.findDriftChangeSetsByCriteria(changeSetCriteria,
new AsyncCallback<PageList<? extends DriftChangeSet>>() {
public void onSuccess(PageList<? extends DriftChangeSet> result) {
Integer carouselSize = getCarouselSizeFilter();
carouselSize = (null == carouselSize || carouselSize < 1) ? CAROUSEL_DEFAULT_SIZE : carouselSize;
int size = carouselSize;
Integer carouselStart = null;
Integer carouselEnd = null;
Criteria initialCriteria = getInitialMemberCriteria(isRefresh ? getCurrentCriteria() : null);
for (DriftChangeSet changeSet : result) {
DriftCarouselMemberView view = new DriftCarouselMemberView(context, changeSet, hasWriteAccess,
initialCriteria);
addCarouselMember(view);
view.addDriftSelectionListener(new DriftSelectionListener() {
public void onDriftSelection(Record record, boolean isSelected) {
if (isSelected) {
selectedRecords.add(record);
} else {
selectedRecords.remove(record);
}
DriftCarouselView.this.refreshCarouselInfo();
setFilter(DriftDataSource.FILTER_PATH, record.getAttribute(DriftDataSource.ATTR_PATH));
}
});
// descending order, so highest changeset version first
if (null == carouselStart) {
carouselStart = changeSet.getVersion();
}
carouselEnd = changeSet.getVersion();
if (--size == 0) {
break;
}
}
if (null == maxSnapshotVersion || null == carouselStart || maxSnapshotVersion < carouselStart) {
maxSnapshotVersion = carouselStart;
setCarouselStartFilterMax(maxSnapshotVersion);
}
setCarouselStartFilter(carouselStart);
setCarouselEndFilter(carouselEnd);
setCarouselSizeFilter(carouselSize);
if (!isRefresh) {
DriftCarouselView.super.onDraw();
} else {
DriftCarouselView.this.refreshCarouselInfo();
}
}
public void onFailure(Throwable caught) {
CoreGUI.getErrorHandler().handleError(MSG.view_drift_failure_load(), caught);
}
});
}
protected static Criteria getInitialMemberCriteria(Criteria additionalCriteria) {
if (null == additionalCriteria) {
return DriftHistoryView.INITIAL_CRITERIA;
}
Criteria initialCriteria = new Criteria();
addCriteria(initialCriteria, DriftHistoryView.INITIAL_CRITERIA);
addCriteria(initialCriteria, additionalCriteria);
return initialCriteria;
}
@Override
protected int getDefaultCarouselSize() {
return CAROUSEL_DEFAULT_SIZE;
}
@Override
protected String getCarouselMemberFixedWidth() {
return CAROUSEL_MEMBER_FIXED_WIDTH;
}
/**
* Only return change sets that actually have drift that matches the current carousel filters, including
* the drift level filtering. This allows us to handle gaps in the presented snapshots when certain changesets
* have no drift matching desired criteria.
*
* @param changeSetCriteria
*/
private void addCarouselCriteria(GenericDriftChangeSetCriteria changeSetCriteria) {
Integer startVersion; // low snapshot version (carouselEndFilter)
Integer endVersion; // high snapshot version (carouselStartFilter)
// if no startFilter is set then don't limit the endVersion
endVersion = getCarouselStartFilter();
if (null != endVersion) {
changeSetCriteria.addFilterEndVersion(String.valueOf(endVersion));
}
// if no endFilter is set then include changesets greater than 0 (never include 0, the initial snapshot)
// else ensure endFilter is not greater than makes sense
startVersion = getCarouselEndFilter();
if (null == startVersion || 1 > startVersion) {
startVersion = 1;
} else if (null != endVersion && startVersion > endVersion) {
startVersion = endVersion;
}
if (null != maxSnapshotVersion && startVersion > maxSnapshotVersion) {
startVersion = maxSnapshotVersion;
}
changeSetCriteria.addFilterStartVersion(String.valueOf(startVersion));
// apply the drift-level carousel filters in order to filter out changesets that have no applicable drift
Criteria criteria = getCurrentCriteria();
DriftCategory[] driftCategoriesFilter = RPCDataSource.getArrayFilter(criteria,
DriftDataSource.FILTER_CATEGORIES, DriftCategory.class);
changeSetCriteria.addFilterDriftCategories(driftCategoriesFilter);
String driftPathFilter = RPCDataSource.getFilter(criteria, DriftDataSource.FILTER_PATH, String.class);
if (null != driftPathFilter && !driftPathFilter.isEmpty()) {
changeSetCriteria.addFilterDriftPath(driftPathFilter);
}
}
private void buildTitle(DriftDefinition driftDef) {
setTitleString(driftDef.getName());
setTitleBackButton(new BackButton(MSG.view_tableSection_backButton(),
LinkManager.getDriftDefinitionsLink(this.context.getResourceId())));
}
@Override
protected void configureCarousel() {
addCarouselAction("Compare", MSG.common_button_compare(), null, new CarouselAction() {
public void executeAction(Object actionValue) {
Record record1 = selectedRecords.get(0);
Record record2 = selectedRecords.get(1);
final String path = record1.getAttribute(DriftDataSource.ATTR_PATH);
String id1 = record1.getAttribute(DriftDataSource.ATTR_ID);
String id2 = record2.getAttribute(DriftDataSource.ATTR_ID);
// regardless of selection order, compare the same way, showing newest changes with '+' signs
int version1 = record1.getAttributeAsInt(DriftDataSource.ATTR_CHANGESET_VERSION);
int version2 = record2.getAttributeAsInt(DriftDataSource.ATTR_CHANGESET_VERSION);
String diffOldId = (version1 < version2) ? id1 : id2;
String diffNewId = (version1 < version2) ? id2 : id1;
GWTServiceLookup.getDriftService().generateUnifiedDiffByIds(diffOldId, diffNewId,
new AsyncCallback<FileDiffReport>() {
public void onFailure(Throwable caught) {
CoreGUI.getErrorHandler().handleError("Failed to generate diff.", caught);
}
public void onSuccess(FileDiffReport diffReport) {
String diffContents = DiffUtility.formatAsHtml(diffReport.getDiff(), 1, 2);
Window window = DiffUtility.createDiffViewerWindow(diffContents, path, 1, 2);
window.show();
}
});
}
public boolean isEnabled() {
if (selectedRecords.size() == 2) {
String path1 = selectedRecords.get(0).getAttribute(DriftDataSource.ATTR_PATH);
String path2 = selectedRecords.get(1).getAttribute(DriftDataSource.ATTR_PATH);
return (null != path1) && path1.equals(path2);
}
return false;
}
});
super.configureCarousel();
}
@Override
protected void configureCarouselFilters() {
// drift category filter
LinkedHashMap<String, String> categories = new LinkedHashMap<String, String>(3);
categories.put(DriftCategory.FILE_ADDED.name(), MSG.view_drift_category_fileAdded());
categories.put(DriftCategory.FILE_CHANGED.name(), MSG.view_drift_category_fileChanged());
categories.put(DriftCategory.FILE_REMOVED.name(), MSG.view_drift_category_fileRemoved());
LinkedHashMap<String, String> categoryIcons = new LinkedHashMap<String, String>(3);
categoryIcons.put(DriftCategory.FILE_ADDED.name(), ImageManager.getDriftCategoryIcon(DriftCategory.FILE_ADDED));
categoryIcons.put(DriftCategory.FILE_CHANGED.name(),
ImageManager.getDriftCategoryIcon(DriftCategory.FILE_CHANGED));
categoryIcons.put(DriftCategory.FILE_REMOVED.name(),
ImageManager.getDriftCategoryIcon(DriftCategory.FILE_REMOVED));
SelectItem categoryFilter = new EnumSelectItem(DriftDataSource.FILTER_CATEGORIES, MSG.common_title_category(),
DriftCategory.class, categories, categoryIcons);
// drift file path filter
TextItem pathFilter = new TextItem(DriftDataSource.FILTER_PATH, MSG.common_title_path());
pathFilter.setEndRow(true);
if (isShowFilterForm()) {
setFilterFormItems(categoryFilter, pathFilter);
}
}
@Override
protected String getCarouselStartFilterLabel() {
return MSG.view_drift_carousel_startFilterLabel();
}
@Override
protected String getCarouselSizeFilterLabel() {
return MSG.view_drift_carousel_sizeFilterLabel();
}
@Override
public boolean isEditable() {
// This ensures the default BackButton is not presented. Instead, we implement our own back capability
return true;
}
// this class is somewhat unusual in that it is a detail view for the drift defs list view, but also a pseudo-master
// view for snapshot and drift detail views. It's "pseudo" in that there is no master-detail infrastructure
// like that found in TableSection. The following paths must be handled (starting at the ^):
// #Resource/10001/Drift/Definitions/10001/Drift/driftId
// #Resource/10001/Drift/Definitions/10001/Snapshot/version
// ^
@Override
public void renderView(ViewPath viewPath) {
if (!viewPath.isEnd() && !viewPath.isNextEnd()) {
String detail = viewPath.getNext().getPath();
if ("Drift".equals(detail) || "Snapshot".equals(detail)) {
this.useDriftDetailsView = !viewPath.isNextEnd() && "Drift".equals(viewPath.getNext().getPath());
super.renderView(viewPath);
}
}
}
@Override
public Canvas getDetailsView(String id) {
if (this.useDriftDetailsView) {
return new DriftDetailsView(id);
}
return new DriftSnapshotView(null, context.getResourceId(), driftDefId, Integer.valueOf(id), hasWriteAccess);
}
}