/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.camel.component.salesforce.api;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.camel.Converter;
import org.apache.camel.Exchange;
import org.apache.camel.component.salesforce.api.dto.analytics.reports.AbstractReportResultsBase;
import org.apache.camel.component.salesforce.api.dto.analytics.reports.AggregateColumnInfo;
import org.apache.camel.component.salesforce.api.dto.analytics.reports.AsyncReportResults;
import org.apache.camel.component.salesforce.api.dto.analytics.reports.DetailColumnInfo;
import org.apache.camel.component.salesforce.api.dto.analytics.reports.GroupingColumnInfo;
import org.apache.camel.component.salesforce.api.dto.analytics.reports.GroupingInfo;
import org.apache.camel.component.salesforce.api.dto.analytics.reports.GroupingValue;
import org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportExtendedMetadata;
import org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportFactWithDetails;
import org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportMetadata;
import org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportRow;
import org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportStatusEnum;
import org.apache.camel.component.salesforce.api.dto.analytics.reports.SummaryValue;
/**
* Salesforce report results to <code>List<List<String>></code>converter.
*/
@Converter
public final class SalesforceReportResultsToListConverter {
public static final String INCLUDE_DETAILS = "CamelSalesforceIncludeDetails";
public static final String INCLUDE_HEADERS = "CamelSalesforceIncludeHeaders";
public static final String INCLUDE_SUMMARY = "CamelSalesforceIncludeSummary";
private static final String ROW_COUNT = "RowCount";
private static final String EMPTY_VALUE = "";
private static final List<String> EMPTY_STRING_LIST = Collections.emptyList();
private SalesforceReportResultsToListConverter() {
}
@Converter
public static List<List<String>> convertToList(final AbstractReportResultsBase reportResults,
final Exchange exchange) {
List<List<String>> results = null;
if (reportResults instanceof AsyncReportResults) {
AsyncReportResults asyncReportResults = (AsyncReportResults) reportResults;
final ReportStatusEnum status = asyncReportResults.getAttributes().getStatus();
// only successfully completed async report results have data rows
if (status != ReportStatusEnum.Success) {
throw new IllegalArgumentException("Invalid asynchronous report results status " + status);
}
}
switch (reportResults.getReportMetadata().getReportFormat()) {
case TABULAR:
results = convertTabularResults(reportResults, exchange);
break;
case SUMMARY:
results = convertSummaryResults(reportResults, exchange);
break;
case MATRIX:
results = convertMatrixResults(reportResults, exchange);
break;
default:
// ignore
}
return results;
}
private static List<List<String>> convertTabularResults(final AbstractReportResultsBase reportResults, final Exchange exchange) {
final ArrayList<List<String>> result = new ArrayList<List<String>>();
final ReportMetadata reportMetadata = reportResults.getReportMetadata();
final String[] detailColumns = reportMetadata.getDetailColumns();
final ReportExtendedMetadata reportExtendedMetadata = reportResults.getReportExtendedMetadata();
final ReportFactWithDetails factWithDetails = reportResults.getFactMap().get("T!T");
// include detail rows?
final String[] aggregates = reportMetadata.getAggregates();
if (reportResults.getHasDetailRows() && getOption(exchange, INCLUDE_DETAILS, Boolean.TRUE)) {
final int rowLength = detailColumns.length;
// include detail headers?
if (getOption(exchange, INCLUDE_HEADERS, Boolean.TRUE)) {
final List<String> headers = new ArrayList<String>(rowLength);
result.add(headers);
addColumnHeaders(headers, reportExtendedMetadata.getDetailColumnInfo(), detailColumns);
}
final ReportRow[] reportRows = factWithDetails.getRows();
result.ensureCapacity(result.size() + reportRows.length);
for (ReportRow reportRow : reportRows) {
final List<String> row = new ArrayList<String>(rowLength);
result.add(row);
addRowValues(row, reportRow.getDataCells());
}
// include summary values?
if (aggregates.length > 0 && getOption(exchange, INCLUDE_SUMMARY, Boolean.TRUE)) {
addSummaryRows(result, detailColumns, null, aggregates, factWithDetails.getAggregates());
}
} else if (aggregates.length > 0) {
final int rowLength = aggregates.length;
// include summary headers?
if (getOption(exchange, INCLUDE_HEADERS, Boolean.TRUE)) {
final List<String> headers = new ArrayList<String>(rowLength);
result.add(headers);
addColumnHeaders(headers, reportExtendedMetadata.getAggregateColumnInfo(), aggregates);
}
// add summary values
final List<String> row = new ArrayList<String>(rowLength);
result.add(row);
addRowValues(row, factWithDetails.getAggregates());
}
return result;
}
private static List<List<String>> convertSummaryResults(final AbstractReportResultsBase reportResults, Exchange exchange) {
final ArrayList<List<String>> result = new ArrayList<List<String>>();
final ReportMetadata reportMetadata = reportResults.getReportMetadata();
final ReportExtendedMetadata reportExtendedMetadata = reportResults.getReportExtendedMetadata();
final String[] aggregates = reportMetadata.getAggregates();
final boolean includeDetails = reportResults.getHasDetailRows()
&& getOption(exchange, INCLUDE_DETAILS, Boolean.TRUE);
final boolean includeSummary = aggregates.length > 0 && getOption(exchange, INCLUDE_SUMMARY, Boolean.TRUE);
// column list, including grouping columns and details if required
final ArrayList<DetailColumnInfo> columnInfos = new ArrayList<DetailColumnInfo>();
final String[] columnNames = getResultColumns(columnInfos, reportMetadata, reportExtendedMetadata,
includeDetails, includeSummary);
// include detail headers?
if (getOption(exchange, INCLUDE_HEADERS, Boolean.TRUE)) {
addColumnHeaders(result, columnInfos);
}
// process down groups
for (GroupingValue groupingValue : reportResults.getGroupingsDown().getGroupings()) {
addSummaryGroupValues(result, reportResults, columnNames, groupingValue,
EMPTY_STRING_LIST, includeDetails, includeSummary);
}
// add grand total
if (includeSummary) {
final ReportFactWithDetails grandTotal = reportResults.getFactMap().get("T!T");
addSummaryValues(result, includeDetails, columnNames, EMPTY_STRING_LIST,
aggregates, grandTotal.getAggregates());
}
return result;
}
private static List<List<String>> convertMatrixResults(final AbstractReportResultsBase reportResults, Exchange exchange) {
final ArrayList<List<String>> result = new ArrayList<List<String>>();
final ReportMetadata reportMetadata = reportResults.getReportMetadata();
final ReportExtendedMetadata reportExtendedMetadata = reportResults.getReportExtendedMetadata();
final String[] aggregates = reportMetadata.getAggregates();
final boolean includeDetails = reportResults.getHasDetailRows()
&& getOption(exchange, INCLUDE_DETAILS, Boolean.TRUE);
final boolean includeSummary = aggregates.length > 0 && getOption(exchange, INCLUDE_SUMMARY, Boolean.TRUE);
// column list, including grouping columns and details if required
final ArrayList<DetailColumnInfo> columnInfos = new ArrayList<DetailColumnInfo>();
final String[] columnNames = getResultColumns(columnInfos, reportMetadata, reportExtendedMetadata,
includeDetails, includeSummary);
// include detail headers?
if (getOption(exchange, INCLUDE_HEADERS, Boolean.TRUE)) {
addColumnHeaders(result, columnInfos);
}
// process down groups
final GroupingValue[] groupingsDown = reportResults.getGroupingsDown().getGroupings();
for (GroupingValue groupingValue : groupingsDown) {
addMatrixGroupValues(result, reportResults, columnNames, groupingValue,
EMPTY_STRING_LIST, includeDetails, includeSummary, EMPTY_VALUE, true);
}
// add grand total
if (includeSummary) {
final Map<String, ReportFactWithDetails> factMap = reportResults.getFactMap();
// first add summary for across groups
final List<String> downGroupsPrefix = new ArrayList<String>(
Collections.nCopies(groupingsDown.length, EMPTY_VALUE));
for (GroupingValue acrossGrouping : reportResults.getGroupingsAcross().getGroupings()) {
addAcrossGroupSummaryValues(result, reportMetadata,
includeDetails, columnNames, factMap, downGroupsPrefix, acrossGrouping);
}
final ReportFactWithDetails grandTotal = factMap.get("T!T");
addSummaryValues(result, includeDetails, columnNames, EMPTY_STRING_LIST,
reportResults.getReportMetadata().getAggregates(), grandTotal.getAggregates());
}
return result;
}
private static void addAcrossGroupSummaryValues(ArrayList<List<String>> result, ReportMetadata reportMetadata,
boolean includeDetails, String[] columnNames,
Map<String, ReportFactWithDetails> factMap,
List<String> downGroupsPrefix, GroupingValue acrossGrouping) {
final List<String> newDownGroupsPrefix = new ArrayList<String>(downGroupsPrefix);
newDownGroupsPrefix.add(acrossGrouping.getLabel());
addSummaryValues(result, includeDetails, columnNames, newDownGroupsPrefix,
reportMetadata.getAggregates(),
factMap.get("T!" + acrossGrouping.getKey()).getAggregates());
// process across subgroups
for (GroupingValue subGroup : acrossGrouping.getGroupings()) {
addAcrossGroupSummaryValues(result, reportMetadata, includeDetails, columnNames, factMap,
newDownGroupsPrefix, subGroup);
}
}
private static void addMatrixGroupValues(ArrayList<List<String>> result, AbstractReportResultsBase reportResults,
String[] columnNames, GroupingValue groupingValue,
List<String> rowPrefix, boolean includeDetails, boolean includeSummary,
String keyPrefix, boolean downGroup) {
final String groupKey = groupingValue.getKey();
final String newKeyPrefix = keyPrefix + groupKey;
// group values prefix
final List<String> newPrefix = new ArrayList<String>(rowPrefix);
newPrefix.add(groupingValue.getLabel());
final GroupingValue[] groupings = groupingValue.getGroupings();
// has subgroups?
if (groupings.length > 0) {
for (GroupingValue subGroup : groupings) {
addMatrixGroupValues(result, reportResults, columnNames, subGroup, newPrefix,
includeDetails, includeSummary, newKeyPrefix + "_", downGroup);
}
// process across groupings?
} else if (downGroup) {
for (GroupingValue acrossGroup : reportResults.getGroupingsAcross().getGroupings()) {
addMatrixGroupValues(result, reportResults, columnNames, acrossGroup, newPrefix,
includeDetails, includeSummary, newKeyPrefix + "!", false);
}
// add lowest level across group detail rows?
} else if (includeDetails) {
addDetailRows(result, newPrefix, reportResults.getFactMap().get(newKeyPrefix));
// add group columns only at lowest across level?
} else if (!includeSummary) {
result.add(newPrefix);
}
// add summary values for down group or lowest level across group
if (includeSummary) {
final String summaryKey = getGroupTotalKey(keyPrefix, downGroup, groupKey);
addSummaryValues(result, includeDetails, columnNames, newPrefix,
reportResults.getReportMetadata().getAggregates(),
reportResults.getFactMap().get(summaryKey).getAggregates());
}
}
private static String getGroupTotalKey(String keyPrefix, boolean downGroup, String key) {
if (downGroup) {
// keyPrefix has rows only
return keyPrefix + key + "!T";
} else {
// keyPrefix is of the form r(_r)*!(c_)*
return keyPrefix + key;
}
}
private static void addSummaryGroupValues(ArrayList<List<String>> result, AbstractReportResultsBase reportResults,
String[] columnNames, GroupingValue groupingValue,
List<String> rowPrefix, boolean includeDetails, boolean includeSummary) {
// get fact map at this level
final ReportFactWithDetails factWithDetails = reportResults.getFactMap().get(groupingValue.getKey() + "!T");
final List<String> newPrefix = new ArrayList<String>(rowPrefix);
newPrefix.add(groupingValue.getLabel());
// more groups?
final GroupingValue[] groupings = groupingValue.getGroupings();
if (groupings.length > 0) {
for (GroupingValue subGroup : groupings) {
addSummaryGroupValues(result, reportResults, columnNames, subGroup, newPrefix,
includeDetails, includeSummary);
}
// add lowest level group detail rows?
} else if (includeDetails) {
addDetailRows(result, newPrefix, factWithDetails);
// add group columns only at lowest level?
} else if (!includeSummary) {
result.add(newPrefix);
}
if (includeSummary) {
final SummaryValue[] summaryValues = factWithDetails.getAggregates();
final String[] aggregates = reportResults.getReportMetadata().getAggregates();
addSummaryValues(result, includeDetails, columnNames, newPrefix, aggregates, summaryValues);
}
}
private static void addDetailRows(ArrayList<List<String>> result, List<String> newPrefix, ReportFactWithDetails factWithDetails) {
final ReportRow[] rows = factWithDetails.getRows();
result.ensureCapacity(result.size() + rows.length);
for (ReportRow row : rows) {
final ArrayList<String> rowValues = new ArrayList<String>(newPrefix);
addRowValues(rowValues, row.getDataCells());
result.add(rowValues);
}
}
private static void addSummaryValues(ArrayList<List<String>> result, boolean includeDetails,
String[] columnNames, List<String> newPrefix,
String[] aggregates, SummaryValue[] summaryValues) {
// no summary values to add
if (summaryValues.length == 0) {
return;
}
if (includeDetails) {
// add summary rows for this group
addSummaryRows(result, columnNames, newPrefix,
aggregates, summaryValues);
} else {
// add summary values as columns for this group
final ArrayList<String> summaryRow = new ArrayList<String>(newPrefix);
// add remaining group values
final int nGroups = columnNames.length - summaryValues.length;
for (int i = summaryRow.size(); i < nGroups; i++) {
summaryRow.add(EMPTY_VALUE);
}
addRowValues(summaryRow, summaryValues);
result.add(summaryRow);
}
}
private static void addSummaryRows(List<List<String>> result,
String[] detailColumns, List<String> rowPrefix,
String[] aggregateColumns, SummaryValue[] summaryValues) {
final ArrayList<List<String>> rows = new ArrayList<List<String>>(summaryValues.length + 1);
String rowCount = null;
for (int i = 0; i < aggregateColumns.length; i++) {
final String aggregate = aggregateColumns[i];
final String valueLabel = summaryValues[i].getLabel();
if (ROW_COUNT.equals(aggregate)) {
rowCount = valueLabel;
} else {
final List<String> summaryRow = rowPrefix == null
? new ArrayList<String>() : new ArrayList<String>(rowPrefix);
rows.add(summaryRow);
// skip rowPrefix columns if not null
for (int j = rowPrefix == null ? 0 : rowPrefix.size(); j < detailColumns.length; j++) {
final String columnName = detailColumns[j];
if (aggregate.endsWith("!" + columnName)) {
final StringBuilder valueBuilder = new StringBuilder();
if (aggregate.startsWith("a!")) {
valueBuilder.append("avg ");
} else if (aggregate.startsWith("mx!")) {
valueBuilder.append("max ");
} else if (aggregate.startsWith("m!")) {
valueBuilder.append("min ");
}
valueBuilder.append(valueLabel);
summaryRow.add(valueBuilder.toString());
} else {
summaryRow.add(EMPTY_VALUE);
}
}
}
}
// add a Grand Totals separator row
final List<String> grandTotal = new ArrayList<String>();
result.add(grandTotal);
if (rowCount != null) {
grandTotal.add("Grand Totals (" + rowCount + " records)");
} else {
grandTotal.add("Grand Totals");
}
// add summary values rows
result.addAll(rows);
}
private static String[] getResultColumns(List<DetailColumnInfo> result, ReportMetadata reportMetadata,
ReportExtendedMetadata reportExtendedMetadata,
boolean includeDetails, boolean includeSummary) {
final List<String> columnNames = new ArrayList<String>();
// add grouping columns before detail columns
final Map<String, GroupingColumnInfo> groupingColumnInfos = reportExtendedMetadata.getGroupingColumnInfo();
for (GroupingInfo downGroup : reportMetadata.getGroupingsDown()) {
final String name = downGroup.getName();
columnNames.add(name);
result.add(groupingColumnInfos.get(name));
}
for (GroupingInfo acrossGroup : reportMetadata.getGroupingsAcross()) {
final String name = acrossGroup.getName();
columnNames.add(name);
result.add(groupingColumnInfos.get(name));
}
// include details?
if (!includeDetails) {
// include summary columns?
if (includeSummary) {
final Map<String, AggregateColumnInfo> aggregateColumnInfos =
reportExtendedMetadata.getAggregateColumnInfo();
for (String aggregateColumnName : reportMetadata.getAggregates()) {
columnNames.add(aggregateColumnName);
result.add(aggregateColumnInfos.get(aggregateColumnName));
}
}
} else {
// add detail columns
final Map<String, DetailColumnInfo> detailColumnInfo = reportExtendedMetadata.getDetailColumnInfo();
for (String columnName : reportMetadata.getDetailColumns()) {
columnNames.add(columnName);
result.add(detailColumnInfo.get(columnName));
}
}
return columnNames.toArray(new String[columnNames.size()]);
}
private static void addColumnHeaders(List<String> headers,
Map<String, ? extends DetailColumnInfo> columnInfos, String[] columns) {
for (String columnName : columns) {
headers.add(columnInfos.get(columnName).getLabel());
}
}
private static void addColumnHeaders(List<List<String>> result, ArrayList<DetailColumnInfo> columnInfos) {
final ArrayList<String> headers = new ArrayList<String>(columnInfos.size());
for (DetailColumnInfo info : columnInfos) {
headers.add(info.getLabel());
}
result.add(headers);
}
private static void addRowValues(List<String> row, SummaryValue[] values) {
for (SummaryValue summaryValue : values) {
row.add(summaryValue.getLabel());
}
}
private static boolean getOption(Exchange exchange, String name, Boolean defaultValue) {
return exchange.getIn().getHeader(name, defaultValue, Boolean.class);
}
}