com.ocs.dynamo.ui.provider.PivotDataProvider Maven / Gradle / Ivy
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.ocs.dynamo.ui.provider;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Queue;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
import com.ocs.dynamo.domain.AbstractEntity;
import com.ocs.dynamo.utils.ClassUtils;
import com.vaadin.flow.data.provider.AbstractDataProvider;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.function.SerializablePredicate;
import lombok.Getter;
import lombok.Setter;
/**
* A pivoting data provider that acts as a wrapper around a regular data
* provider.
*
* This component must be initialized with the lower level entity that is being
* queried. Multiple rows from the lower level will then be aggregated into a
* PivotedItem based on the value of the rowKeyProperty.
*
* @author Bas Rutten
*
* @param the primary key of the entity to query
* @param the entity to query
*/
public class PivotDataProvider>
extends AbstractDataProvider> {
private static final int PAGE_SIZE = 1000;
private static final long serialVersionUID = 4243018942036820420L;
/**
* Code to carry out after the count query completes
*/
@Getter
@Setter
private Consumer afterCountCompleted;
/**
* The name of the property that contains the identifying value for a column
*/
@Getter
private String columnKeyProperty;
/**
* Cache for keeping track of current page of data
*/
private Queue dataCache = new LinkedList<>();
@Getter
private List fixedColumnKeys;
/**
* The offset of the latest page that was fetched from the wrapped provider
*/
private int lastPivotOffset = 0;
/**
* The offset of the latest page that was requested from the outside
*/
private int lastRequestedOffset = 0;
/**
* The "row key" value of the last retrieved row
*/
private Object lastRowKeyValue;
/**
* Mapping from requested offset to offset in the wrapped provider
*/
private Map offsetMap = new HashMap<>();
/**
* The pivoted item that is currently being constructed
*/
private PivotedItem pivotedItem;
/**
* The list of pivoted properties
*/
@Getter
private List pivotedProperties;
/**
* The list of hidden pivoted properties
*/
@Getter
private List hiddenPivotedProperties = new ArrayList<>();
/**
* The data provider that is being wrapped
*/
private BaseDataProvider provider;
/**
* The property that is checked to see if a new row has been reached
*/
@Getter
private String rowKeyProperty;
@Getter
private int size;
/**
* Supplier to carry out to retrieve size of pivoted data set
*/
private Supplier sizeSupplier;
@Setter
private Map aggregationMap = new HashMap<>();
@Setter
private Map> aggregationClassMap = new HashMap<>();
/**
*
* @param provider the wrapped data provider
* @param rowKeyProperty the property to check for unique row values
* @param columnKeyProperty the property to check for the column key
* @param fixedColumnKeys the fixed columns
* @param pivotedProperties the pivoted properties
* @param hiddenPivotedProperties the hidden pivoted properties
* @param sizeSupplier supplier that is called to determine the
* number of rows in the result set
*/
public PivotDataProvider(BaseDataProvider provider, String rowKeyProperty, String columnKeyProperty,
List fixedColumnKeys, List pivotedProperties, List hiddenPivotedProperties,
Supplier sizeSupplier) {
this.provider = provider;
this.columnKeyProperty = columnKeyProperty;
this.rowKeyProperty = rowKeyProperty;
this.fixedColumnKeys = fixedColumnKeys;
this.pivotedProperties = pivotedProperties;
this.hiddenPivotedProperties = hiddenPivotedProperties == null ? Collections.emptyList()
: hiddenPivotedProperties;
this.sizeSupplier = sizeSupplier;
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Stream fetch(Query> query) {
// these methods must be called in order to not break the contract
int requestedOffset = query.getOffset();
query.getLimit();
List result = new ArrayList<>();
while (result.size() < query.getLimit()) {
Optional> predicate = (Optional) query.getFilter();
// re-load earlier page
if (requestedOffset < lastRequestedOffset && offsetMap.containsKey(requestedOffset)) {
dataCache.clear();
lastPivotOffset = offsetMap.get(requestedOffset);
pivotedItem = null;
}
lastRequestedOffset = requestedOffset;
// fetch next page
fetchNextPage(query, requestedOffset, predicate);
if (dataCache.isEmpty()) {
// no more records left before end of page, abort
break;
} else {
handleRow(result);
}
}
// add last item
if (pivotedItem != null && result.size() < query.getLimit()) {
result.add(pivotedItem);
}
return result.stream();
}
@Override
public boolean isInMemory() {
return false;
}
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public int size(Query> query) {
dataCache.clear();
offsetMap.clear();
lastRequestedOffset = 0;
lastPivotOffset = 0;
pivotedItem = null;
// query the underlying provider
Optional> sp = (Optional) query.getFilter();
Query> newQuery = new Query<>(query.getOffset(), query.getLimit(),
query.getSortOrders(), null, sp.isPresent() ? sp.get() : null);
provider.size(newQuery);
// get the number of pivoted rows
this.size = sizeSupplier.get();
if (getAfterCountCompleted() != null) {
getAfterCountCompleted().accept(size);
}
return size;
}
/**
* Adds an aggregation of the specified type for the specified property
* @param pivotProperty the property to aggregate on
* @param type the type of aggregation (e.g. sum, count)
* @param clazz the data type of the aggregation
*/
public void addAggregation(String pivotProperty, PivotAggregationType type, Class> clazz) {
aggregationMap.put(pivotProperty, type);
aggregationClassMap.put(pivotProperty, clazz);
}
public PivotAggregationType getAggregation(String pivotProperty) {
return aggregationMap.get(pivotProperty);
}
public Class> getAggregationClass(String pivotProperty) {
return aggregationClassMap.get(pivotProperty);
}
public List getAllPrivotProperties() {
List allProps = new ArrayList<>();
allProps.addAll(getPivotedProperties());
allProps.addAll(getHiddenPivotedProperties());
return allProps;
}
/**
* Handles a single row from the underlying result set
*
* @param result
*/
private void handleRow(List result) {
T entity = dataCache.poll();
// get the row value to determine if we need a new row
Object rowKeyValue = ClassUtils.getFieldValue(entity, rowKeyProperty);
// create new pivoted item if needed and add existing one to result set
if (lastRowKeyValue == null || !Objects.equals(rowKeyValue, lastRowKeyValue)) {
// add previous item
if (pivotedItem != null) {
result.add(pivotedItem);
}
// create new item
pivotedItem = new PivotedItem(rowKeyValue);
lastRowKeyValue = rowKeyValue;
}
// add fixed columns
for (int i = 0; i < fixedColumnKeys.size(); i++) {
String fixedColumnKey = fixedColumnKeys.get(i);
Object value = ClassUtils.getFieldValue(entity, fixedColumnKey);
pivotedItem.setFixedValue(fixedColumnKey, value);
}
// extract the useful values from this row
Object colKeyValue = ClassUtils.getFieldValue(entity, columnKeyProperty);
for (int i = 0; i < pivotedProperties.size(); i++) {
String propertyName = pivotedProperties.get(i);
Object value = ClassUtils.getFieldValue(entity, propertyName);
pivotedItem.setValue(colKeyValue, propertyName, value);
}
// extract additional useful (but invisible) values
if (hiddenPivotedProperties != null) {
for (int i = 0; i < hiddenPivotedProperties.size(); i++) {
String propertyName = hiddenPivotedProperties.get(i);
Object value = ClassUtils.getFieldValue(entity, propertyName);
pivotedItem.setValue(colKeyValue, propertyName, value);
}
}
}
/**
* Fetches the next page of data from the underlying provider
*
* @param query the incoming query object
* @param requestedOffset the requested offset
* @param predicate the filter predicate
*/
private void fetchNextPage(Query> query, int requestedOffset,
Optional> predicate) {
if (dataCache.isEmpty()) {
offsetMap.put(requestedOffset, lastPivotOffset);
Query> newQuery = new Query<>(lastPivotOffset, PAGE_SIZE, query.getSortOrders(),
null, predicate.orElse(null));
provider.fetch(newQuery).forEach(t -> dataCache.add(t));
lastPivotOffset = lastPivotOffset + PAGE_SIZE;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy