Browse Source

Migrate UsingJDBCwithLazyQueryContainerAndFilteringTable

tags/7.7.11
Erik Lumme 6 years ago
parent
commit
6390e2fb37

+ 413
- 0
documentation/articles/UsingJDBCwithLazyQueryContainerAndFilteringTable.asciidoc View File

@@ -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<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("`","`"));
....

+ 1
- 0
documentation/articles/contents.asciidoc View File

@@ -3,3 +3,4 @@
[discrete]
== Articles
- link:LazyQueryContainer.asciidoc[Lazy query container]
- link:UsingJDBCwithLazyQueryContainerAndFilteringTable.asciidoc[Using JDBC with Lazy Query Container and FilteringTable]

Loading…
Cancel
Save