Home | History | Annotate | Download | only in afe
      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 {
     53         public HasText getHostnameField();
     54         public HasValue<Boolean> getAllowOneTimeHostsField();
     55         public HasClickHandlers getAddByHostnameButton();
     56         public SimplifiedList getLabelList();
     57         public HasText getLabelNumberField();
     58         public HasClickHandlers getAddByLabelButton();
     59         public void setVisible(boolean visible);
     60 
     61         // a temporary measure until the table code gets refactored to support Passive View
     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 
     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 
    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
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    254     private void selectRow(JSONObject row) {
    255         selectedHostData.addItem(row);
    256     }
    257 
    258     private void deselectRow(JSONObject row) {
    259         selectedHostData.removeItem(row);
    260     }
    261 
    262     private void deselectAll() {
    263         availableSelection.deselectAll();
    264         // get rid of leftover meta-host entries
    265         selectedHostData.clear();
    266         selectionRefresh();
    267     }
    268 
    269     private void populateLabels(SimplifiedList list) {
    270         String[] labelNames = AfeUtils.getLabelStrings();
    271         for (String labelName : labelNames) {
    272             list.addItem(labelName, "");
    273         }
    274     }
    275 
    276     private String getHostname(JSONObject row) {
    277         return row.get("hostname").isString().stringValue();
    278     }
    279 
    280     private boolean isMetaEntry(JSONObject row) {
    281         return getHostname(row).startsWith(META_PREFIX);
    282     }
    283 
    284     private int getMetaNumber(JSONObject row) {
    285         return Integer.parseInt(getHostname(row).substring(META_PREFIX.length()));
    286     }
    287 
    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      */
    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      */
    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      */
    338     private void selectionRefresh() {
    339         selectedTable.refresh();
    340         updateHostnameList();
    341     }
    342 
    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 
    355     public void refresh() {
    356         availableTable.refresh();
    357         selectionRefresh();
    358     }
    359 
    360     public void setEnabled(boolean enabled) {
    361         this.enabled = enabled;
    362         display.setVisible(enabled);
    363     }
    364 }
    365