summaryrefslogtreecommitdiffstats
path: root/documentation/articles/UsingJDBCwithLazyQueryContainerAndFilteringTable.asciidoc
blob: 940667a333f39c24a868d72a472dcc0179e2b26b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
---
title: Using JDBC with Lazy Query Container and FilteringTable
order: 3
layout: page
---

[[using-jdbc-with-lazy-query-container-and-filteringtable]]
= Using JDBC with Lazy Query Container and FilteringTable

Introduction

Populating display tables from a database is a deceptively complicated
operation, especially when mixing multiple techniques together. This
page provides an example of one way to efficiently load data from a SQL
database table into a filterable UI, using the _Lazy Query Container_ and
_FilteringTable_ add-ons.

Note: Do not use the SQLContainer package. This is buggy and will have
your database and garbage collector crunching in loops.

`Query` and `QueryFactory` implementation

The place to start is the Lazy Query Container's (LQC) Query interface.
This is where the interface with your database happens. This example
access a database table with computer statistics. It's read-only. How to
log and access your JDBC connection differs in each environment; they
are treated generically here. Only select imports are included.

[source,java]
....
import org.vaadin.addons.lazyquerycontainer.Query;
import org.vaadin.addons.lazyquerycontainer.QueryDefinition;
import org.vaadin.addons.lazyquerycontainer.QueryFactory;

import com.vaadin.data.Container.Filter;
import com.vaadin.data.Item;
import com.vaadin.data.util.ObjectProperty;
import com.vaadin.data.util.PropertysetItem;
import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
import com.vaadin.data.util.sqlcontainer.query.generator.filter.QueryBuilder;

/**
 * Query for using the database's device-status table as a data source
 * for a Vaadin container (table).
 */
public class DeviceStatusQuery implements Query {
  private static  final Logger log = LoggerFactory.getLogger(DeviceStatusQuery.class);

  /**
   * The table column names. Use these instead of typo-prone magic strings.
   */
  public static enum Column {
    hostname, loc_id, update_when, net_ip, lan_ip, lan_mac, hardware,
    opsys, image, sw_ver, cpu_load, proc_count, mem_usage, disk_usage;

    public boolean is(Object other) {
      if (other instanceof String)
        return this.toString().equals(other);
      else
        return (this == other);
    }
  };

  public static class Factory implements QueryFactory {
    private int locId;

    /**
     * Constructor
     * @param locId - location ID
     */
    public Factory(int locId) {
      this.locId = locId;
    }

    @Override
    public Query constructQuery(QueryDefinition def) {
      return new DeviceStatusQuery(def, locId);
    }
  }//class Factory

  /////// INSTANCE ///////

  private String countQuery;
  private String fetchQuery;
  /** Borrow from SQLContainer to build filter queries */
  private StatementHelper stmtHelper = new StatementHelper();

  /**
   * Constructor
   * @param locId - location ID
   * @param userId - ID of user viewing the data
   */
  private DeviceStatusQuery(QueryDefinition def, int locId) {
    Build filters block List<Filter> filters = def.getFilters();
    String filterStr = null;
    if (filters != null && !filters.isEmpty())
      filterStr = QueryBuilder.getJoinedFilterString(filters, "AND", stmtHelper);

    // Count query
    StringBuilder query = new StringBuilder( "SELECT COUNT(*) FROM device_status");
    query.append(" WHERE loc_id=").append(locId);

    if (filterStr != null)
      query.append(" AND ").append(filterStr);

    this.countQuery = query.toString();

    // Fetch query
    query = new StringBuilder(
      "SELECT hostname, loc_id, update_when, net_ip, lan_ip, " +
      "lan_mac, hardware, opsys, image, sw_ver, cpu_load, " +
      "proc_count, mem_usage, disk_usage FROM device_status");
    query.append(" WHERE loc_id=").append(locId);

    if (filterStr != null)
      query.append(" AND ").append(filterStr);

    // Build Order by
    Object[] sortIds = def.getSortPropertyIds();
    if (sortIds != null && sortIds.length > 0) {
      query.append(" ORDER BY ");
      boolean[] sortAsc = def.getSortPropertyAscendingStates();
      assert sortIds.length == sortAsc.length;

      for (int si = 0; si < sortIds.length; ++si) {
        if (si > 0) query.append(',');

        query.append(sortIds[si]);
        if (sortAsc[si]) query.append(" ASC");
        else query.append(" DESC");
      }
    }
    else query.append(" ORDER BY hostname");

    this.fetchQuery = query.toString();

    log.trace("DeviceStatusQuery count: {}", this.countQuery);
    log.trace("DeviceStatusQuery fetch: {}", this.fetchQuery);
  }//constructor

