• 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         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