2012-09-27 40 views
3

可以说我拥有一个ActivePivot立方体,事实上只包含Value和Currency。 可以说我的魔方具有货币作为常规维度。ActivePivot叶级别聚合和分析维

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

我们有一个外汇服务,需要货币和参考货币来获得利率。

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

但是,可以说我们不想仅转换为美元,我们希望转换为10种不同的货币,并在屏幕上看到彼此相邻的结果。 因此,我们创建了一个分析维度,称为ReferenceCurrency,其中包含10个成员。

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

回答

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="[email protected]" /> 
     </properties> 
    </postProcessor> 
</measure> 
... 
+0

非常感谢Antoine,这就是我一直在寻找的东西。 – Hamid

0

分析尺寸带来了如此多的复杂性,他们应该考虑的其他功能分开你的魔方。处理您的问题的一种方法是:

  1. 添加沿分析维度正确展开的第一个度量。就你而言,它将简单地沿着ReferenceCurrency复制基础度量,可选地进行FX转换。这项措施可以作为几项措施的基础。

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