/*
* RHQ Management Platform
* Copyright (C) 2005-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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.coregui.client.inventory.groups.definitions;
import static org.rhq.coregui.client.inventory.groups.ResourceGroupDataSourceField.CATEGORY;
import static org.rhq.coregui.client.inventory.groups.ResourceGroupDataSourceField.DESCRIPTION;
import static org.rhq.coregui.client.inventory.groups.ResourceGroupDataSourceField.NAME;
import static org.rhq.coregui.client.inventory.groups.ResourceGroupDataSourceField.PLUGIN;
import static org.rhq.coregui.client.inventory.groups.ResourceGroupDataSourceField.TYPE;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.smartgwt.client.data.Criteria;
import com.smartgwt.client.data.DSCallback;
import com.smartgwt.client.data.DSRequest;
import com.smartgwt.client.data.DSResponse;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.DSOperationType;
import com.smartgwt.client.types.ListGridFieldType;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.util.BooleanCallback;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.events.DoubleClickEvent;
import com.smartgwt.client.widgets.events.DoubleClickHandler;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.CheckboxItem;
import com.smartgwt.client.widgets.form.fields.FormItemIcon;
import com.smartgwt.client.widgets.form.fields.SelectItem;
import com.smartgwt.client.widgets.form.fields.SpacerItem;
import com.smartgwt.client.widgets.form.fields.SpinnerItem;
import com.smartgwt.client.widgets.form.fields.TextAreaItem;
import com.smartgwt.client.widgets.form.fields.TextItem;
import com.smartgwt.client.widgets.form.fields.events.ChangedEvent;
import com.smartgwt.client.widgets.form.fields.events.ChangedHandler;
import com.smartgwt.client.widgets.form.fields.events.FormItemClickHandler;
import com.smartgwt.client.widgets.form.fields.events.FormItemIconClickEvent;
import com.smartgwt.client.widgets.form.validator.IsIntegerValidator;
import com.smartgwt.client.widgets.form.validator.RegExpValidator;
import com.smartgwt.client.widgets.form.validator.Validator;
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 com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.Layout;
import org.rhq.core.domain.authz.Permission;
import org.rhq.core.domain.criteria.ResourceGroupDefinitionCriteria;
import org.rhq.core.domain.plugin.CannedGroupExpression;
import org.rhq.core.domain.resource.group.DuplicateExpressionTypeException;
import org.rhq.core.domain.resource.group.GroupCategory;
import org.rhq.core.domain.resource.group.GroupDefinition;
import org.rhq.core.domain.util.PageList;
import org.rhq.coregui.client.BookmarkableView;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.ImageManager;
import org.rhq.coregui.client.LinkManager;
import org.rhq.coregui.client.PermissionsLoadedListener;
import org.rhq.coregui.client.PermissionsLoader;
import org.rhq.coregui.client.ViewPath;
import org.rhq.coregui.client.components.table.EscapedHtmlCellFormatter;
import org.rhq.coregui.client.components.table.Table;
import org.rhq.coregui.client.gwt.GWTServiceLookup;
import org.rhq.coregui.client.inventory.groups.ResourceGroupsDataSource;
import org.rhq.coregui.client.inventory.groups.definitions.GroupDefinitionExpressionBuilder.AddExpressionHandler;
import org.rhq.coregui.client.util.StringUtility;
import org.rhq.coregui.client.util.enhanced.EnhancedIButton;
import org.rhq.coregui.client.util.enhanced.EnhancedIButton.ButtonColor;
import org.rhq.coregui.client.util.enhanced.EnhancedVLayout;
import org.rhq.coregui.client.util.message.Message;
import org.rhq.coregui.client.util.message.Message.Severity;
/**
* @author Joseph Marques
*/
public class SingleGroupDefinitionView extends EnhancedVLayout implements BookmarkableView {
private int groupDefinitionId;
private GroupDefinition groupDefinition;
private String basePath;
private GroupDefinitionExpressionBuilder builder;
// editable form
private TextItem id;
private TextItem name;
private TextAreaItem description;
private CheckboxItem recursive;
private SpacerItem templateSelectorTitleSpacer;
private SelectItem templateSelector;
private TreeMap<String,CannedGroupExpression> cannedExpressions;
private TextAreaItem expression;
private SpinnerItem recalculationInterval;
public SingleGroupDefinitionView() {
super();
buildForm();
setWidth100();
setOverflow(Overflow.AUTO);
}
public void setGroupDefinition(final GroupDefinition groupDefinition) {
this.groupDefinition = groupDefinition;
// form setup
id.setValue(groupDefinition.getId());
name.setValue(groupDefinition.getName());
recursive.setValue(groupDefinition.isRecursive());
description.setValue(groupDefinition.getDescription());
recalculationInterval.setValue(groupDefinition.getRecalculationInterval() / (60 * 1000));
Validator intervalValidator = new IsIntegerValidator();
intervalValidator.setErrorMessage(MSG.view_dynagroup_recalculationInterval_error());
recalculationInterval.setValidators(intervalValidator);
expression.setValue(groupDefinition.getExpression());
Validator nameValidator = new RegExpValidator("^[^\\<\\$\\'\\{\\[]{1,100}$");
nameValidator.setErrorMessage("Name must not contain following characters: < $ ' [ {");
name.setValidators(nameValidator);
final DynamicForm form = new DynamicForm();
form.setFields(id, name, description, templateSelectorTitleSpacer, templateSelector, expression, recursive,
recalculationInterval);
form.setDataSource(GroupDefinitionDataSource.getInstance());
form.setHiliteRequiredFields(true);
form.setRequiredTitleSuffix(" <span style=\"color: red;\">* </span>:");
DSOperationType saveOperationType = (groupDefinition.getId() == 0) ? DSOperationType.ADD
: DSOperationType.UPDATE;
form.setSaveOperationType(saveOperationType);
final DynaGroupChildrenView dynaGroupChildrenView = new DynaGroupChildrenView(groupDefinitionId);
// button setup
IButton saveButton = new EnhancedIButton(MSG.common_button_save(), ButtonColor.BLUE);
saveButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent clickEvent) {
if (form.validate()) {
saveFormCheckCannedExpr(form, dynaGroupChildrenView, false);
}
}
});
IButton recalculateButton = new EnhancedIButton(MSG.view_dynagroup_saveAndRecalculate());
recalculateButton.setWidth(150);
recalculateButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent clickEvent) {
if (form.validate()) {
saveFormCheckCannedExpr(form, dynaGroupChildrenView, true);
}
}
});
IButton resetButton = new EnhancedIButton(MSG.common_button_reset());
resetButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent clickEvent) {
form.reset();
}
});
HLayout buttonLayout = new HLayout(10); // margin between members
buttonLayout.setMargin(10); // margin around layout widget
buttonLayout.addMember(saveButton);
buttonLayout.addMember(recalculateButton);
buttonLayout.addMember(resetButton);
// canvas setup
addMember(form);
addMember(buttonLayout);
addMember(dynaGroupChildrenView);
markForRedraw();
}
public void setCannedExpressions(final ArrayList<CannedGroupExpression> list) {
this.cannedExpressions = new TreeMap<String, CannedGroupExpression>();
for (CannedGroupExpression cge : list) {
this.cannedExpressions.put(cge.getPlugin() + " - " + cge.getName(), cge);
}
templateSelector.setValueMap(cannedExpressions.keySet().toArray(new String[cannedExpressions.size()]));
}
private void saveFormCheckCannedExpr(final DynamicForm form, final DynaGroupChildrenView dynaGroupChildrenView,
final boolean recalc) {
if (this.groupDefinition.getCannedExpression() != null) {
SC.ask(MSG.view_dynagroup_saveCannedDefWarning(this.groupDefinition.getCannedExpression().replaceAll(":.*", "")), new BooleanCallback() {
public void execute(Boolean confirmed) {
if (confirmed) {
saveForm(form, dynaGroupChildrenView, recalc);
}
}
});
} else {
saveForm(form, dynaGroupChildrenView, recalc);
}
}
private void saveForm(final DynamicForm form, final DynaGroupChildrenView dynaGroupChildrenView,
final boolean recalc) {
form.saveData(new DSCallback() {
@Override
public void execute(DSResponse response, Object rawData, DSRequest request) {
boolean hasDuplicateNameError = false;
boolean hasParseExpressionError = false;
if (form.isNewRecord()) {
Record[] results = response.getData();
if (results.length != 1) {
// handle the special case for name already exists error
for (Object entryObject : response.getErrors().entrySet()) {
Map.Entry thisEntry = (Map.Entry) entryObject;
String fieldKey = (String) thisEntry.getKey();
// the duplicate name error will be keyed by 'name' in the errorMap
if (fieldKey.equals("name")) {
String errorValue = (String) thisEntry.getValue();
CoreGUI.getErrorHandler().handleError(errorValue);
hasDuplicateNameError = true;
} else if (fieldKey.equals("expression")) {
String errorValue = (String) thisEntry.getValue();
CoreGUI.getErrorHandler().handleError(errorValue);
hasParseExpressionError = true;
}
}
if (!hasDuplicateNameError && !hasParseExpressionError) {
CoreGUI.getErrorHandler().handleError(
MSG.view_dynagroup_singleSaveFailure(String.valueOf(results.length)));
}
} else {
Record newRecord = results[0];
GroupDefinition newGroupDefinition = GroupDefinitionDataSource.getInstance().copyValues(
newRecord);
if (recalc) {
recalculate(dynaGroupChildrenView, newGroupDefinition.getId());
}
CoreGUI.goToView(basePath + "/" + newGroupDefinition.getId());
}
} else {
dynaGroupChildrenView.refresh();
if (recalc) {
recalculate(dynaGroupChildrenView, groupDefinitionId);
}
}
}
});
}
private void recalculate(final DynaGroupChildrenView dynaGroupChildrenView, int groupDefId) {
GWTServiceLookup.getResourceGroupService(600000).recalculateGroupDefinitions(new int[] { groupDefId },
new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
if (caught instanceof DuplicateExpressionTypeException) {
CoreGUI.getMessageCenter().notify(new Message(caught.getMessage(), Message.Severity.Warning));
} else {
CoreGUI.getErrorHandler().handleError(MSG.view_dynagroup_recalcFailure(), caught);
}
}
@Override
public void onSuccess(Void result) {
dynaGroupChildrenView.refresh();
CoreGUI.getMessageCenter()
.notify(new Message(MSG.view_dynagroup_recalcSuccessful(), Severity.Info));
}
});
}
class DynaGroupChildrenView extends Table<ResourceGroupsDataSource> {
public DynaGroupChildrenView(int groupDefinitionId) {
super(MSG.view_dynagroup_children(), new Criteria("groupDefinitionId", String.valueOf(groupDefinition
.getId())));
setDataSource(ResourceGroupsDataSource.getInstance());
}
@Override
protected void configureTableContents(Layout contents) {
super.configureTableContents(contents);
contents.setMinMemberSize(100);
contents.setOverflow(Overflow.VISIBLE);
}
@Override
protected void configureTable() {
// i couldn't get percentage widths to work for some reason
ListGridField idField = new ListGridField("id", MSG.common_title_id());
idField.setType(ListGridFieldType.INTEGER);
idField.setWidth("50");
ListGridField nameField = new ListGridField(NAME.propertyName(), NAME.title());
nameField.setWidth("*");
nameField.setCellFormatter(new CellFormatter() {
@Override
public String format(Object value, ListGridRecord record, int rowNum, int colNum) {
String linkName = record.getAttributeAsString(NAME.propertyName());
String linkUrl = LinkManager.getResourceGroupLink(record.getAttributeAsInt("id"));
return "<a href=\"" + linkUrl + "\">" + StringUtility.escapeHtml(linkName) + "</a>";
}
});
ListGridField descriptionField = new ListGridField(DESCRIPTION.propertyName(), DESCRIPTION.title());
descriptionField.setWidth("300");
descriptionField.setCellFormatter(new EscapedHtmlCellFormatter());
ListGridField typeNameField = new ListGridField(TYPE.propertyName(), TYPE.title());
typeNameField.setWidth("100");
ListGridField pluginNameField = new ListGridField(PLUGIN.propertyName(), PLUGIN.title());
pluginNameField.setWidth("100");
ListGridField categoryField = new ListGridField(CATEGORY.propertyName(), CATEGORY.title());
categoryField.setWidth("30");
categoryField.setAlign(Alignment.CENTER);
categoryField.setTitle(" ");
categoryField.setType(ListGridFieldType.ICON);
HashMap<String, String> categoryImages = new HashMap<String, String>(2);
categoryImages.put(GroupCategory.COMPATIBLE.name(), ImageManager.getGroupIcon(GroupCategory.COMPATIBLE));
categoryImages.put(GroupCategory.MIXED.name(), ImageManager.getGroupIcon(GroupCategory.MIXED));
categoryField.setValueIcons(categoryImages);
categoryField.setShowHover(true);
categoryField.setHoverCustomizer(new HoverCustomizer() {
@Override
public String hoverHTML(Object value, ListGridRecord record, int rowNum, int colNum) {
String category = record.getAttributeAsString(CATEGORY.propertyName());
if (GroupCategory.COMPATIBLE.name().equals(category)) {
return MSG.view_dynagroup_compatible();
} else if (GroupCategory.MIXED.name().equals(category)) {
return MSG.view_dynagroup_mixed();
} else {
throw new IllegalStateException("Unknown group category: " + category);
}
}
});
setListGridFields(idField, categoryField, nameField, descriptionField, typeNameField, pluginNameField);
setListGridDoubleClickHandler(new DoubleClickHandler() {
@Override
public void onDoubleClick(DoubleClickEvent event) {
ListGrid listGrid = (ListGrid) event.getSource();
ListGridRecord[] selectedRows = listGrid.getSelectedRecords();
if (selectedRows != null && selectedRows.length == 1) {
String groupUrl = LinkManager.getResourceGroupLink(selectedRows[0].getAttributeAsInt("id"));
CoreGUI.goToView(groupUrl);
}
}
});
}
}
public void switchToEditMode() {
name.show();
description.show();
recursive.show();
templateSelectorTitleSpacer.show();
templateSelector.show();
expression.show();
recalculationInterval.show();
markForRedraw();
}
private void buildForm() {
FormItemIcon expressionBuilderIcon = new FormItemIcon();
expressionBuilderIcon.setSrc("[SKIN]/actions/add.png");
expressionBuilderIcon.setPrompt(MSG.view_dynagroup_expressionBuilderIconTooltip());
expressionBuilderIcon.addFormItemClickHandler(new FormItemClickHandler() {
@Override
public void onFormItemClick(FormItemIconClickEvent event) {
showExpressionBuilder();
}
});
id = new TextItem("id", MSG.common_title_id());
id.setVisible(false);
name = new TextItem("name", MSG.common_title_name());
name.setWidth(400);
name.setDefaultValue("");
description = new TextAreaItem("description", MSG.common_title_description());
description.setWidth(400);
description.setHeight(50);
description.setDefaultValue("");
recursive = new CheckboxItem("recursive", MSG.view_dynagroup_recursive());
templateSelectorTitleSpacer = new SpacerItem();
templateSelectorTitleSpacer.setShowTitle(false);
templateSelectorTitleSpacer.setColSpan(1);
templateSelectorTitleSpacer.setEndRow(false);
templateSelector = new SelectItem("templateSelector", MSG.view_dynagroup_exprBuilder_savedExpression());
templateSelector.setAllowEmptyValue(true);
templateSelector.setWidth(400);
templateSelector.setColSpan(1);
templateSelector.setEndRow(true);
templateSelector.setStartRow(false);
templateSelector.setIcons(expressionBuilderIcon);
templateSelector.setHoverWidth(200);
templateSelector.setValueMap("");
expression = new TextAreaItem("expression", MSG.view_dynagroup_expression());
expression.setWidth(400);
expression.setHeight(150);
expression.setDefaultValue("");
expression.addChangedHandler(new ChangedHandler() {
@Override
public void onChanged(ChangedEvent event) {
// the user changed the expression text, clear the template drop down
// so the user isn't confused thinking this new value is the template text
templateSelector.clearValue();
}
});
if (builder != null) {
builder.destroy();
builder = null;
}
templateSelector.addChangedHandler(new ChangedHandler() {
@Override
public void onChanged(ChangedEvent event) {
if (event.getValue() != null) {
String selectedTemplateId = event.getValue().toString();
// user picked one of the canned expressions - update field values
CannedGroupExpression cge = cannedExpressions.get(selectedTemplateId);
if (cge != null) {
StringBuilder expr = new StringBuilder();
for (String e : cge.getExpression()) {
expr.append(e+"\n");
}
expression.setValue(expr.toString());
recalculationInterval.setValue(cge.getRecalcInMinutes());
description.setValue(cge.getDescription());
recursive.setValue(cge.isRecursive());
name.setValue(cge.getName());
}
else {
expression.setValue("");
}
} else {
expression.setValue("");
}
}
});
recalculationInterval = new SpinnerItem("recalculationInterval", MSG.view_dynagroup_recalculationInterval());
recalculationInterval.setMin(0);
recalculationInterval.setMax(60 * 24 * 7); // max set to 1 week
recalculationInterval.setDefaultValue(0);
recalculationInterval.setStep(1); // the recalc interval is in milliseconds, step up one minute at a time
}
private void lookupCannedExpressions() {
GWTServiceLookup.getPluginService().getCannedGroupExpressions(new AsyncCallback<ArrayList<CannedGroupExpression>>() {
@Override
public void onFailure(Throwable arg0) {
}
@Override
public void onSuccess(ArrayList<CannedGroupExpression> arg0) {
setCannedExpressions(arg0);
}
});;
}
private void lookupDetails(final int groupDefinitionId) {
ResourceGroupDefinitionCriteria criteria = new ResourceGroupDefinitionCriteria();
criteria.addFilterId(groupDefinitionId);
if (groupDefinitionId == 0) {
GroupDefinition newGroupDefinition = new GroupDefinition();
setGroupDefinition(newGroupDefinition);
switchToEditMode();
} else {
GWTServiceLookup.getResourceGroupService().findGroupDefinitionsByCriteria(criteria,
new AsyncCallback<PageList<GroupDefinition>>() {
public void onFailure(Throwable caught) {
CoreGUI.getErrorHandler().handleError(
MSG.view_dynagroup_loadDefinitionFailure(String.valueOf(groupDefinitionId)), caught);
History.back();
}
public void onSuccess(PageList<GroupDefinition> result) {
if (result.size() == 0) {
CoreGUI.getErrorHandler().handleError(
MSG.view_dynagroup_loadDefinitionMissing(String.valueOf(groupDefinitionId)));
History.back();
} else {
GroupDefinition existingGroupDefinition = result.get(0);
setGroupDefinition(existingGroupDefinition);
switchToEditMode();
}
}
});
}
}
private void showExpressionBuilder() {
if (builder == null) {
builder = new GroupDefinitionExpressionBuilder(new AddExpressionHandler() {
@Override
public void addExpression(String newExpression) {
String currentExpression = "";
String value = expression.getValueAsString();
if (value != null) {
currentExpression = value + "\n";
}
expression.setValue(currentExpression + newExpression);
// the user changed the expression text, clear the template drop down
// so the user isn't confused thinking this new value is the template text
templateSelector.clearValue();
}
});
}
builder.show();
}
@Override
public void renderView(final ViewPath viewPath) {
new PermissionsLoader().loadExplicitGlobalPermissions(new PermissionsLoadedListener() {
@Override
public void onPermissionsLoaded(Set<Permission> permissions) {
if (permissions != null && permissions.contains(Permission.MANAGE_INVENTORY)) {
groupDefinitionId = viewPath.getCurrentAsInt();
basePath = viewPath.getPathToCurrent();
lookupDetails(groupDefinitionId);
lookupCannedExpressions();
} else {
handleAuthorizationFailure();
}
}
private void handleAuthorizationFailure() {
CoreGUI.getErrorHandler().handleError(MSG.view_dynagroup_permDenied());
History.back();
}
});
}
}