/* * RHQ Management Platform * Copyright (C) 2005-2010 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.bundle.deploy; import java.util.EnumSet; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.rpc.AsyncCallback; import com.smartgwt.client.util.SC; import com.smartgwt.client.widgets.Canvas; import com.smartgwt.client.widgets.form.DynamicForm; import com.smartgwt.client.widgets.form.fields.CanvasItem; import com.smartgwt.client.widgets.form.fields.FormItemIcon; import com.smartgwt.client.widgets.form.fields.RadioGroupItem; import com.smartgwt.client.widgets.form.fields.TextAreaItem; import com.smartgwt.client.widgets.form.fields.TextItem; import com.smartgwt.client.widgets.form.fields.events.ChangeEvent; import com.smartgwt.client.widgets.form.fields.events.ChangeHandler; 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.DataArrivedEvent; import com.smartgwt.client.widgets.form.fields.events.DataArrivedHandler; import com.smartgwt.client.widgets.form.fields.events.IconClickEvent; import com.smartgwt.client.widgets.form.fields.events.IconClickHandler; import com.smartgwt.client.widgets.form.validator.IsIntegerValidator; import com.smartgwt.client.widgets.form.validator.Validator; import com.smartgwt.client.widgets.layout.VLayout; import org.rhq.core.domain.bundle.BundleDestination; import org.rhq.core.domain.bundle.ResourceTypeBundleConfiguration; import org.rhq.core.domain.bundle.ResourceTypeBundleConfiguration.BundleDestinationSpecification; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.domain.resource.group.ResourceGroup; import org.rhq.coregui.client.CoreGUI; import org.rhq.coregui.client.bundle.deploy.selection.SingleCompatibleResourceGroupSelector; import org.rhq.coregui.client.components.wizard.AbstractWizardStep; import org.rhq.coregui.client.gwt.GWTServiceLookup; import org.rhq.coregui.client.inventory.groups.wizard.AbstractGroupCreateWizard; import org.rhq.coregui.client.inventory.resource.type.ResourceTypeRepository; import org.rhq.coregui.client.inventory.resource.type.ResourceTypeRepository.MetadataType; import org.rhq.coregui.client.inventory.resource.type.ResourceTypeRepository.TypesLoadedCallback; import org.rhq.coregui.client.util.FormUtility; 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 Jay Shaughnessy * */ public class GetDestinationStep extends AbstractWizardStep { private final BundleDeployWizard wizard; private VLayout form; DynamicForm valForm = new DynamicForm(); private SingleCompatibleResourceGroupSelector selector; private BundleDestination destination = new BundleDestination(); private boolean createInProgress = false; private RadioGroupItem destSpecItem; public GetDestinationStep(BundleDeployWizard wizard) { this.wizard = wizard; } public String getName() { return MSG.view_bundle_deployWizard_getDestStep(); } public Canvas getCanvas() { if (this.form == null) { this.form = new EnhancedVLayout(); this.valForm.setWidth100(); this.valForm.setNumCols(2); this.valForm.setColWidths("50%", "*"); final TextItem nameTextItem = new TextItem("name", MSG.view_bundle_deployWizard_getDest_name()); nameTextItem.setWidth(300); nameTextItem.setRequired(true); nameTextItem.addChangedHandler(new ChangedHandler() { public void onChanged(ChangedEvent event) { Object value = event.getValue(); if (value == null) { value = ""; } wizard.setSubtitle(value.toString()); destination.setName(value.toString()); } }); FormUtility.addContextualHelp(nameTextItem, MSG.view_bundle_deployWizard_getDest_name_help()); final TextAreaItem descriptionTextAreaItem = new TextAreaItem("description", MSG.view_bundle_deployWizard_getDest_desc()); descriptionTextAreaItem.setWidth(300); descriptionTextAreaItem.addChangedHandler(new ChangedHandler() { public void onChanged(ChangedEvent event) { Object value = event.getValue(); if (value == null) { value = ""; } destination.setDescription(value.toString()); } }); final TextItem deployDirTextItem = new TextItem("deployDir", MSG.view_bundle_deployWizard_getDest_deployDir()); deployDirTextItem.setWidth(300); deployDirTextItem.setRequired(false); deployDirTextItem.addChangedHandler(new ChangedHandler() { public void onChanged(ChangedEvent event) { Object value = event.getValue(); if (value != null) { destination.setDeployDir(value.toString()); } } }); FormUtility.addContextualHelp(deployDirTextItem, MSG.view_bundle_deployWizard_getDest_deployDir_help()); this.destSpecItem = new RadioGroupItem("destSpec", MSG.view_bundle_deployWizard_getDest_destBaseDirName()); this.destSpecItem.setWidth(300); this.destSpecItem.setRequired(true); this.destSpecItem.setDisabled(true); this.destSpecItem.addChangedHandler(new ChangedHandler() { public void onChanged(ChangedEvent event) { Object value = event.getValue(); if (value != null && value.toString().length() > 0) { destination.setDestinationSpecificationName(value.toString()); } else { destination.setDestinationSpecificationName(null); } } }); this.selector = new SingleCompatibleResourceGroupSelector("group", MSG.common_title_resource_group(), wizard.getBundle().getBundleType().getName()); this.selector.setWidth(300); this.selector.setRequired(true); Validator validator = new IsIntegerValidator(); validator.setErrorMessage(MSG.view_bundle_deployWizard_error_8()); this.selector.setValidators(validator); // BZ 1134486 - for some reason, we are getting spurious "null" change events in certain circumstances - ignore them this.selector.addChangeHandler(new ChangeHandler() { @Override public void onChange(ChangeEvent event) { if (event.getValue() == null) { if (event.getOldValue() != null) { event.cancel(); if (event.getOldValue() instanceof Integer) { groupSelectionChanged((Integer) event.getOldValue()); } } } } }); this.selector.addChangedHandler(new ChangedHandler() { @Override public void onChanged(ChangedEvent event) { // if the user is typing in the name of the group, and is only partially // done, the event value will be the String of the partial group name. // If the selection is an actual group name, the event value will be // an integer (the group ID) and that is our indication that the selection // of an actual group has been made Object eventValue = event.getValue(); Integer selectedGroupId = null; if (eventValue instanceof Integer) { selectedGroupId = (Integer) eventValue; } groupSelectionChanged(selectedGroupId); } }); final FormItemIcon newGroupIcon = new FormItemIcon(); newGroupIcon.setSrc("[SKIN]/actions/add.png"); this.selector.addIconClickHandler(new IconClickHandler() { public void onIconClick(IconClickEvent event) { if (event.getIcon().equals(newGroupIcon)) { new QuickGroupCreateWizard(selector).startWizard(); } } }); FormUtility.addContextualHelp(this.selector, MSG.view_bundle_deployWizard_getDest_group_help(), newGroupIcon); this.valForm.setItems(nameTextItem, descriptionTextAreaItem, this.selector, this.destSpecItem, deployDirTextItem); CanvasItem ci1 = new CanvasItem(); ci1.setShowTitle(false); ci1.setCanvas(valForm); ci1.setDisabled(true); this.form.addMember(this.valForm); } return this.form; } public boolean nextPage() { if (!valForm.validate() || createInProgress) { return false; } // protect against multiple calls to create if the user clicks Next multiple times. createInProgress = true; // protect against re-execution of this step via the "Previous" button. If we had created // a dest previously it must be deleted before we try to create a new one. if (wizard.isNewDestination() && (null != wizard.getDestination())) { GWTServiceLookup.getBundleService().deleteBundleDestination(wizard.getDestination().getId(), // new AsyncCallback<Void>() { public void onSuccess(Void voidReturn) { createDestination(); } public void onFailure(Throwable caught) { CoreGUI.getErrorHandler().handleError(MSG.view_bundle_deployWizard_error_9(), caught); // try anyway and potentially fail again from there createDestination(); } }); } else { createDestination(); } return false; } // this will advance or decrement the step depending on creation success or failure private void createDestination() { int selectedGroup = (Integer) this.valForm.getValue("group"); GWTServiceLookup.getBundleService().createBundleDestination(wizard.getBundleId(), destination.getName(), destination.getDescription(), destination.getDestinationSpecificationName(), destination.getDeployDir(), selectedGroup, // new AsyncCallback<BundleDestination>() { public void onSuccess(BundleDestination result) { wizard.setDestination(result); wizard.setNewDestination(true); CoreGUI.getMessageCenter().notify( new Message(MSG.view_bundle_deployWizard_destinationCreatedDetail_concise(result.getName()), MSG.view_bundle_deployWizard_destinationCreatedDetail(result.getName(), result.getDescription()), Severity.Info)); createInProgress = false; wizard.getView().incrementStep(); } public void onFailure(Throwable caught) { String message = MSG.view_bundle_deployWizard_error_10(); wizard.getView().showMessage(message); CoreGUI.getErrorHandler().handleError(message, caught); createInProgress = false; wizard.getView().decrementStep(); } }); } private void groupSelectionChanged(Integer selectedGroupId) { // new group is, or is in the process of being, selected so forget what the base location was before destination.setDestinationSpecificationName(null); destSpecItem.clearValue(); destSpecItem.setValueMap((String[]) null); // BZ 1134486 - for some reason, the selector gets new null events if it keeps focus - blur it to avoid this bug if (selectedGroupId != null) { selector.blurItem(); } // this will be null if there is no true group actually selected (e.g. user is typing a partial name to search) if (selectedGroupId != null) { GWTServiceLookup.getBundleService().getResourceTypeBundleConfiguration(selectedGroupId.intValue(), new AsyncCallback<ResourceTypeBundleConfiguration>() { public void onSuccess(ResourceTypeBundleConfiguration result) { // populate the base location drop down with all the possible dest base directories LinkedHashMap<String, String> menuItems = null; if (result != null) { Set<BundleDestinationSpecification> destSpecs; destSpecs = result.getAcceptableBundleDestinationSpecifications( wizard.getBundle().getBundleType().getName()); if (destSpecs != null && destSpecs.size() > 0) { String defaultSelectedItem = null; menuItems = new LinkedHashMap<String, String>(destSpecs.size()); for (BundleDestinationSpecification spec : destSpecs) { if (spec.getDescription() != null) { menuItems.put(spec.getName(), "<b>" + spec.getName() + "</b>: " + spec.getDescription()); } else { menuItems.put(spec.getName(), spec.getName()); } if (defaultSelectedItem == null) { defaultSelectedItem = spec.getName(); } } destSpecItem.setValueMap(menuItems); destSpecItem.setValue(defaultSelectedItem); destination.setDestinationSpecificationName(defaultSelectedItem); } } destSpecItem.setDisabled(menuItems == null); } public void onFailure(Throwable caught) { destSpecItem.setDisabled(true); CoreGUI.getErrorHandler().handleError(MSG.view_bundle_deployWizard_error_noBundleConfig(), caught); } }); } else { destSpecItem.setDisabled(true); } } private class QuickGroupCreateWizard extends AbstractGroupCreateWizard { private SingleCompatibleResourceGroupSelector groupSelector; private HandlerRegistration handlerRegistrar; public QuickGroupCreateWizard(SingleCompatibleResourceGroupSelector theSelector) { super(); this.groupSelector = theSelector; } @Override public boolean createGroup() { Integer[] ids = memberStep.getSelecterResourceTypeIds(); if (ids == null || ids.length == 0) { SC.warn(MSG.view_bundle_deployWizard_createGroup_error_1()); return false; } // BZ 1069793 - We must get these first, before the async call is made. // Otherwise, the group wizard (and this data) will get destroyed before we have a change to get it. final ResourceGroup group = createStep.getGroup(); final int[] selectedResourceIds = memberStep.getSelectedResourceIds(); ResourceTypeRepository typeRepository = ResourceTypeRepository.Cache.getInstance(); typeRepository.getResourceTypes(ids, EnumSet.of(MetadataType.bundleConfiguration), new TypesLoadedCallback() { public void onTypesLoaded(Map<Integer, ResourceType> types) { Set<ResourceType> typeSet = new HashSet<ResourceType>(types.values()); if (typeSet.size() != 1) { SC.warn(MSG.view_bundle_deployWizard_createGroup_error_2()); } else if (typeSet.iterator().next().getResourceTypeBundleConfiguration() == null) { SC.warn(MSG.view_bundle_deployWizard_createGroup_error_3()); } else { QuickGroupCreateWizard.super.createGroup(group, selectedResourceIds); } } }); return true; } @Override public void groupCreateCallback(final ResourceGroup group) { // note: "group" is essentially a flyweight - it doesn't have much other than ID this.groupSelector.setValue(group.getId()); this.handlerRegistrar = this.groupSelector.addDataArrivedHandler(new DataArrivedHandler() { public void onDataArrived(DataArrivedEvent event) { handlerRegistrar.removeHandler(); // this handler is only needed once, when group wizard is finished with and we created our group if (groupSelector.getSelectedRecord() == null) { // it appears that the user created a group that cannot be a bundle target. groupSelector.clearValue(); groupSelectionChanged(null); } else { groupSelectionChanged(group.getId()); } } }); // order is important - we set the value first above, add dataArrivedHandler, then fetch, which triggers our handler this.groupSelector.fetchData(); } } }