  @Override
  public int size() {
    int result = 0;
    try (Connection conn = Database.getConnection()) {
      PreparedStatement stmt = conn.prepareStatement(this.countQuery);
      stmtHelper.setParameterValuesToStatement(stmt);
      ResultSet rs = stmt.executeQuery();
      if (rs.next()) result = rs.getInt(1);

      stmt.close();
    }
    catch (SQLException ex) {
      log.error("DB access failure", ex);
    }

    log.trace("DeviceStatusQuery size=\{}", result);
    return result;
  }

  @Override
  public List<Item> loadItems(int startIndex, int count) {
    List<Item> items = new ArrayList<Item>();
    try (Connection conn = Database.getConnection()) {
      String q = this.fetchQuery + " LIMIT " + count + " OFFSET " + startIndex;
      PreparedStatement stmt = conn.prepareStatement(q);
      stmtHelper.setParameterValuesToStatement(stmt);

      ResultSet rs = stmt.executeQuery();
      while (rs.next()) {
        PropertysetItem item = new PropertysetItem();
        // Include the data type parameter on ObjectProperty any time the value could be null
        item.addItemProperty(Column.hostname,
          new ObjectProperty<String>(rs.getString(1), String.class));
        item.addItemProperty(Column.loc_id,
          new ObjectProperty<Integer>(rs.getInt(2), Integer.class));
        item.addItemProperty(Column.update_when,
          new ObjectProperty<Timestamp>(rs.getTimestamp(3), Timestamp.class));
        item.addItemProperty(Column.net_ip,
          new ObjectProperty<String>(rs.getString(4)));
        item.addItemProperty(Column.lan_ip,
          new ObjectProperty<String>(rs.getString(5)));
        item.addItemProperty(Column.lan_mac,
          new ObjectProperty<String>(rs.getString(6)));
        item.addItemProperty(Column.hardware,
          new ObjectProperty<String>(rs.getString(7)));
        item.addItemProperty(Column.opsys,
          new ObjectProperty<String>(rs.getString(8)));
        item.addItemProperty(Column.image,
          new ObjectProperty<String>(rs.getString(9)));
        item.addItemProperty(Column.sw_ver,
          new ObjectProperty<String>(rs.getString(10)));
        item.addItemProperty(Column.cpu_load,
          new ObjectProperty<String>(rs.getString(11)));
        item.addItemProperty(Column.proc_count,
          new ObjectProperty<Integer>(rs.getInt(12)));
        item.addItemProperty(Column.mem_usage,
          new ObjectProperty<Integer>(rs.getInt(13)));
        item.addItemProperty(Column.disk_usage,
          new ObjectProperty<Integer>(rs.getInt(14)));

        items.add(item);
      }
      rs.close();
      stmt.close();
    }
    catch (SQLException ex) {
      log.error("DB access failure", ex);
    }

    log.trace("DeviceStatusQuery load {} items from {}={} found", count,
        startIndex, items.size());
    return items;
  } //loadItems()

/**
 * Only gets here if loadItems() fails, so return an empty state.
 * Throwing from here causes an infinite loop.
 */
 @Override
 public Item constructItem() {
  PropertysetItem item = new PropertysetItem();
  item.addItemProperty(Column.hostname, new ObjectProperty<String>(""));
  item.addItemProperty(Column.loc_id, new ObjectProperty<Integer>(-1));
  item.addItemProperty(Column.update_when,
    new ObjectProperty<Timestamp>(new Timestamp(System.currentTimeMillis())));
  item.addItemProperty(Column.net_ip, new ObjectProperty<String>(""));
  item.addItemProperty(Column.lan_ip, new ObjectProperty<String>(""));
  item.addItemProperty(Column.lan_mac, new ObjectProperty<String>(""));
  item.addItemProperty(Column.hardware, new ObjectProperty<String>(""));
  item.addItemProperty(Column.opsys, new ObjectProperty<String>(""));
  item.addItemProperty(Column.image, new ObjectProperty<String>(""));
  item.addItemProperty(Column.sw_ver, new ObjectProperty<String>(""));
  item.addItemProperty(Column.cpu_load, new ObjectProperty<String>(""));
  item.addItemProperty(Column.proc_count, new ObjectProperty<Integer>(0));
  item.addItemProperty(Column.mem_usage, new ObjectProperty<Integer>(0));
  item.addItemProperty(Column.disk_usage, new ObjectProperty<Integer>(0));

  log.warn("Shouldn't be calling DeviceStatusQuery.constructItem()");
    return item;
  }

