Агрегирование на конечном уровне ActivePivot и измерение анализа

скажем, у меня есть куб ActivePivot с фактами, содержащими только значение и валюту. скажем, мой куб имеет Валюту как обычное измерение.

Мы заполняем куб фактами, имеющими множество валют.

У нас есть форекс-сервис, который принимает валюту и базовую валюту для получения курса.

Теперь Value.SUM не имеет никакого смысла, мы суммируем значения в разных валютах, поэтому нам нужен постпроцессор, который может конвертировать все значения в эталонную валюту, скажем, в доллары США, а затем суммировать их, поэтому мы пишем постпроцессор, который расширяет ADynamicAggregationPostProcessor, указывает валюту в качестве измерения конечного уровня и использует службу форекс для выполнения преобразования, и мы довольны.

Но, скажем, мы не хотим конвертировать только в доллары США, мы хотим конвертировать в 10 разных валют и видеть результаты рядом друг с другом на экране. Итак, мы создаем измерение Analysis, скажем, ReferenceCurrency, с 10 элементами.

Мой вопрос: как я могу изменить вышеуказанный постпроцессор для обработки измерения анализа? Простой ванильный ADynamicAggregationPostProcessor не обрабатывает измерения анализа, этому постпроцессору виден только элемент по умолчанию. Другие постпроцессоры, обрабатывающие измерения анализа, такие как DefaultAggregatePostProcessor, не имеют средств для указания конечных уровней, поэтому я не могу получить агрегаты по валюте и, следовательно, не могу выполнить конвертацию валют. Как я могу получить свой торт и съесть его?


person Hamid    schedule 27.09.2012    source источник


Ответы (2)


Похоже, вы хотите одновременно использовать две расширенные функции 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>
...
person Antoine CHAMBILLE    schedule 28.09.2012
comment
Большое спасибо, Антуан, это то, что я искал. - person Hamid; 28.09.2012

Измерения анализа настолько сложны, что их следует рассматривать отдельно от других функций вашего куба. Тогда один из способов решить вашу проблему:

  1. Добавьте первую меру, правильно расширяющуюся вдоль измерения анализа. В вашем случае он просто скопирует базовую меру вместе с ReferenceCurrency, опционально выполняя преобразование FX. Эта мера может быть использована в качестве основы для нескольких мер.

  2. Добавьте вторую меру на основе обычного динамического агрегирования. Эта вторая реализация очень проста, поскольку она не знает, что существует измерение анализа.

person blacelle    schedule 08.10.2012