3

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

我们用具有多种货币的事实填充立方体。

我们有一项外汇服务,它采用货币和参考货币来获得汇率。

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

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

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

4

2 回答 2

1

看起来您想同时使用 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>
...
于 2012-09-28T09:39:33.210 回答
0

Analysis Dimensions brings so much complexity they should be considered apart of the others features of your cube. One way to handle your issue is then:

  1. Add a first measure expanding properly along the analysis dimension. In your case, it will simply copy the underlying measure along ReferenceCurrency, optionally doing the FX conversion. This measure may be used as underlying of several measures.

  2. Add a second measure, based on usual dynamic aggregation. This second implementation is very simple as it does not know there is an analysis dimension.

于 2012-10-08T15:59:23.020 回答