  @Override
  public boolean deleteAllItems() {
    throw new UnsupportedOperationException();
  }

  @Override
  public void saveItems(List<Item> arg0, List<Item> arg1, List<Item> arg2) {
    throw new UnsupportedOperationException();
  }
}
....

Using the Query with FilteringTable

Now that we have our Query, we need to create a table to hold it. Here's
one of many ways to do it with FilteringTable.

[source,java]
....

import org.tepi.filtertable.FilterDecorator;
import org.tepi.filtertable.numberfilter.NumberFilterPopupConfig;
import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer;

import com.vaadin.data.Property;
import com.vaadin.server.Resource;
import com.vaadin.shared.ui.datefield.Resolution;
import com.vaadin.ui.DateField;
import com.vaadin.ui.AbstractTextField.TextChangeEventMode;

/**
 * Filterable table of device statuses.
 */
public class DeviceStatusTable extends FilterTable {
  private final
  String[] columnHeaders = {"Device", "Site", "Last Report", "Report IP",
      "LAN IP", "MAC Adrs", "Hardware", "O/S", "Image", "Software", "CPU"
      "Load", "Processes", "Memory Use", "Disk Use"};

  /**
   * Configuration this table for displaying of DeviceStatusQuery data.
   */
  public void configure(LazyQueryContainer dataSource) {
    super.setFilterGenerator(new LQCFilterGenerator(dataSource));
    super.setFilterBarVisible(true);
    super.setSelectable(true);
    super.setImmediate(true);
    super.setColumnReorderingAllowed(true);
    super.setColumnCollapsingAllowed(true);
    super.setSortEnabled(true);

    dataSource.addContainerProperty(Column.hostname, String.class, null, true, true);
    dataSource.addContainerProperty(Column.loc_id, Integer.class, null, true, false);
    dataSource.addContainerProperty(Column.update_when, Timestamp.class, null, true, true);
    dataSource.addContainerProperty(Column.net_ip, String.class, null, true, true);
    dataSource.addContainerProperty(Column.lan_ip, String.class, null, true, true);
    dataSource.addContainerProperty(Column.lan_mac, String.class, null, true, true);
    dataSource.addContainerProperty(Column.hardware, String.class, null, true, true);
    dataSource.addContainerProperty(Column.opsys, String.class, null, true, true);
    dataSource.addContainerProperty(Column.image, String.class, null, true, true);
    dataSource.addContainerProperty(Column.sw_ver, String.class, null, true, true);
    dataSource.addContainerProperty(Column.cpu_load, String.class, null, true, true);
    dataSource.addContainerProperty(Column.proc_count, Integer.class, null, true, true);
    dataSource.addContainerProperty(Column.mem_usage, Integer.class, null, true, true);
    dataSource.addContainerProperty(Column.disk_usage, Integer.class, null, true, true);

    super.setContainerDataSource(dataSource);
    super.setColumnHeaders(columnHeaders);
    super.setColumnCollapsed(Column.lan_mac, true);
    super.setColumnCollapsed(Column.opsys, true);
    super.setColumnCollapsed(Column.image, true);
    super.setFilterFieldVisible(Column.loc_id, false);
  }

