看起来您想同时使用 ActivePivot 的两个高级功能(分析维度以公开同一聚合的多个结果,动态聚合以聚合以不同货币表示的金额)。
通过配置和几行代码注入,每一个都相当容易设置。但是要交叉使用两者,您需要了解后处理器评估的内部结构,并在正确的位置注入业务逻辑。
这是一个基于 ActivePivot 4.3.3 的示例。它是在开源沙盒应用程序中编写的,因此您可以在将其适应您自己的项目之前快速运行它。
首先,我们需要一个简单的分析维度来保存可能的参考货币:
package com.quartetfs.pivot.sandbox.postprocessor.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import com.quartetfs.biz.pivot.cube.hierarchy.axis.impl.AAnalysisDimension;
import com.quartetfs.fwk.QuartetExtendedPluginValue;
/**
*
* An analysis dimension bearing the
* list of possible reference currencies.
*
* @author Quartet FS
*
*/
@QuartetExtendedPluginValue(interfaceName = "com.quartetfs.biz.pivot.cube.hierarchy.IDimension", key = ReferenceCurrencyDimension.TYPE)
public class ReferenceCurrencyDimension extends AAnalysisDimension {
/** serialVersionUID */
private static final long serialVersionUID = 42706811331081328L;
/** Default reference currency */
public static final String DEFAULT_CURRENCY = "EUR";
/** Static list of non-default possible reference currencies */
public static final List<Object[]> CURRENCIES;
static {
List<Object[]> currencies = new ArrayList<Object[]>();
currencies.add(new Object[] {"USD"});
currencies.add(new Object[] {"GBP"});
currencies.add(new Object[] {"JPY"});
CURRENCIES = Collections.unmodifiableList(currencies);
}
/** Plugin type */
public static final String TYPE = "REF_CCY";
/** Constructor */
public ReferenceCurrencyDimension(String name, int ordinal, Properties properties, Set<String> measureGroups) {
super(name, ordinal, properties, measureGroups);
}
@Override
public Object getDefaultDiscriminator(int levelOrdinal) { return DEFAULT_CURRENCY; }
@Override
public Collection<Object[]> buildDiscriminatorPaths() { return CURRENCIES; }
@Override
public int getLevelsCount() { return 1; }
@Override
public String getLevelName(int levelOrdinal) {
return levelOrdinal == 0 ? "Currency" : super.getLevelName(levelOrdinal);
}
@Override
public String getType() { return TYPE; }
}
然后是后处理器本身,一个定制的动态聚合后处理器修改为处理分析维度并多次输出相同的聚合,每个参考货币一次。
package com.quartetfs.pivot.sandbox.postprocessor.impl;
import java.util.List;
import java.util.Properties;
import com.quartetfs.biz.pivot.IActivePivot;
import com.quartetfs.biz.pivot.ILocation;
import com.quartetfs.biz.pivot.ILocationPattern;
import com.quartetfs.biz.pivot.aggfun.IAggregationFunction;
import com.quartetfs.biz.pivot.cellset.ICellSet;
import com.quartetfs.biz.pivot.cube.hierarchy.IDimension;
import com.quartetfs.biz.pivot.cube.hierarchy.axis.IAxisMember;
import com.quartetfs.biz.pivot.impl.Location;
import com.quartetfs.biz.pivot.postprocessing.impl.ADynamicAggregationPostProcessor;
import com.quartetfs.biz.pivot.postprocessing.impl.ADynamicAggregationProcedure;
import com.quartetfs.biz.pivot.query.IQueryCache;
import com.quartetfs.biz.pivot.query.aggregates.IAggregatesRetriever;
import com.quartetfs.biz.pivot.query.aggregates.RetrievalException;
import com.quartetfs.fwk.QuartetException;
import com.quartetfs.fwk.QuartetExtendedPluginValue;
import com.quartetfs.pivot.sandbox.service.impl.ForexService;
import com.quartetfs.tech.type.IDataType;
import com.quartetfs.tech.type.impl.DoubleDataType;
/**
* Forex post processor with two features:
* <ul>
* <li>Dynamically aggregates amounts in their native currencies into reference currency
* <li>Applies several reference currencies, exploded along an analysis dimension.
* </ul>
*
* @author Quartet FS
*/
@QuartetExtendedPluginValue(interfaceName = "com.quartetfs.biz.pivot.postprocessing.IPostProcessor", key = ForexPostProcessor.TYPE)
public class ForexPostProcessor extends ADynamicAggregationPostProcessor<Double> {
/** serialVersionUID */
private static final long serialVersionUID = 15874126988574L;
/** post processor plugin type */
public final static String TYPE = "FOREX";
/** Post processor return type */
private static final IDataType<Double> DATA_TYPE = new DoubleDataType();
/** Ordinal of the native currency dimension */
protected int nativeCurrencyDimensionOrdinal;
/** Ordinal of the native currency level */
protected int nativeCurrencyLevelOrdinal;
/** Ordinal of the reference currencies dimension */
protected int referenceCurrenciesOrdinal;
/** forex service*/
private ForexService forexService;
/** constructor */
public ForexPostProcessor(String name, IActivePivot pivot) {
super(name, pivot);
}
/** Don't forget to inject the Forex service into the post processor */
public void setForexService(ForexService forexService) {
this.forexService = forexService;
}
/** post processor initialization */
@Override
public void init(Properties properties) throws QuartetException {
super.init(properties);
nativeCurrencyDimensionOrdinal = leafLevelsOrdinals.get(0)[0];
nativeCurrencyLevelOrdinal = leafLevelsOrdinals.get(0)[1];
IDimension referenceCurrenciesDimension = getDimension("ReferenceCurrencies");
referenceCurrenciesOrdinal = referenceCurrenciesDimension.getOrdinal();
}
/**
* Handling of the analysis dimension:<br>
* Before retrieving leaves, wildcard the reference currencies dimension.
*/
protected ICellSet retrieveLeaves(ILocation location, IAggregatesRetriever retriever) throws RetrievalException {
ILocation baseLocation = location;
if(location.getLevelDepth(referenceCurrenciesOrdinal-1) > 0) {
Object[][] array = location.arrayCopy();
array[referenceCurrenciesOrdinal-1][0] = null; // wildcard
baseLocation = new Location(array);
}
return super.retrieveLeaves(baseLocation, retriever);
}
/**
* Perform the evaluation of the post processor on a leaf (as defined in the properties).
* Here the leaf level is the UnderlierCurrency level in the Underlyings dimension .
*/
@Override
protected Double doLeafEvaluation(ILocation leafLocation, Object[] underlyingMeasures) throws QuartetException {
// Extract the native and reference currencies from the evaluated location
String currency = (String) leafLocation.getCoordinate(nativeCurrencyDimensionOrdinal-1, nativeCurrencyLevelOrdinal);
String refCurrency = (String) leafLocation.getCoordinate(referenceCurrenciesOrdinal-1, 0);
// Retrieve the measure in the native currency
double nativeAmount = (Double) underlyingMeasures[0];
// If currency is reference currency or measureNative is equal to 0.0 no need to convert
if ((currency.equals(refCurrency)) || (nativeAmount == .0) ) return nativeAmount;
// Retrieve the rate and rely on the IQueryCache
// in order to retrieve the same rate for the same currency for our query
IQueryCache queryCache = pivot.getContext().get(IQueryCache.class);
Double rate = (Double) queryCache.get(currency + "_" + refCurrency);
if(rate == null) {
Double rateRetrieved = forexService.retrieveQuotation(currency, refCurrency);
Double rateCached = (Double) queryCache.putIfAbsent(currency + "_" + refCurrency, rateRetrieved);
rate = rateCached == null ? rateRetrieved : rateCached;
}
// Compute equivalent in reference currency
return rate == null ? nativeAmount : nativeAmount * rate;
}
@Override
protected IDataType<Double> getDataType() { return DATA_TYPE; }
/** @return the type of this post processor, within the post processor extended plugin. */
@Override
public String getType() { return TYPE; }
/**
* @return our own custom dynamic aggregation procedure,
* so that we can inject our business logic.
*/
protected DynamicAggregationProcedure createProcedure(ICellSet cellSet, IAggregationFunction aggregationFunction, ILocationPattern pattern) {
return new DynamicAggregationProcedure(cellSet, aggregationFunction, pattern);
}
/**
* Custom dynamic aggregation procedure.<br>
* When the procedure is executed over a leaf location,
* we produce several aggregates instead of only one:
* one aggregate for each of the visible reference currencies.
*/
protected class DynamicAggregationProcedure extends ADynamicAggregationProcedure<Double> {
protected DynamicAggregationProcedure(ICellSet cellSet, IAggregationFunction aggregationFunction, ILocationPattern pattern) {
super(ForexPostProcessor.this, aggregationFunction, cellSet, pattern);
}
/**
* Execute the procedure over one row of the leaf cell set.
* We compute one aggregate for each of the reference currencies.
*/
@Override
public boolean execute(ILocation location, int rowId, Object[] measures) {
if(location.getLevelDepth(referenceCurrenciesOrdinal-1) > 0) {
// Lookup the visible reference currencies
IDimension referenceCurrenciesDimension = pivot.getDimensions().get(referenceCurrenciesOrdinal);
List<IAxisMember> referenceCurrencies = (List<IAxisMember>) referenceCurrenciesDimension.retrieveMembers(0);
for(IAxisMember member : referenceCurrencies) {
Object[][] array = location.arrayCopy();
array[referenceCurrenciesOrdinal-1][0] = member.getDiscriminator();
ILocation loc = new Location(array);
super.execute(loc, rowId, measures);
}
return true;
} else {
return super.execute(location, rowId, measures);
}
}
@Override
protected Double doLeafEvaluation(ILocation location, Object[] measures) throws QuartetException {
return ForexPostProcessor.this.doLeafEvaluation(location, measures);
}
}
}
在多维数据集的描述中,分析维度和后处理器将如下所示:
...
<dimension name="ReferenceCurrencies" pluginKey="REF_CCY" />
...
<measure name="cross" isIntrospectionMeasure="false">
<postProcessor pluginKey="FOREX">
<properties>
<entry key="id" value="pv.SUM" />
<entry key="underlyingMeasures" value="pv.SUM" />
<entry key="leafLevels" value="UnderlierCurrency@Underlyings" />
</properties>
</postProcessor>
</measure>
...