Vaadin grid - filtering with lazy loading - java

I have vaadin grid, and it's great that it has lazy data loading from the box. But for some reasons I have custom filters, which I use via
CallbackDataProvider<> dataProvider.fetch(Query query)
Query object has parameters for loading by portions (offset and limit), so I need to set it dynamically (?) and somehow listen grid scrolling event to load next part of data when user scrolls down (?)
Grid.dataComunicator has field Range pushRows but there no public methods to get it. And all i have is grid with lazy loading without filtered data or grid with eager loading with filtered data.
So, is there any way to implement filtering data with lazy loading in vaadin grid element?

ok, problem solved by using ConfigurableFilterDataProvider<> as wrapper over CallbackDataProvider<>.
so, when i filter table, this wrapper adds filtering conditions to all queries, and data loads lazy as usual.

I arrived here using vaadin 22. The answer probably isn't in the same context as the question but given I arrived here I suspect others will.
To create a grid that uses lazy loading and is able to inject a filter into the query use:
class SearchableGrid<E> {
Grid<E> entityGrid = new Grid<>();
private SearchableGrid(DaoDataProvider daoProvider)
{
var view = entityGrid.setItems(query ->
{
// add the filter to the query
var q = new Query<E, String>(query.getOffset(), query.getLimit(), query.getSortOrders(), null,
getSearchField().getValue());
return daoProvider.fetchFromBackEnd(q);
});
view.setItemCountCallback(query ->
{
// add the filter to the query
var q = new Query<E, String>(query.getOffset(), query.getLimit(), query.getSortOrders(), null,
getSearchField().getValue());
return daoProvider.sizeInBackEnd(q);
});
}
I've packaged the methods into a BackEndDataProvider as the same class
can be used to as a provider for comboboxes.
public class DaoDataProvider<E extends CrudEntity>
extends AbstractBackEndDataProvider<E, String>
{
JpaBaseDao<E> dao;
GetFilterBuilder<E> getFilterBuilder;
public DaoDataProvider(JpaBaseDao<E> daoProvider, GetFilterBuilder<E> getFilterBuilder)
{
this.dao = daoProvider;
this.getFilterBuilder = getFilterBuilder;
}
#Override
public int sizeInBackEnd(Query<E, String> query)
{
var q = getFilterBuilder.builderFilter(query);
return (int) q.count().intValue();
}
#Override
public Stream<E> fetchFromBackEnd(Query<E, String> query)
{
var q = getFilterBuilder.builderFilter(query);
q.startPosition(query.getOffset()).limit(query.getLimit());
return q.getResultList().stream();
}
}
The filterBuilder is where you construct your query for your back end data provider.

Related

Is there a way to convert AbstractBackEndDataProvider to ListProvider?

I have written a custom https://mindbug.in/vaadin/vaadin-dataprovider-example/ CallBackDataProvider that I based on this link here, which is used for a multi-select combo box (an addon https://github.com/bonprix/vaadin-combobox-multiselect from Vaadin's addon directory) for the purpose of providing a item lazy loading.
According to the addon's clear() and selectAll(), it expects a ListDataProvider. I've already set the component's data provider to used the custom data provider above. Whenever a clear or selectAll function is triggered, the Class Cast Exception is being thrown. It is expecting a ListDataProvider.
The very straightforward workaround for this case is to disable the clear and selectAll method by setting the boolean flag to false, but from the user's point of view, this will not be flexible.
Another step attempted is to to convert the stream into a Collection List, yet, it didn't work. It still throws an error.
This is the custom CallbackDataProvider, extended from the AbstractBackendDataProvider:
public ItemDataProvider(ReceiptService receiptService) {
if(receiptService != null){
this.receiptService = receiptService;
}else {
this.receiptService = new ReceiptService();
}
}
#Override
protected Stream<SkusSelectBox> fetchFromBackEnd(Query<SkusSelectBox, String> query) {
stream = receiptService.fetchSkus(query.getFilter().orElse(null), query.getLimit(), query.getOffset(), query.getSortOrders()).stream();
return stream;
}
#Override
protected int sizeInBackEnd(Query<SkusSelectBox, String> query) {
return receiptService.countSkus(query.getFilter().orElse(null));
}
#Override
public Object getId(SkusSelectBox item) {
return item.getItemId();
}
public Stream<SkusSelectBox> getStream(){
return stream;
}
The SkuSelectBox is a simple two string attribute object that retrieves the id and the name.
For this component, I have set the following at the view page:
ItemDataProvider itemDataProvider = new ItemDataProvider(receiptService);
ComboBoxMultiselect<SkusSelectBox> skuSelect = new ComboBoxMultiselect<>("Items");
skuSelect.setPlaceholder("Choose Items");
skuSBox.add(new SkusSelectBox("0", "No data found"));
skuSelect.setWidth(80, Unit.PERCENTAGE);
skuSelect.setRequiredIndicatorVisible(true);
skuSelect.setItemCaptionGenerator(SkusSelectBox::getItemName);
skuSelect.setSelectAllButtonCaption("Select All");
skuSelect.setClearButtonCaption("Clear");
skuSelect.showSelectAllButton(true);
skuSelect.showClearButton(true);
skuSelect.setDataProvider(itemDataProvider);
skuSelect.getDataProvider().refreshAll();
skuSelect.isReadOnly();
skuSelect.setPageLength(20);
if(skuSBox.size() <=1 ){
skuSelect.showSelectAllButton(false);
//skuSelect.showClearButton(false);
}
skuSelect.setResponsive(true);
The selectAll and clear methods are very similar except for the very end of the method:
#Override
public void selectAll(final String filter) {
final ListDataProvider<T> listDataProvider = ((ListDataProvider) getDataProvider());
final Set<String> addedItems = listDataProvider.getItems()
.stream()
.filter(t -> {
final String caption = getItemCaptionGenerator().apply(t);
if (t == null) {
return false;
}
return caption.toLowerCase()
.contains(filter.toLowerCase());
})
.map(t -> itemToKey(t))
.collect(Collectors.toSet());
updateSelection(addedItems, new HashSet<>(), true);
updateSelection(new HashSet<>(), removedItems, true); (this is for clear method)
}
Basically the class cast exception is shown in this error message, referring to either the clear or selectAll, whichever method I was invoking:
java.lang.ClassCastException: com.igi.sycarda.dashboard.hib.utils.ItemDataProvider cannot be cast to com.vaadin.data.provider.ListDataProvider
at org.vaadin.addons.ComboBoxMultiselect$1.clear(ComboBoxMultiselect.java:224)
I'm looking at the selectAll or clear method, when invoked to work as usual as if not using a CallbackDataProvider.
Until the next patch release for the addon is released, I need to put in a workaround for this problem, how can I convert a custom provider to a ListDataProvider either in a quick dirty way or a cleaner way if required?
UPDATE: Normally, I would do a direct fetch from the service class, but when tested with a tenant that has about 20K of item records, the loading of the page and the specific component box is quite slow to load. That CallbackDataProvider is to test this will work for those big amount of records.
The idea with a list data provider is that all items are loaded into memory. It is possible to load all items from a database into memory and then use that to create a list data provider. This does on the other hand defeat the purpose of having a callback data provider.
It's probably more straightforward for you to fetch the items into a list directly from your receiptService rather than going through the existing data provider.
Since there are restrictions or blocks that cause error to approach I was doing, someone just suggested to me to create a view derived from the tables / columns required and used them instead of the normal tables.
After creating a view, I just reverted and removed these lines below to the usual implementation:
skuSelect.setDataProvider(itemDataProvider);
skuSelect.getDataProvider().refreshAll();
skuSelect.isReadOnly();
skuSelect.setPageLength(20);
if(skuSBox.size() <=1 ){
skuSelect.showSelectAllButton(false);
//skuSelect.showClearButton(false);
}
At the time of writing this, we've tested it an hour ago and it solves the problem without sacrificing the performance time taken and creating an additional component. In terms of time measurement, a 20K result set in a view loads in less than 10 seconds vs 7-9 minutes previously.

How to set TTL for a specific Couchbase document using spring-data-couchbase?

How to set TTL (Time to Live) for a specific couchbase document using spring-data-couchbase?
I know there is way to set expiry time using Document notation as follows
#Document(expiry = 10)
http://docs.spring.io/spring-data/couchbase/docs/1.1.1.RELEASE/reference/html/couchbase.entity.html
It will set TTL for all the documents save through the Entity class.
But it seems there is way to set expiration(TTL) time for a specific document
"Get and touch: Fetch a specified document and update the document expiration."
mentioned in
http://docs.couchbase.com/developer/dev-guide-3.0/read-write.html
How can I achieve above functionality through spring-data-couchbase
Even If I can achieve the functionality using Java SDK, would be fine.
Any help.....
Using Spring data couchbase, this is a simple way you can configure ttl per document.
public class CouchbaseConfig extends AbstractCouchbaseConfiguration {
#Override
protected List<String> bootstrapHosts() {
return Arrays.asList("localhost");
}
#Override
protected String getBucketName() {
return "default";
}
#Override
protected String getBucketPassword() {
return "password1";
}
#Bean
public MappingCouchbaseConverter mappingCouchbaseConverter() throws Exception {
MappingCouchbaseConverter converter = new ExpiringDocumentCouchbaseConverter(couchbaseMappingContext());
converter.setCustomConversions(customConversions());
return converter;
}
class ExpiringDocumentCouchbaseConverter extends MappingCouchbaseConverter {
/**
* Create a new {#link MappingCouchbaseConverter}.
*
* #param mappingContext the mapping context to use.
*/
public ExpiringDocumentCouchbaseConverter(MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext) {
super(mappingContext);
}
// Setting custom TTL on documents.
#Override
public void write(final Object source, final CouchbaseDocument target) {
super.write(source, target);
if (source instanceof ClassContainingTTL) {
target.setExpiration(((ClassContainingTTL) source).getTimeToLive());
}
}
}
}
Using Spring-Data-Couchbase, you cannot set a TTL on a particular instance. Inserting (mutating) and setting the TTL in one go would be quite complicated given the transcoding steps that are hidden away in the CouchbaseTemplate save method.
However, if what you want to do is just update the TTL of an already persisted document (which is what getAndTouch does), there is a way that doesn't involve any transcoding and so can be applied easily:
From the CouchbaseTemplate, get access to the underlying SDK client via getCouchbaseClient() (note for now sdc is built on top of the previous generation of SDK, 1.4.x, but there'll be a preview of sdc-2.0 soon ;) )
Using the SDK, perform a touch operation on the document's ID, give it the new TTL
The touch() method returns an OperationFuture (it is async), so make sure to either block on it or consider the touch done only if notified so in the callback.
As of spring-data-couchbase:4.3.0 the code should look like yourRepository.getOperations().getCouchbaseClientFactory().getCollection(null).touch(id, ttl) or alternatively this can be done through CouchbaseTemplate as couchbaseTemplate.getCollection(null).touch(id, ttl)
findById() has a withExpiry() method that results in getAndTouch() being used and the expiration being set
User foundUser = couchbaseTemplate.findById(User.class).withExpiry(Duration.ofSeconds(1)).one(id);

How to design DI where a class is dependent on the order of instance creation

This feels like a pretty standard question and has probably been asked before, but I found it hard to find because it is hard to define in words. So if this is a duplicate, go ahead and redirect me!
I'm using Vaadin to build this web app, but that shouldn't matter to the problem at hand, unless there is an even better way of solving this through some Vaadin magic.
I have three classes:
Table
FilterGenerator
Container
My "design" looks like this:
The Container adds some properties (column headers) to itself in its constructor
The FilterGenerator #Inject the Container (in order to use the Container's getDistinct() method that gets the distinct items from the container - in order to present them nicely in a ComboBox in the filter)
The Table #Inject the FilterGenerator (in order to table.setFilterGenerator(filterGenerator))
The Table #Inject the Container and calls the containers addItems() method to add items to the container
The Table then adds the container as a datasource
What happens?
What I should have now is a table with a ComboBox in the column header, presenting distinct values to filter.
What I get is a table with a ComboBox in the column header, presenting nothing in the ComboBox, because there are no items in the ComboBox.
This is not surprising, because when the FilterGenerator calls the Containers getDistinct() method, it will get an empty map of <Column, items> back, because at the time of #Inject in the FilterGenerator, the Table hasn't called the Containers addItems() method, so the Container will at this moment be empty.
The question
How should I design this application if I want a component (FilterTable) to get something from a second component (Container), when a third component (Table) is #Inject-ing both forementioned components and it is crucial that the second component (Container) already has been initialized when the first component (FilterGenerator) gets something from it?
I could:
In the Table, simply create a new FilterGenerator. This would work, but it isn't very nice. For example, what happens if some other component wants to use the FilterGenerator?
Go back to xml-configuration in order to "manually" create the instances in the correct order. This would probably work (if I remember correctly), but having instance creation depending on the order of the elements in your xml file doesn't sound very good to me.
Use "programmatic injection" by using the ApplicationContext.getBean() in code. This would probably be even worse than the above alternatives?
Does anyone have any good suggestions on how to solve this triangular drama?
Here is the relevant code:
The Table
#Component
#Scope("session")
public class SampleAppMainTable extends FilteringTable {
#Inject
private SampleAppMainTableContainer sampleAppMainTableContainer;
#Inject
private SampleAppService sampleAppService;
#Inject
private SampleAppMainTableFilterGenerator sampleAppMainTableFilterGenerator;
public SampleAppMainTable() {
//...setting up the table
}
#PostConstruct
public void PostConstruct() throws GeneralSecurityException {
addMainTableItems();
setupMainTable();
}
public void setupMainTable() {
this.setFilterGenerator(sampleAppMainTableFilterGenerator);
sampleAppMainTableFilterGenerator.getCustomFilterComponent("Sample Id");
this.setContainerDataSource(sampleAppMainTableContainer);
}
public void addMainTableItems() {
sampleAppMainTableContainer.addItemsToContainer(sampleAppService.getAllSamples());
}
}
The Container
#Component
#Scope("prototype")
public class SampleAppMainTableContainer extends IndexedContainer {
public void addItemsToContainer(List<Sample> samples) {
// adding items to the container...
}
public Map<String, List<String>> getDistinctProperties() {
// extracting distinct items from the table...
}
}
The FilterGenerator
#Component
#Scope("session")
public class SampleAppMainTableFilterGenerator implements FilterGenerator {
#Inject
SampleAppMainTableContainer sampleAppMainTableContainer;
private List<String> aList = null;
#Override
public AbstractField<?> getCustomFilterComponent(Object propertyId) {
Map<String, List<String>> map = new HashMap<String, List<String>>();
map = sampleAppMainTableContainer.getDistinctProperties();
if (propertyId.equals("Sample Id")) {
ComboBox sampleIdCB = new ComboBox();
BeanItemContainer<String> dataList = new BeanItemContainer<String>(String.class);
List<String> aList = map.get("Sample Id");
dataList.addAll(aList);
sampleIdCB.setContainerDataSource(dataList);
sampleIdCB.setImmediate(true);
return sampleIdCB;
}
return null;
}
// other overridden methods needed...
}
I think you problem is that you do processing logic during the the injection phase. You should wait till everything is set up and then do the processing. You can do something like this by moving the processing logic from the constructor to an initialization method and marking this method with #Inject. By definition injection is done last on methods, i.e., at the time the method gets called by the injector, all the fields are injected.

Vaadin : Filtering output on the table

im new in java component base framework, especially vaadin.
before use this framework, im using struts 2.
so when i want to query some table, i have a search box, contains many textfield. when user click Search Button, then the parameters from the texfield will be sent into my hibernate directly using http post.
my question, how to filter the output using vaadin?
Just update your BeanContainer with new data. Here is an example of my code
public void refreshTableData() {
getBeanContainer().removeAllItems();
List<Customer> customers = customerDao.getByCustomerFilter(getCustomerFilterForm().getFilterItem().getBean());
getBeanContainer().addAll(customers);
}
Where CustomerFilter is a bean that has all the search criteria data, that I fill it within a form earlier (e.g with comboboxes), and beanContainer is my table container data source.
filterString = checkBox.getValue().toString();
Filterable f = (Filterable)(table.getContainerDataSource());
if(filters==null)
filters=new TreeMap<Object, SimpleStringFilter>();
SimpleStringFilter filter=filters.remove(propertyId);
if (filter != null){
f.removeContainerFilter(filter);
}
filter = new SimpleStringFilter(propertyId, filterString, ignoreCase, onlyMatchPrefix);
filters.put(propertyId, filter);
f.addContainerFilter(filter);
This is my solution to filter rows using a text that user inputs using textfield:
textField.addTextChangeListener(new TextChangeListener() {
#Override
public void textChange(TextChangeEvent event) {
Filterable filter= (Filterable) (table.getContainerDataSource());
filter.removeAllContainerFilters();
String filterString = event.getText();
if (filterString.length() > 0) {
filter.addContainerFilter(new Like("columnName", "%"+filterString +"%"));
}
}
});
I hope code is selfexplanatory.

Is it possible to request fields by specific pattern in solr?

I can use fl=fld1,fld2,fld3 tor return specific fields from solr. But sometimes i generate dynamic field names like ".*_attribute_group1" and want solr to return all group.
Is it posible to extend solr 'fl' field with regexp? Where to look in solr codebase?
Solr doesn't support wildcard patterns in field names ( "fl" param ). But you could write your own component to process the request & identify the list of fileds present in the index that you want.
Pesudo Code of extending search component to implement custom fields..
// PSUEDO CODE
public class FLPatternCustomComponent extends SearchComponent {
#Override
//Gauranteed to be called before any other SearchComponent.process
public void prepare(ResponseBuilder rb) throws IOException {
SolrParams params = rb.req.getParams();
//Input fl=field_*
String[] inputFl = params.getParams(CommonParams.FL);
Collection<String> existingFl = rb.req.getSearcher().getFieldNames();
//process & find matching fields{
SolrQuery newFields = new SolrQuery();
newFields.set(CommonParams.FL, "field_1,field_2,field_3,field_4");
AppendedSolrParams appendedParams = new AppendedSolrParams(params, q);
rb.req.setParams(appendedParams);
super.prepare(rb);
}
#Override
public void process(ResponseBuilder rb) throws IOException {
//Process request
super.process(rb);
}
}
You could have this a component chained to your existing request handler or create your request handler & perhaps you could also add any additional invariants.
You may want to consider any additional performance overhead of custom component & its processing. I have created couple of custom components for custom ranking & custom request handlers & use it without much issues.
You might want to check Solr Plugin Development.

Categories