  @Override
  protected String formatPropertyValue(Object rowId, Object colId, Property<?> property) {
    if (Column.loc_id.is(colId)) {
      // Example of how to translate a column value
      return Hierarchy.getLocation(((Integer) property.getValue())).getShortName();
    } else if (Column.update_when.is(colId)) {
      // Example of how to format a value.
      return ((java.sql.Timestamp) property.getValue()).toString().substring(0, 19);
    }

    return super.formatPropertyValue(rowId, colId, property);
  }

  /**
   * Filter generator that triggers a refresh of a LazyQueryContainer
   * whenever the filters change.
   */
  public class LQCFilterGenerator implements FilterGenerator {
    private final LazyQueryContainer lqc;

    public LQCFilterGenerator(LazyQueryContainer lqc) {
      this.lqc = lqc;
    }

    @Override
    public Filter generateFilter(Object propertyId, Object value) {
      return null;
    }

    @Override
    public Filter generateFilter(Object propertyId, Field<?> originatingField) {
      return null;
    }

    @Override
    public AbstractField<?> getCustomFilterComponent(Object propertyId) {
      return null;
    }

    @Override
    public void filterRemoved(Object propertyId) {
      this.lqc.refresh();
    }

    @Override
    public void filterAdded(Object propertyId, Class<? extends Filter> filterType, Object value) {
      this.lqc.refresh();
    }

    @Override
    public Filter filterGeneratorFailed(Exception reason, Object propertyId, Object value) {
      return null;
    }
  }
}
....
Put them together on the UI

Now we have our Container that reads from the database, and a Table for
displaying them, lets put the final pieces together somewhere in some UI
code:

[source,java]
....
final DeviceStatusTable table = new DeviceStatusTable();
table.setSizeFull();

DeviceStatusQuery.Factory factory = new DeviceStatusQuery.Factory(locationID);
final LazyQueryContainer statusDataContainer = new LazyQueryContainer(factory,
  /*index*/ null, /*batchSize*/ 50, false);
statusDataContainer.getQueryView().setMaxCacheSize(300);
table.configure(statusDataContainer);

layout.addComponent(table);
layout.setHeight(100f, Unit.PERCENTAGE); // no scrollbar

// Respond to row click
table.addValueChangeListener(new Property.ValueChangeListener() {
  @Override
  public void valueChange(ValueChangeEvent event) {
    Object index = event.getProperty().getValue();
    if (index != nulll) {
      int locId = (Integer) statusDataContainer.getItem(index)
          .getItemProperty(DeviceStatusQuery.Column.loc_id).getValue();
      doSomething(locId);
      table.setValue(null); //visually deselect
    }
  }
});
....

And finally, since we're using `SQLContainer`{empty}'s `QueryBuilder`, depending on
your database you may need to include something like this once during
your application startup:

[source,java]
....
import com.vaadin.data.util.sqlcontainer.query.generator.filter.QueryBuilder;
import com.vaadin.data.util.sqlcontainer.query.generator.filter.StringDecorator;

// Configure Vaadin SQLContainer to work with MySQL
QueryBuilder.setStringDecorator(new StringDecorator("`","`"));
....