ActivePivot叶级聚合和分析维

时间:2012-09-27 18:18:31

标签: analysis dimension activepivot

假设我有一个ActivePivot多维数据集,其中的事实只包含Value和Currency。 假设我的多维数据集将Currency作为常规维度。

我们用多种货币的事实填充多维数据集。

我们提供外汇服务,使用货币和参考货币来获得汇率。

现在,Value.SUM没有任何意义,我们正在添加不同货币的值,所以我们希望有一个后处理器可以将所有值转换为参考货币,比如美元,然后求和,所以我们编写一个扩展 ADynamicAggregationPostProcessor 的后处理器,将Currency指定为叶级维度,并使用forex服务进行转换,我们很高兴。

但是,假设我们不想仅转换为美元,我们希望转换为10种不同的货币,并在屏幕上看到彼此相邻的结果。 因此,我们创建了一个Analysis维度,比如ReferenceCurrency,有10个成员。

我的问题是:如何更改上述后处理器以处理Analysis维度?普通的 ADynamicAggregationPostProcessor 不处理Analysis维度,只有默认成员对此后处理器可见。处理Analysis维度的其他后处理器(如 DefaultAggregatePostProcessor )没有指定叶级别的方法,因此我无法通过Currency获取聚合,因此无法进行外汇转换。我怎么能吃蛋糕呢?

2 个答案:

答案 0 :(得分:1)

您希望同时使用ActivePivot的两个高级功能(分析维度可以显示同一聚合的多个结果,动态聚合以汇总以不同货币表示的数量)。

另外,每个人都可以通过配置和几行代码进行设置。但要交错使用,您需要了解后处理器评估的内部结构,并在正确的位置注入业务逻辑。

以下是基于ActivePivot 4.3.3的示例。它已经在开源Sandbox应用程序中编写,因此您可以在将其调整到您自己的项目之前快速运行它。

首先,我们需要一个简单的分析维度来保存可能的参考货币:

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>
...

答案 1 :(得分:0)

分析维度带来了如此多的复杂性,应该将它们视为多维数据集的其他功能。处理问题的一种方法是:

  1. 添加沿分析维度正确展开的第一个度量。在您的情况下,它将简单地复制ReferenceCurrency中的基础度量,可选择执行FX转换。该措施可用作若干措施的基础。

  2. 根据通常的动态聚合添加第二个度量。第二种实现非常简单,因为它不知道存在分析维度。