| package autotest.afe; |
| |
| import autotest.common.Utils; |
| import autotest.common.table.ArrayDataSource; |
| import autotest.common.table.DataSource.DefaultDataCallback; |
| import autotest.common.table.DataSource.Query; |
| import autotest.common.table.DynamicTable.DynamicTableListener; |
| import autotest.common.table.SelectionManager; |
| import autotest.common.table.SelectionManager.SelectionListener; |
| import autotest.common.table.TableDecorator; |
| import autotest.common.ui.NotifyManager; |
| import autotest.common.ui.SimplifiedList; |
| |
| import com.google.gwt.event.dom.client.ClickEvent; |
| import com.google.gwt.event.dom.client.ClickHandler; |
| import com.google.gwt.event.dom.client.HasClickHandlers; |
| import com.google.gwt.json.client.JSONArray; |
| import com.google.gwt.json.client.JSONNumber; |
| import com.google.gwt.json.client.JSONObject; |
| import com.google.gwt.json.client.JSONString; |
| import com.google.gwt.user.client.ui.Anchor; |
| import com.google.gwt.user.client.ui.HasText; |
| import com.google.gwt.user.client.ui.HasValue; |
| import com.google.gwt.user.client.ui.Widget; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * A widget to facilitate selection of a group of hosts for running a job. The |
| * widget displays two side-by-side tables; the left table is a normal |
| * {@link HostTable} displaying available, unselected hosts, and the right table |
| * displays selected hosts. Click on a host in either table moves it to the |
| * other (i.e. selects or deselects a host). The widget provides several |
| * convenience controls (such as one to remove all selected hosts) and a special |
| * section for adding meta-host entries. |
| */ |
| public class HostSelector implements ClickHandler { |
| private static final int TABLE_SIZE = 10; |
| public static final String META_PREFIX = "Any "; |
| public static final String ONE_TIME = "(one-time host)"; |
| |
| public static class HostSelection { |
| public List<String> hosts = new ArrayList<String>(); |
| public List<String> metaHosts = new ArrayList<String>(); |
| public List<String> oneTimeHosts = new ArrayList<String>(); |
| } |
| |
| public interface Display { |
| public HasText getHostnameField(); |
| public HasValue<Boolean> getAllowOneTimeHostsField(); |
| public HasClickHandlers getAddByHostnameButton(); |
| public SimplifiedList getLabelList(); |
| public HasText getLabelNumberField(); |
| public HasClickHandlers getAddByLabelButton(); |
| public void setVisible(boolean visible); |
| |
| // a temporary measure until the table code gets refactored to support Passive View |
| public void addTables(Widget availableTable, Widget selectedTable); |
| } |
| |
| private ArrayDataSource<JSONObject> selectedHostData = |
| new ArrayDataSource<JSONObject>(new String[] {"hostname"}); |
| |
| private Display display; |
| private HostDataSource hostDataSource = new HostDataSource(); |
| // availableTable needs its own data source |
| private HostTable availableTable = new HostTable(new HostDataSource()); |
| private HostTableDecorator availableDecorator = |
| new HostTableDecorator(availableTable, TABLE_SIZE); |
| private HostTable selectedTable = new HostTable(selectedHostData); |
| private TableDecorator selectedDecorator = new TableDecorator(selectedTable); |
| private boolean enabled = true; |
| |
| private SelectionManager availableSelection; |
| |
| public void initialize() { |
| selectedTable.setClickable(true); |
| selectedTable.setRowsPerPage(TABLE_SIZE); |
| selectedDecorator.addPaginators(); |
| |
| Anchor clearSelection = new Anchor("Clear selection"); |
| clearSelection.addClickHandler(new ClickHandler() { |
| public void onClick(ClickEvent event) { |
| deselectAll(); |
| } |
| }); |
| selectedDecorator.setActionsWidget(clearSelection); |
| |
| availableTable.setClickable(true); |
| availableDecorator.lockedFilter.setSelectedChoice("No"); |
| availableDecorator.aclFilter.setActive(true); |
| availableSelection = availableDecorator.addSelectionManager(false); |
| availableDecorator.addSelectionPanel(true); |
| |
| availableTable.addListener(new DynamicTableListener() { |
| public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) { |
| availableSelection.toggleSelected(row); |
| } |
| |
| public void onTableRefreshed() {} |
| }); |
| |
| availableSelection.addListener(new SelectionListener() { |
| public void onAdd(Collection<JSONObject> objects) { |
| for (JSONObject row : objects) { |
| selectRow(row); |
| } |
| selectionRefresh(); |
| } |
| |
| public void onRemove(Collection<JSONObject> objects) { |
| for (JSONObject row : objects) { |
| deselectRow(row); |
| } |
| selectionRefresh(); |
| } |
| }); |
| |
| selectedTable.addListener(new DynamicTableListener() { |
| public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) { |
| if (isMetaEntry(row) || isOneTimeHost(row)) { |
| deselectRow(row); |
| selectionRefresh(); |
| } else { |
| availableSelection.deselectObject(row); |
| } |
| } |
| |
| public void onTableRefreshed() {} |
| }); |
| } |
| |
| public void bindDisplay(Display display) { |
| this.display = display; |
| display.getAddByHostnameButton().addClickHandler(this); |
| display.getAddByLabelButton().addClickHandler(this); |
| display.addTables(availableDecorator, selectedDecorator); |
| |
| populateLabels(display.getLabelList()); |
| } |
| |
| @Override |
| public void onClick(ClickEvent event) { |
| if (event.getSource() == display.getAddByLabelButton()) { |
| onAddByLabel(); |
| } else if (event.getSource() == display.getAddByHostnameButton()) { |
| onAddByHostname(); |
| } |
| } |
| |
| private void onAddByHostname() { |
| List<String> hosts = Utils.splitListWithSpaces(display.getHostnameField().getText()); |
| boolean allowOneTimeHosts = display.getAllowOneTimeHostsField().getValue(); |
| setSelectedHostnames(hosts, allowOneTimeHosts); |
| } |
| |
| public void setSelectedHostnames(final List<String> hosts, final boolean allowOneTimeHosts) { |
| // figure out which hosts exist in the system and which should be one-time hosts |
| JSONObject params = new JSONObject(); |
| params.put("hostname__in", Utils.stringsToJSON(hosts)); |
| hostDataSource.query(params, new DefaultDataCallback () { |
| @Override |
| public void onQueryReady(Query query) { |
| query.getPage(null, null, null, this); |
| } |
| |
| @Override |
| public void handlePage(List<JSONObject> data) { |
| processAddByHostname(hosts, data, allowOneTimeHosts); |
| } |
| }); |
| } |
| |
| private List<String> findOneTimeHosts(List<String> requestedHostnames, |
| List<JSONObject> foundHosts) { |
| Set<String> existingHosts = new HashSet<String>(); |
| for (JSONObject host : foundHosts) { |
| existingHosts.add(Utils.jsonToString(host.get("hostname"))); |
| } |
| |
| List<String> oneTimeHostnames = new ArrayList<String>(); |
| for (String hostname : requestedHostnames) { |
| if (!existingHosts.contains(hostname)) { |
| oneTimeHostnames.add(hostname); |
| } |
| } |
| |
| return oneTimeHostnames; |
| } |
| |
| private void processAddByHostname(final List<String> requestedHostnames, |
| List<JSONObject> foundHosts, |
| boolean allowOneTimeHosts) { |
| List<String> oneTimeHostnames = findOneTimeHosts(requestedHostnames, foundHosts); |
| if (!allowOneTimeHosts && !oneTimeHostnames.isEmpty()) { |
| NotifyManager.getInstance().showError("Hosts not found: " + |
| Utils.joinStrings(", ", oneTimeHostnames)); |
| return; |
| } |
| |
| // deselect existing non-metahost hosts |
| // iterate over copy to allow modification |
| for (JSONObject host : new ArrayList<JSONObject>(selectedHostData.getItems())) { |
| if (isOneTimeHost(host)) { |
| selectedHostData.removeItem(host); |
| } |
| } |
| availableSelection.deselectAll(); |
| |
| // add one-time hosts |
| for (String hostname : oneTimeHostnames) { |
| JSONObject oneTimeObject = new JSONObject(); |
| oneTimeObject.put("hostname", new JSONString(hostname)); |
| oneTimeObject.put("platform", new JSONString(ONE_TIME)); |
| selectRow(oneTimeObject); |
| } |
| |
| // add existing hosts |
| availableSelection.selectObjects(foundHosts); // this refreshes the selection |
| } |
| |
| private void onAddByLabel() { |
| SimplifiedList labelList = display.getLabelList(); |
| String labelName = labelList.getSelectedName(); |
| String label = AfeUtils.decodeLabelName(labelName); |
| String number = display.getLabelNumberField().getText(); |
| try { |
| Integer.parseInt(number); |
| } |
| catch (NumberFormatException exc) { |
| String error = "Invalid number " + number; |
| NotifyManager.getInstance().showError(error); |
| return; |
| } |
| |
| addMetaHosts(label, number); |
| selectionRefresh(); |
| } |
| |
| public void addMetaHosts(String label, String number) { |
| JSONObject metaObject = new JSONObject(); |
| metaObject.put("hostname", new JSONString(META_PREFIX + number)); |
| metaObject.put("platform", new JSONString(label)); |
| metaObject.put("labels", new JSONArray()); |
| metaObject.put("status", new JSONString("")); |
| metaObject.put("locked", new JSONNumber(0)); |
| selectRow(metaObject); |
| } |
| |
| private void selectRow(JSONObject row) { |
| selectedHostData.addItem(row); |
| } |
| |
| private void deselectRow(JSONObject row) { |
| selectedHostData.removeItem(row); |
| } |
| |
| private void deselectAll() { |
| availableSelection.deselectAll(); |
| // get rid of leftover meta-host entries |
| selectedHostData.clear(); |
| selectionRefresh(); |
| } |
| |
| private void populateLabels(SimplifiedList list) { |
| String[] labelNames = AfeUtils.getLabelStrings(); |
| for (String labelName : labelNames) { |
| list.addItem(labelName, ""); |
| } |
| } |
| |
| private String getHostname(JSONObject row) { |
| return row.get("hostname").isString().stringValue(); |
| } |
| |
| private boolean isMetaEntry(JSONObject row) { |
| return getHostname(row).startsWith(META_PREFIX); |
| } |
| |
| private int getMetaNumber(JSONObject row) { |
| return Integer.parseInt(getHostname(row).substring(META_PREFIX.length())); |
| } |
| |
| private boolean isOneTimeHost(JSONObject row) { |
| JSONString platform = row.get("platform").isString(); |
| if (platform == null) { |
| return false; |
| } |
| return platform.stringValue().equals(ONE_TIME); |
| } |
| |
| /** |
| * Retrieve the set of selected hosts. |
| */ |
| public HostSelection getSelectedHosts() { |
| HostSelection selection = new HostSelection(); |
| if (!enabled) { |
| return selection; |
| } |
| |
| for (JSONObject row : selectedHostData.getItems() ) { |
| if (isMetaEntry(row)) { |
| int count = getMetaNumber(row); |
| String platform = row.get("platform").isString().stringValue(); |
| for(int counter = 0; counter < count; counter++) { |
| selection.metaHosts.add(platform); |
| } |
| } |
| else { |
| String hostname = getHostname(row); |
| if (isOneTimeHost(row)) { |
| selection.oneTimeHosts.add(hostname); |
| } else { |
| selection.hosts.add(hostname); |
| } |
| } |
| } |
| |
| return selection; |
| } |
| |
| /** |
| * Reset the widget (deselect all hosts). |
| */ |
| public void reset() { |
| deselectAll(); |
| selectionRefresh(); |
| setEnabled(true); |
| } |
| |
| /** |
| * Refresh as necessary for selection change, but don't make any RPCs. |
| */ |
| private void selectionRefresh() { |
| selectedTable.refresh(); |
| updateHostnameList(); |
| } |
| |
| private void updateHostnameList() { |
| List<String> hostnames = new ArrayList<String>(); |
| for (JSONObject hostObject : selectedHostData.getItems()) { |
| if (!isMetaEntry(hostObject)) { |
| hostnames.add(Utils.jsonToString(hostObject.get("hostname"))); |
| } |
| } |
| |
| String hostList = Utils.joinStrings(", ", hostnames); |
| display.getHostnameField().setText(hostList); |
| } |
| |
| public void refresh() { |
| availableTable.refresh(); |
| selectionRefresh(); |
| } |
| |
| public void setEnabled(boolean enabled) { |
| this.enabled = enabled; |
| display.setVisible(enabled); |
| } |
| } |