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 availableSelection = availableDecorator.addSelectionManager(false); 97 availableDecorator.addSelectionPanel(true); 98 99 availableTable.addListener(new DynamicTableListener() { 100 public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) { 101 availableSelection.toggleSelected(row); 102 } 103 104 public void onTableRefreshed() {} 105 }); 106 107 availableSelection.addListener(new SelectionListener() { 108 public void onAdd(Collection<JSONObject> objects) { 109 for (JSONObject row : objects) { 110 selectRow(row); 111 } 112 selectionRefresh(); 113 } 114 115 public void onRemove(Collection<JSONObject> objects) { 116 for (JSONObject row : objects) { 117 deselectRow(row); 118 } 119 selectionRefresh(); 120 } 121 }); 122 123 selectedTable.addListener(new DynamicTableListener() { 124 public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) { 125 if (isMetaEntry(row) || isOneTimeHost(row)) { 126 deselectRow(row); 127 selectionRefresh(); 128 } else { 129 availableSelection.deselectObject(row); 130 } 131 } 132 133 public void onTableRefreshed() {} 134 }); 135 } 136 bindDisplay(Display display)137 public void bindDisplay(Display display) { 138 this.display = display; 139 display.getAddByHostnameButton().addClickHandler(this); 140 display.getAddByLabelButton().addClickHandler(this); 141 display.addTables(availableDecorator, selectedDecorator); 142 143 populateLabels(display.getLabelList()); 144 } 145 146 @Override onClick(ClickEvent event)147 public void onClick(ClickEvent event) { 148 if (event.getSource() == display.getAddByLabelButton()) { 149 onAddByLabel(); 150 } else if (event.getSource() == display.getAddByHostnameButton()) { 151 onAddByHostname(); 152 } 153 } 154 onAddByHostname()155 private void onAddByHostname() { 156 List<String> hosts = Utils.splitListWithSpaces(display.getHostnameField().getText()); 157 boolean allowOneTimeHosts = display.getAllowOneTimeHostsField().getValue(); 158 setSelectedHostnames(hosts, allowOneTimeHosts); 159 } 160 setSelectedHostnames(final List<String> hosts, final boolean allowOneTimeHosts)161 public void setSelectedHostnames(final List<String> hosts, final boolean allowOneTimeHosts) { 162 // figure out which hosts exist in the system and which should be one-time hosts 163 JSONObject params = new JSONObject(); 164 params.put("hostname__in", Utils.stringsToJSON(hosts)); 165 hostDataSource.query(params, new DefaultDataCallback () { 166 @Override 167 public void onQueryReady(Query query) { 168 query.getPage(null, null, null, this); 169 } 170 171 @Override 172 public void handlePage(List<JSONObject> data) { 173 processAddByHostname(hosts, data, allowOneTimeHosts); 174 } 175 }); 176 } 177 findOneTimeHosts(List<String> requestedHostnames, List<JSONObject> foundHosts)178 private List<String> findOneTimeHosts(List<String> requestedHostnames, 179 List<JSONObject> foundHosts) { 180 Set<String> existingHosts = new HashSet<String>(); 181 for (JSONObject host : foundHosts) { 182 existingHosts.add(Utils.jsonToString(host.get("hostname"))); 183 } 184 185 List<String> oneTimeHostnames = new ArrayList<String>(); 186 for (String hostname : requestedHostnames) { 187 if (!existingHosts.contains(hostname)) { 188 oneTimeHostnames.add(hostname); 189 } 190 } 191 192 return oneTimeHostnames; 193 } 194 processAddByHostname(final List<String> requestedHostnames, List<JSONObject> foundHosts, boolean allowOneTimeHosts)195 private void processAddByHostname(final List<String> requestedHostnames, 196 List<JSONObject> foundHosts, 197 boolean allowOneTimeHosts) { 198 List<String> oneTimeHostnames = findOneTimeHosts(requestedHostnames, foundHosts); 199 if (!allowOneTimeHosts && !oneTimeHostnames.isEmpty()) { 200 NotifyManager.getInstance().showError("Hosts not found: " + 201 Utils.joinStrings(", ", oneTimeHostnames)); 202 return; 203 } 204 205 // deselect existing non-metahost hosts 206 // iterate over copy to allow modification 207 for (JSONObject host : new ArrayList<JSONObject>(selectedHostData.getItems())) { 208 if (isOneTimeHost(host)) { 209 selectedHostData.removeItem(host); 210 } 211 } 212 availableSelection.deselectAll(); 213 214 // add one-time hosts 215 for (String hostname : oneTimeHostnames) { 216 JSONObject oneTimeObject = new JSONObject(); 217 oneTimeObject.put("hostname", new JSONString(hostname)); 218 oneTimeObject.put("platform", new JSONString(ONE_TIME)); 219 selectRow(oneTimeObject); 220 } 221 222 // add existing hosts 223 availableSelection.selectObjects(foundHosts); // this refreshes the selection 224 } 225 onAddByLabel()226 private void onAddByLabel() { 227 SimplifiedList labelList = display.getLabelList(); 228 String labelName = labelList.getSelectedName(); 229 String label = AfeUtils.decodeLabelName(labelName); 230 String number = display.getLabelNumberField().getText(); 231 try { 232 Integer.parseInt(number); 233 } 234 catch (NumberFormatException exc) { 235 String error = "Invalid number " + number; 236 NotifyManager.getInstance().showError(error); 237 return; 238 } 239 240 addMetaHosts(label, number); 241 selectionRefresh(); 242 } 243 addMetaHosts(String label, String number)244 public void addMetaHosts(String label, String number) { 245 JSONObject metaObject = new JSONObject(); 246 metaObject.put("hostname", new JSONString(META_PREFIX + number)); 247 metaObject.put("platform", new JSONString(label)); 248 metaObject.put("labels", new JSONArray()); 249 metaObject.put("status", new JSONString("")); 250 metaObject.put("locked", new JSONNumber(0)); 251 selectRow(metaObject); 252 } 253 selectRow(JSONObject row)254 private void selectRow(JSONObject row) { 255 selectedHostData.addItem(row); 256 } 257 deselectRow(JSONObject row)258 private void deselectRow(JSONObject row) { 259 selectedHostData.removeItem(row); 260 } 261 deselectAll()262 private void deselectAll() { 263 availableSelection.deselectAll(); 264 // get rid of leftover meta-host entries 265 selectedHostData.clear(); 266 selectionRefresh(); 267 } 268 populateLabels(SimplifiedList list)269 private void populateLabels(SimplifiedList list) { 270 String[] labelNames = AfeUtils.getLabelStrings(); 271 for (String labelName : labelNames) { 272 list.addItem(labelName, ""); 273 } 274 } 275 getHostname(JSONObject row)276 private String getHostname(JSONObject row) { 277 return row.get("hostname").isString().stringValue(); 278 } 279 isMetaEntry(JSONObject row)280 private boolean isMetaEntry(JSONObject row) { 281 return getHostname(row).startsWith(META_PREFIX); 282 } 283 getMetaNumber(JSONObject row)284 private int getMetaNumber(JSONObject row) { 285 return Integer.parseInt(getHostname(row).substring(META_PREFIX.length())); 286 } 287 isOneTimeHost(JSONObject row)288 private boolean isOneTimeHost(JSONObject row) { 289 JSONString platform = row.get("platform").isString(); 290 if (platform == null) { 291 return false; 292 } 293 return platform.stringValue().equals(ONE_TIME); 294 } 295 296 /** 297 * Retrieve the set of selected hosts. 298 */ getSelectedHosts()299 public HostSelection getSelectedHosts() { 300 HostSelection selection = new HostSelection(); 301 if (!enabled) { 302 return selection; 303 } 304 305 for (JSONObject row : selectedHostData.getItems() ) { 306 if (isMetaEntry(row)) { 307 int count = getMetaNumber(row); 308 String platform = row.get("platform").isString().stringValue(); 309 for(int counter = 0; counter < count; counter++) { 310 selection.metaHosts.add(platform); 311 } 312 } 313 else { 314 String hostname = getHostname(row); 315 if (isOneTimeHost(row)) { 316 selection.oneTimeHosts.add(hostname); 317 } else { 318 selection.hosts.add(hostname); 319 } 320 } 321 } 322 323 return selection; 324 } 325 326 /** 327 * Reset the widget (deselect all hosts). 328 */ reset()329 public void reset() { 330 deselectAll(); 331 selectionRefresh(); 332 setEnabled(true); 333 } 334 335 /** 336 * Refresh as necessary for selection change, but don't make any RPCs. 337 */ selectionRefresh()338 private void selectionRefresh() { 339 selectedTable.refresh(); 340 updateHostnameList(); 341 } 342 updateHostnameList()343 private void updateHostnameList() { 344 List<String> hostnames = new ArrayList<String>(); 345 for (JSONObject hostObject : selectedHostData.getItems()) { 346 if (!isMetaEntry(hostObject)) { 347 hostnames.add(Utils.jsonToString(hostObject.get("hostname"))); 348 } 349 } 350 351 String hostList = Utils.joinStrings(", ", hostnames); 352 display.getHostnameField().setText(hostList); 353 } 354 refresh()355 public void refresh() { 356 availableTable.refresh(); 357 selectionRefresh(); 358 } 359 setEnabled(boolean enabled)360 public void setEnabled(boolean enabled) { 361 this.enabled = enabled; 362 display.setVisible(enabled); 363 } 364 } 365