1 package autotest.afe; 2 3 import autotest.common.Utils; 4 import autotest.common.table.ArrayDataSource; 5 import autotest.common.table.DataSource.DefaultDataCallback; 6 import autotest.common.table.DataSource.Query; 7 import autotest.common.table.DynamicTable.DynamicTableListener; 8 import autotest.common.table.SelectionManager; 9 import autotest.common.table.SelectionManager.SelectionListener; 10 import autotest.common.table.TableDecorator; 11 import autotest.common.ui.NotifyManager; 12 import autotest.common.ui.SimplifiedList; 13 14 import com.google.gwt.event.dom.client.ClickEvent; 15 import com.google.gwt.event.dom.client.ClickHandler; 16 import com.google.gwt.event.dom.client.HasClickHandlers; 17 import com.google.gwt.json.client.JSONArray; 18 import com.google.gwt.json.client.JSONNumber; 19 import com.google.gwt.json.client.JSONObject; 20 import com.google.gwt.json.client.JSONString; 21 import com.google.gwt.user.client.ui.Anchor; 22 import com.google.gwt.user.client.ui.HasText; 23 import com.google.gwt.user.client.ui.HasValue; 24 import com.google.gwt.user.client.ui.Widget; 25 26 import java.util.ArrayList; 27 import java.util.Collection; 28 import java.util.HashSet; 29 import java.util.List; 30 import java.util.Set; 31 32 /** 33 * A widget to facilitate selection of a group of hosts for running a job. The 34 * widget displays two side-by-side tables; the left table is a normal 35 * {@link HostTable} displaying available, unselected hosts, and the right table 36 * displays selected hosts. Click on a host in either table moves it to the 37 * other (i.e. selects or deselects a host). The widget provides several 38 * convenience controls (such as one to remove all selected hosts) and a special 39 * section for adding meta-host entries. 40 */ 41 public class HostSelector implements ClickHandler { 42 private static final int TABLE_SIZE = 10; 43 public static final String META_PREFIX = "Any "; 44 public static final String ONE_TIME = "(one-time host)"; 45 46 public static class HostSelection { 47 public List<String> hosts = new ArrayList<String>(); 48 public List<String> metaHosts = new ArrayList<String>(); 49 public List<String> oneTimeHosts = new ArrayList<String>(); 50 } 51 52 public interface Display { getHostnameField()53 public HasText getHostnameField(); getAllowOneTimeHostsField()54 public HasValue<Boolean> getAllowOneTimeHostsField(); getAddByHostnameButton()55 public HasClickHandlers getAddByHostnameButton(); getLabelList()56 public SimplifiedList getLabelList(); getLabelNumberField()57 public HasText getLabelNumberField(); getAddByLabelButton()58 public HasClickHandlers getAddByLabelButton(); setVisible(boolean visible)59 public void setVisible(boolean visible); 60 61 // a temporary measure until the table code gets refactored to support Passive View addTables(Widget availableTable, Widget selectedTable)62 public void addTables(Widget availableTable, Widget selectedTable); 63 } 64 65 private ArrayDataSource<JSONObject> selectedHostData = 66 new ArrayDataSource<JSONObject>(new String[] {"hostname"}); 67 68 private Display display; 69 private HostDataSource hostDataSource = new HostDataSource(); 70 // availableTable needs its own data source 71 private HostTable availableTable = new HostTable(new HostDataSource()); 72 private HostTableDecorator availableDecorator = 73 new HostTableDecorator(availableTable, TABLE_SIZE); 74 private HostTable selectedTable = new HostTable(selectedHostData); 75 private TableDecorator selectedDecorator = new TableDecorator(selectedTable); 76 private boolean enabled = true; 77 78 private SelectionManager availableSelection; 79 initialize()80 public void initialize() { 81 selectedTable.setClickable(true); 82 selectedTable.setRowsPerPage(TABLE_SIZE); 83 selectedDecorator.addPaginators(); 84 85 Anchor clearSelection = new Anchor("Clear selection"); 86 clearSelection.addClickHandler(new ClickHandler() { 87 public void onClick(ClickEvent event) { 88 deselectAll(); 89 } 90 }); 91 selectedDecorator.setActionsWidget(clearSelection); 92 93 availableTable.setClickable(true); 94 availableDecorator.lockedFilter.setSelectedChoice("No"); 95 availableDecorator.aclFilter.setActive(true); 96 availableDecorator.excludeAtomicGroupsFilter.setActive(true); 97 availableSelection = availableDecorator.addSelectionManager(false); 98 availableDecorator.addSelectionPanel(true); 99 100 availableTable.addListener(new DynamicTableListener() { 101 public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) { 102 availableSelection.toggleSelected(row); 103 } 104 105 public void onTableRefreshed() {} 106 }); 107 108 availableSelection.addListener(new SelectionListener() { 109 public void onAdd(Collection<JSONObject> objects) { 110 for (JSONObject row : objects) { 111 selectRow(row); 112 } 113 selectionRefresh(); 114 } 115 116 public void onRemove(Collection<JSONObject> objects) { 117 for (JSONObject row : objects) { 118 deselectRow(row); 119 } 120 selectionRefresh(); 121 } 122 }); 123 124 selectedTable.addListener(new DynamicTableListener() { 125 public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) { 126 if (isMetaEntry(row) || isOneTimeHost(row)) { 127 deselectRow(row); 128 selectionRefresh(); 129 } else { 130 availableSelection.deselectObject(row); 131 } 132 } 133 134 public void onTableRefreshed() {} 135 }); 136 } 137 bindDisplay(Display display)138 public void bindDisplay(Display display) { 139 this.display = display; 140 display.getAddByHostnameButton().addClickHandler(this); 141 display.getAddByLabelButton().addClickHandler(this); 142 display.addTables(availableDecorator, selectedDecorator); 143 144 populateLabels(display.getLabelList()); 145 } 146 147 @Override onClick(ClickEvent event)148 public void onClick(ClickEvent event) { 149 if (event.getSource() == display.getAddByLabelButton()) { 150 onAddByLabel(); 151 } else if (event.getSource() == display.getAddByHostnameButton()) { 152 onAddByHostname(); 153 } 154 } 155 onAddByHostname()156 private void onAddByHostname() { 157 List<String> hosts = Utils.splitListWithSpaces(display.getHostnameField().getText()); 158 boolean allowOneTimeHosts = display.getAllowOneTimeHostsField().getValue(); 159 setSelectedHostnames(hosts, allowOneTimeHosts); 160 } 161 setSelectedHostnames(final List<String> hosts, final boolean allowOneTimeHosts)162 public void setSelectedHostnames(final List<String> hosts, final boolean allowOneTimeHosts) { 163 // figure out which hosts exist in the system and which should be one-time hosts 164 JSONObject params = new JSONObject(); 165 params.put("hostname__in", Utils.stringsToJSON(hosts)); 166 hostDataSource.query(params, new DefaultDataCallback () { 167 @Override 168 public void onQueryReady(Query query) { 169 query.getPage(null, null, null, this); 170 } 171 172 @Override 173 public void handlePage(List<JSONObject> data) { 174 processAddByHostname(hosts, data, allowOneTimeHosts); 175 } 176 }); 177 } 178 findOneTimeHosts(List<String> requestedHostnames, List<JSONObject> foundHosts)179 private List<String> findOneTimeHosts(List<String> requestedHostnames, 180 List<JSONObject> foundHosts) { 181 Set<String> existingHosts = new HashSet<String>(); 182 for (JSONObject host : foundHosts) { 183 existingHosts.add(Utils.jsonToString(host.get("hostname"))); 184 } 185 186 List<String> oneTimeHostnames = new ArrayList<String>(); 187 for (String hostname : requestedHostnames) { 188 if (!existingHosts.contains(hostname)) { 189 oneTimeHostnames.add(hostname); 190 } 191 } 192 193 return oneTimeHostnames; 194 } 195 processAddByHostname(final List<String> requestedHostnames, List<JSONObject> foundHosts, boolean allowOneTimeHosts)196 private void processAddByHostname(final List<String> requestedHostnames, 197 List<JSONObject> foundHosts, 198 boolean allowOneTimeHosts) { 199 List<String> oneTimeHostnames = findOneTimeHosts(requestedHostnames, foundHosts); 200 if (!allowOneTimeHosts && !oneTimeHostnames.isEmpty()) { 201 NotifyManager.getInstance().showError("Hosts not found: " + 202 Utils.joinStrings(", ", oneTimeHostnames)); 203 return; 204 } 205 206 // deselect existing non-metahost hosts 207 // iterate over copy to allow modification 208 for (JSONObject host : new ArrayList<JSONObject>(selectedHostData.getItems())) { 209 if (isOneTimeHost(host)) { 210 selectedHostData.removeItem(host); 211 } 212 } 213 availableSelection.deselectAll(); 214 215 // add one-time hosts 216 for (String hostname : oneTimeHostnames) { 217 JSONObject oneTimeObject = new JSONObject(); 218 oneTimeObject.put("hostname", new JSONString(hostname)); 219 oneTimeObject.put("platform", new JSONString(ONE_TIME)); 220 selectRow(oneTimeObject); 221 } 222 223 // add existing hosts 224 availableSelection.selectObjects(foundHosts); // this refreshes the selection 225 } 226 onAddByLabel()227 private void onAddByLabel() { 228 SimplifiedList labelList = display.getLabelList(); 229 String labelName = labelList.getSelectedName(); 230 String label = AfeUtils.decodeLabelName(labelName); 231 String number = display.getLabelNumberField().getText(); 232 try { 233 Integer.parseInt(number); 234 } 235 catch (NumberFormatException exc) { 236 String error = "Invalid number " + number; 237 NotifyManager.getInstance().showError(error); 238 return; 239 } 240 241 addMetaHosts(label, number); 242 selectionRefresh(); 243 } 244 addMetaHosts(String label, String number)245 public void addMetaHosts(String label, String number) { 246 JSONObject metaObject = new JSONObject(); 247 metaObject.put("hostname", new JSONString(META_PREFIX + number)); 248 metaObject.put("platform", new JSONString(label)); 249 metaObject.put("labels", new JSONArray()); 250 metaObject.put("status", new JSONString("")); 251 metaObject.put("locked", new JSONNumber(0)); 252 selectRow(metaObject); 253 } 254 selectRow(JSONObject row)255 private void selectRow(JSONObject row) { 256 selectedHostData.addItem(row); 257 } 258 deselectRow(JSONObject row)259 private void deselectRow(JSONObject row) { 260 selectedHostData.removeItem(row); 261 } 262 deselectAll()263 private void deselectAll() { 264 availableSelection.deselectAll(); 265 // get rid of leftover meta-host entries 266 selectedHostData.clear(); 267 selectionRefresh(); 268 } 269 populateLabels(SimplifiedList list)270 private void populateLabels(SimplifiedList list) { 271 String[] labelNames = AfeUtils.getLabelStrings(); 272 for (String labelName : labelNames) { 273 list.addItem(labelName, ""); 274 } 275 } 276 getHostname(JSONObject row)277 private String getHostname(JSONObject row) { 278 return row.get("hostname").isString().stringValue(); 279 } 280 isMetaEntry(JSONObject row)281 private boolean isMetaEntry(JSONObject row) { 282 return getHostname(row).startsWith(META_PREFIX); 283 } 284 getMetaNumber(JSONObject row)285 private int getMetaNumber(JSONObject row) { 286 return Integer.parseInt(getHostname(row).substring(META_PREFIX.length())); 287 } 288 isOneTimeHost(JSONObject row)289 private boolean isOneTimeHost(JSONObject row) { 290 JSONString platform = row.get("platform").isString(); 291 if (platform == null) { 292 return false; 293 } 294 return platform.stringValue().equals(ONE_TIME); 295 } 296 297 /** 298 * Retrieve the set of selected hosts. 299 */ getSelectedHosts()300 public HostSelection getSelectedHosts() { 301 HostSelection selection = new HostSelection(); 302 if (!enabled) { 303 return selection; 304 } 305 306 for (JSONObject row : selectedHostData.getItems() ) { 307 if (isMetaEntry(row)) { 308 int count = getMetaNumber(row); 309 String platform = row.get("platform").isString().stringValue(); 310 for(int counter = 0; counter < count; counter++) { 311 selection.metaHosts.add(platform); 312 } 313 } 314 else { 315 String hostname = getHostname(row); 316 if (isOneTimeHost(row)) { 317 selection.oneTimeHosts.add(hostname); 318 } else { 319 selection.hosts.add(hostname); 320 } 321 } 322 } 323 324 return selection; 325 } 326 327 /** 328 * Reset the widget (deselect all hosts). 329 */ reset()330 public void reset() { 331 deselectAll(); 332 selectionRefresh(); 333 setEnabled(true); 334 } 335 336 /** 337 * Refresh as necessary for selection change, but don't make any RPCs. 338 */ selectionRefresh()339 private void selectionRefresh() { 340 selectedTable.refresh(); 341 updateHostnameList(); 342 } 343 updateHostnameList()344 private void updateHostnameList() { 345 List<String> hostnames = new ArrayList<String>(); 346 for (JSONObject hostObject : selectedHostData.getItems()) { 347 if (!isMetaEntry(hostObject)) { 348 hostnames.add(Utils.jsonToString(hostObject.get("hostname"))); 349 } 350 } 351 352 String hostList = Utils.joinStrings(", ", hostnames); 353 display.getHostnameField().setText(hostList); 354 } 355 refresh()356 public void refresh() { 357 availableTable.refresh(); 358 selectionRefresh(); 359 } 360 setEnabled(boolean enabled)361 public void setEnabled(boolean enabled) { 362 this.enabled = enabled; 363 display.setVisible(enabled); 364 } 365 } 366