From 6390e2fb37e730fb0ae75660bff53bf933bdffa5 Mon Sep 17 00:00:00 2001 From: Erik Lumme Date: Thu, 14 Sep 2017 11:43:30 +0300 Subject: [PATCH] Migrate UsingJDBCwithLazyQueryContainerAndFilteringTable --- ...zyQueryContainerAndFilteringTable.asciidoc | 413 ++++++++++++++++++ documentation/articles/contents.asciidoc | 1 + 2 files changed, 414 insertions(+) create mode 100644 documentation/articles/UsingJDBCwithLazyQueryContainerAndFilteringTable.asciidoc diff --git a/documentation/articles/UsingJDBCwithLazyQueryContainerAndFilteringTable.asciidoc b/documentation/articles/UsingJDBCwithLazyQueryContainerAndFilteringTable.asciidoc new file mode 100644 index 0000000000..2f5e73e6ed --- /dev/null +++ b/documentation/articles/UsingJDBCwithLazyQueryContainerAndFilteringTable.asciidoc @@ -0,0 +1,413 @@ +[[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 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 loadItems(int startIndex, int count) { + List items = new ArrayList(); + 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(rs.getString(1), String.class)); + item.addItemProperty(Column.loc_id, + new ObjectProperty(rs.getInt(2), Integer.class)); + item.addItemProperty(Column.update_when, + new ObjectProperty(rs.getTimestamp(3), Timestamp.class)); + item.addItemProperty(Column.net_ip, + new ObjectProperty(rs.getString(4))); + item.addItemProperty(Column.lan_ip, + new ObjectProperty(rs.getString(5))); + item.addItemProperty(Column.lan_mac, + new ObjectProperty(rs.getString(6))); + item.addItemProperty(Column.hardware, + new ObjectProperty(rs.getString(7))); + item.addItemProperty(Column.opsys, + new ObjectProperty(rs.getString(8))); + item.addItemProperty(Column.image, + new ObjectProperty(rs.getString(9))); + item.addItemProperty(Column.sw_ver, + new ObjectProperty(rs.getString(10))); + item.addItemProperty(Column.cpu_load, + new ObjectProperty(rs.getString(11))); + item.addItemProperty(Column.proc_count, + new ObjectProperty(rs.getInt(12))); + item.addItemProperty(Column.mem_usage, + new ObjectProperty(rs.getInt(13))); + item.addItemProperty(Column.disk_usage, + new ObjectProperty(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("")); + item.addItemProperty(Column.loc_id, new ObjectProperty(-1)); + item.addItemProperty(Column.update_when, + new ObjectProperty(new Timestamp(System.currentTimeMillis()))); + item.addItemProperty(Column.net_ip, new ObjectProperty("")); + item.addItemProperty(Column.lan_ip, new ObjectProperty("")); + item.addItemProperty(Column.lan_mac, new ObjectProperty("")); + item.addItemProperty(Column.hardware, new ObjectProperty("")); + item.addItemProperty(Column.opsys, new ObjectProperty("")); + item.addItemProperty(Column.image, new ObjectProperty("")); + item.addItemProperty(Column.sw_ver, new ObjectProperty("")); + item.addItemProperty(Column.cpu_load, new ObjectProperty("")); + item.addItemProperty(Column.proc_count, new ObjectProperty(0)); + item.addItemProperty(Column.mem_usage, new ObjectProperty(0)); + item.addItemProperty(Column.disk_usage, new ObjectProperty(0)); + + log.warn("Shouldn't be calling DeviceStatusQuery.constructItem()"); + return item; + } + + @Override + public boolean deleteAllItems() { + throw new UnsupportedOperationException(); + } + + @Override + public void saveItems(List arg0, List arg1, List 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 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("`","`")); +.... diff --git a/documentation/articles/contents.asciidoc b/documentation/articles/contents.asciidoc index 02bddf5beb..0fc3ebc5f1 100644 --- a/documentation/articles/contents.asciidoc +++ b/documentation/articles/contents.asciidoc @@ -3,3 +3,4 @@ [discrete] == Articles - link:LazyQueryContainer.asciidoc[Lazy query container] +- link:UsingJDBCwithLazyQueryContainerAndFilteringTable.asciidoc[Using JDBC with Lazy Query Container and FilteringTable] -- 2.39.5