【问题标题】:ActivePivot leaf level aggregation and Analysis dimensionActivePivot 叶级聚合和分析维度
【发布时间】:2012-09-27 18:18:30
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

    标签: analysis dimension activepivot


    【解决方案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>
    ...
    

    【讨论】:

    • 非常感谢 Antoine,这正是我要找的。​​span>
    【解决方案2】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2013-01-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-20
      • 2017-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多