• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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