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 hosts = new ArrayList(); public List metaHosts = new ArrayList(); public List oneTimeHosts = new ArrayList(); } public interface Display { public HasText getHostnameField(); public HasValue 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 selectedHostData = new ArrayDataSource(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 objects) { for (JSONObject row : objects) { selectRow(row); } selectionRefresh(); } public void onRemove(Collection 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 hosts = Utils.splitListWithSpaces(display.getHostnameField().getText()); boolean allowOneTimeHosts = display.getAllowOneTimeHostsField().getValue(); setSelectedHostnames(hosts, allowOneTimeHosts); } public void setSelectedHostnames(final List 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 data) { processAddByHostname(hosts, data, allowOneTimeHosts); } }); } private List findOneTimeHosts(List requestedHostnames, List foundHosts) { Set existingHosts = new HashSet(); for (JSONObject host : foundHosts) { existingHosts.add(Utils.jsonToString(host.get("hostname"))); } List oneTimeHostnames = new ArrayList(); for (String hostname : requestedHostnames) { if (!existingHosts.contains(hostname)) { oneTimeHostnames.add(hostname); } } return oneTimeHostnames; } private void processAddByHostname(final List requestedHostnames, List foundHosts, boolean allowOneTimeHosts) { List 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(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 hostnames = new ArrayList(); 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); } }