/*
* Copyright 2014 Red Hat, Inc. and/or its affiliates.
*
* 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 org.dashbuilder.displayer.client;
import org.dashbuilder.common.client.error.ClientRuntimeError;
import org.dashbuilder.dataset.*;
import org.dashbuilder.dataset.client.DataSetClientServices;
import org.dashbuilder.dataset.client.DataSetReadyCallback;
import org.dashbuilder.dataset.engine.group.IntervalBuilder;
import org.dashbuilder.dataset.engine.group.IntervalBuilderLocator;
import org.dashbuilder.dataset.filter.DataSetFilter;
import org.dashbuilder.dataset.group.*;
import org.dashbuilder.dataset.sort.ColumnSort;
import org.dashbuilder.dataset.sort.DataSetSort;
import org.dashbuilder.dataset.sort.SortOrder;
import java.util.*;
public class DataSetHandlerImpl implements DataSetHandler {
protected DataSetClientServices clientServices;
protected DataSetLookup lookupBase;
protected DataSetLookup lookupCurrent;
protected DataSet lastLookedUpDataSet;
public DataSetHandlerImpl(DataSetClientServices clientServices, DataSetLookup lookup) {
this.clientServices = clientServices;
this.lookupBase = lookup;
this.lookupCurrent = lookup.cloneInstance();
}
@Override
public DataSet getLastDataSet() {
return lastLookedUpDataSet;
}
@Override
public DataSetLookup getCurrentDataSetLookup() {
return lookupCurrent;
}
@Override
public void resetAllOperations() {
this.lookupCurrent = lookupBase.cloneInstance();
}
@Override
public void limitDataSetRows(int offset, int rows) {
int offsetBase = lookupBase.getRowOffset();
int rowsBase = lookupBase.getNumberOfRows();
lookupCurrent.setRowOffset(offsetBase + offset);
// base 0 to all, 0 to 20 => offset=0, rows=20
// base 0 to 1, 0 to 20 => offset=0, rows=1
// base 50 to 51, 0 to 20 => offset=50, rows=20
// base 10 to 31, 20 to 10 => offset=30, rows=10
// base 10 to 31, 0 to 50 => offset=10, rows=31
if (rowsBase < 1 || rowsBase > rows) {
lookupCurrent.setNumberOfRows(rows);
}
}
@Override
public DataSetGroup getGroupOperation(String columnId) {
String sourceId = _getSourceColumnId(columnId);
int index = lookupCurrent.getLastGroupOpIndex(0, sourceId, false);
if (index != -1) {
return (DataSetGroup) lookupCurrent.getOperation(index).cloneInstance();
}
DataSetGroup result = new DataSetGroup();
result.setColumnGroup(new ColumnGroup(sourceId, sourceId, GroupStrategy.DYNAMIC));
return result;
}
@Override
public boolean filter(DataSetGroup op) {
ColumnGroup cg = op.getColumnGroup();
if (cg == null) {
throw new RuntimeException("Group ops require a pivot column to be specified.");
}
if (!op.isSelect()) {
throw new RuntimeException("Group intervals not specified.");
}
// Avoid duplicates
for (DataSetGroup next : lookupCurrent.getOperationList(DataSetGroup.class)) {
if (op.equals(next)) {
return false;
}
}
// The interval selection op. must be added right before the first existing group op.
DataSetGroup clone = op.cloneInstance();
//clone.getGroupFunctions().clear();
int idx = lookupCurrent.getFirstGroupOpIndex(0, null, null);
_filter(idx < 0 ? 0 : idx, clone, false);
return true;
}
@Override
public boolean filter(DataSetFilter op) {
if (op == null) {
return false;
}
// Avoid duplicates
for (DataSetFilter next : lookupCurrent.getOperationList(DataSetFilter.class)) {
if (op.equals(next)) {
return false;
}
}
lookupCurrent.addOperation(0, op);
return true;
}
@Override
public boolean drillDown(DataSetGroup op) {
ColumnGroup cg = op.getColumnGroup();
if (cg == null) {
throw new RuntimeException("Group ops require a pivot column to be specified.");
}
if (!op.isSelect()) {
throw new RuntimeException("Group intervals not specified.");
}
// Avoid duplicates
for (DataSetGroup next : lookupCurrent.getOperationList(DataSetGroup.class)) {
if (op.equals(next)) {
return false;
}
}
// Get the latest group op. for the target column being selected.
int lastSelection = lookupCurrent.getLastGroupOpIndex(0, null, true) + 1;
int targetGroup = lookupCurrent.getLastGroupOpIndex(lastSelection, cg.getColumnId(), false);
// If the selection does not exists just add it.
if (targetGroup == -1) {
DataSetGroup clone = op.cloneInstance();
//clone.getGroupFunctions().clear();
_filter(lastSelection, clone, true);
return true;
}
// If there not exists a group op after the target then the target op must be propagated along the selection.
DataSetGroup targetOp = lookupCurrent.getOperation(targetGroup);
int latestGroup = lookupCurrent.getLastGroupOpIndex(targetGroup + 1, null, false);
if (latestGroup == -1) {
DataSetGroup clone = targetOp.cloneInstance();
_filter(targetGroup + 1, clone, true);
}
// Enable the selection
_select(targetOp, op.getSelectedIntervalList());
return true;
}
@Override
public boolean unfilter(DataSetGroup op) {
return _unfilter(op, false);
}
@Override
public boolean unfilter(DataSetFilter op) {
if (op == null) {
return false;
}
int idx = lookupCurrent.getOperationIdx(op);
if (idx != -1) {
lookupCurrent.removeOperation(idx);
return true;
}
return false;
}
@Override
public boolean drillUp(DataSetGroup op) {
return _unfilter(op, true);
}
@Override
public void sort(String columnId, SortOrder sortOrder) {
unsort();
String sourceId = _getSourceColumnId(columnId);
DataSetSort sortOp = new DataSetSort();
sortOp.addSortColumn(new ColumnSort(sourceId, sortOrder));
lookupCurrent.addOperation(sortOp);
}
public boolean unsort() {
int n = lookupCurrent.removeOperations(DataSetOpType.SORT);
return n > 0;
}
@Override
public void lookupDataSet(final DataSetReadyCallback callback) throws Exception {
clientServices.lookupDataSet(lookupCurrent, new DataSetReadyCallback() {
public void callback(DataSet dataSet) {
lastLookedUpDataSet = dataSet;
callback.callback(dataSet);
}
public void notFound() {
callback.notFound();
}
@Override
public boolean onError(final ClientRuntimeError error) {
return callback.onError(error);
}
});
}
@Override
public Interval getInterval(String columnId, int row) {
if (lastLookedUpDataSet == null) {
return null;
}
DataColumn column = lastLookedUpDataSet.getColumnById(columnId);
if (column == null) {
return null;
}
// For grouped by date data sets, locate the interval corresponding to the row specified
ColumnGroup cg = column.getColumnGroup();
DataSetMetadata metadata = clientServices.getMetadata(lookupBase.getDataSetUUID());
if (cg != null && metadata != null) {
IntervalBuilderLocator intervalBuilderLocator = clientServices.getIntervalBuilderLocator();
ColumnType columnType = metadata.getColumnType(cg.getSourceId());
if (columnType == null) {
throw new RuntimeException("Column type not found in data set metadata: " + cg.getSourceId());
}
IntervalBuilder intervalBuilder = intervalBuilderLocator.lookup(columnType, cg.getStrategy());
Interval target = intervalBuilder.locate(column, row);
// The resulting interval must be portable.
Interval result = new Interval(target.getName(), target.getIndex());
result.setType(target.getType());
result.setMinValue(target.getMinValue());
result.setMaxValue(target.getMaxValue());
return result;
}
// Return the interval by name.
List values = column.getValues();
if (row >= values.size()) {
return null;
}
Object value = values.get(row);
if (value == null) {
return null;
}
return new Interval(value.toString());
}
// Internal filter/drillDown implementation logic
protected Map<String,List<GroupOpFilter>> _groupOpsAdded = new HashMap<String,List<GroupOpFilter>>();
protected Map<String,List<GroupOpFilter>> _groupOpsSelected = new HashMap<String,List<GroupOpFilter>>();
protected void _filter(int index, DataSetGroup op, boolean drillDown) {
ColumnGroup cgroup = op.getColumnGroup();
String columnId = cgroup.getColumnId();
if (!_groupOpsAdded.containsKey(columnId)) _groupOpsAdded.put(columnId, new ArrayList<GroupOpFilter>());
List<GroupOpFilter> filterOps = _groupOpsAdded.get(columnId);
// When adding an external filter, look first if it exists an existing filter already.
if (!drillDown) {
for (GroupOpFilter filterOp : filterOps) {
if (!filterOp.drillDown && filterOp.groupOp.getColumnGroup().equals(cgroup)) {
filterOp.groupOp.getSelectedIntervalList().clear();
filterOp.groupOp.getSelectedIntervalList().addAll(op.getSelectedIntervalList());
return;
}
}
}
GroupOpFilter groupOpFilter = new GroupOpFilter(op, drillDown);
filterOps.add(groupOpFilter);
lookupCurrent.addOperation(index, op);
}
protected void _select(DataSetGroup op, List<Interval> intervalList) {
GroupOpFilter groupOpFilter = new GroupOpFilter(op, true);
op.setSelectedIntervalList(intervalList);
//op.getGroupFunctions().clear();
String columnId = op.getColumnGroup().getColumnId();
if (!_groupOpsSelected.containsKey(columnId)) {
_groupOpsSelected.put(columnId, new ArrayList<GroupOpFilter>());
}
_groupOpsSelected.get(columnId).add(groupOpFilter);
}
protected boolean _unfilter(DataSetGroup op, boolean drillDown) {
boolean opFound = false;
String columnId = op.getColumnGroup().getColumnId();
if (_groupOpsAdded.containsKey(columnId)) {
Iterator<GroupOpFilter> it1 = _groupOpsAdded.get(columnId).iterator();
while (it1.hasNext()) {
GroupOpFilter target = it1.next();
Iterator<DataSetOp> it2 = lookupCurrent.getOperationList().iterator();
while (it2.hasNext()) {
DataSetOp next = it2.next();
if (next == target.groupOp && target.drillDown == drillDown) {
it1.remove();
it2.remove();
opFound = true;
}
}
}
}
if (_groupOpsSelected.containsKey(columnId)) {
Iterator<GroupOpFilter> it1 = _groupOpsSelected.get(columnId).iterator();
while (it1.hasNext()) {
GroupOpFilter target = it1.next();
Iterator<DataSetGroup> it2 = lookupCurrent.getOperationList(DataSetGroup.class).iterator();
while (it2.hasNext()) {
DataSetGroup next = it2.next();
if (next == target.groupOp && target.drillDown == drillDown) {
it1.remove();
next.getSelectedIntervalList().clear();
next.getGroupFunctions().clear();
next.getSelectedIntervalList().addAll(target.intervalList);
next.getGroupFunctions().addAll(target.groupFunctions);
opFound = true;
}
}
}
}
return opFound;
}
protected String _getSourceColumnId(String columnId) {
if (lastLookedUpDataSet != null) {
DataColumn column = lastLookedUpDataSet.getColumnById(columnId);
if (column != null && column.getGroupFunction() != null) {
String sourceId = column.getGroupFunction().getSourceId();
if (sourceId != null) {
return sourceId;
}
}
}
for (List<GroupOpFilter> currentSelections : _groupOpsSelected.values()) {
for (GroupOpFilter groupOpFilter : currentSelections) {
GroupFunction gf = groupOpFilter.groupOp.getGroupFunction(columnId);
if (gf != null) {
return gf.getSourceId();
}
}
}
return columnId;
}
protected static class GroupOpFilter {
DataSetGroup groupOp;
boolean drillDown = false;
List<GroupFunction> groupFunctions;
List<Interval> intervalList;
private GroupOpFilter(DataSetGroup op, boolean drillDown) {
this.groupOp = op;
this.drillDown = drillDown;
this.groupFunctions = new ArrayList<GroupFunction>(op.getGroupFunctions());
this.intervalList = new ArrayList<Interval>(op.getSelectedIntervalList());
}
public String toString() {
StringBuilder out = new StringBuilder();
out.append("drillDown(").append(drillDown).append(") ");
if (groupOp != null) out.append("groupOp(").append(groupOp).append(")");
return out.toString();
}
}
}