com.squid.kraken.v4.api.core.EngineUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.squid.kraken.v4.api.core.EngineUtils.java

Source

/*******************************************************************************
 * Copyright  Squid Solutions, 2016
 *
 * This file is part of Open Bouquet software.
 *  
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation (version 3 of the License).
 *
 * There is a special FOSS exception to the terms and conditions of the 
 * licenses as they are applied to this program. See LICENSE.txt in
 * the directory of this program distribution.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Squid Solutions also offers commercial licenses with additional warranties,
 * professional functionalities or services. If you purchase a commercial
 * license, then it supersedes and replaces any other agreement between
 * you and Squid Solutions (above licenses and LICENSE.txt included).
 * See http://www.squidsolutions.com/EnterpriseBouquet/
 *******************************************************************************/
package com.squid.kraken.v4.api.core;

import java.text.ParseException;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;

import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.squid.core.domain.IDomain;
import com.squid.core.expression.ExpressionAST;
import com.squid.core.expression.scope.ScopeException;
import com.squid.kraken.v4.core.analysis.engine.hierarchy.DimensionIndex;
import com.squid.kraken.v4.core.analysis.engine.hierarchy.DimensionIndex.Status;
import com.squid.kraken.v4.core.analysis.engine.hierarchy.DimensionMember;
import com.squid.kraken.v4.core.analysis.engine.hierarchy.DimensionOptionUtils;
import com.squid.kraken.v4.core.analysis.engine.hierarchy.DomainFacetCompute;
import com.squid.kraken.v4.core.analysis.engine.hierarchy.DomainHierarchy;
import com.squid.kraken.v4.core.analysis.engine.hierarchy.SegmentManager;
import com.squid.kraken.v4.core.analysis.engine.processor.ComputingException;
import com.squid.kraken.v4.core.analysis.model.DashboardSelection;
import com.squid.kraken.v4.core.analysis.model.DomainSelection;
import com.squid.kraken.v4.core.analysis.model.Intervalle;
import com.squid.kraken.v4.core.analysis.model.IntervalleObject;
import com.squid.kraken.v4.core.analysis.universe.Axis;
import com.squid.kraken.v4.core.analysis.universe.Universe;
import com.squid.kraken.v4.core.expression.scope.DimensionDefaultValueScope;
import com.squid.kraken.v4.core.expression.scope.ExpressionEvaluator;
import com.squid.kraken.v4.model.AccessRight.Role;
import com.squid.kraken.v4.model.Dimension.Type;
import com.squid.kraken.v4.model.DimensionOption;
import com.squid.kraken.v4.model.DimensionPK;
import com.squid.kraken.v4.model.Domain;
import com.squid.kraken.v4.model.Facet;
import com.squid.kraken.v4.model.FacetMember;
import com.squid.kraken.v4.model.FacetMemberInterval;
import com.squid.kraken.v4.model.FacetMemberString;
import com.squid.kraken.v4.model.FacetSelection;
import com.squid.kraken.v4.persistence.AppContext;

/**
 * provide methods to convert API FacetSelection into Engine DashboardSelection
 *  
 * @author sergefantino
 *
 */
public class EngineUtils {

    enum Bound {
        LOWER, UPPER
    }

    static final Logger logger = LoggerFactory.getLogger(EngineUtils.class);

    private static EngineUtils instance;

    public static EngineUtils getInstance() {
        if (instance == null) {
            synchronized (EngineUtils.class) {
                if (instance == null) {
                    instance = new EngineUtils();
                }
            }
        }
        return instance;
    }

    /**
     * return the facetID: support legacy code
     * @param facet
     * @return
     */
    public Object getFacetID(Facet facet) {
        if (facet.getId() == null) {
            return facet.getDimensionId();
        } else {
            return facet.getId();
        }
    }

    /**
     * compare the two facet using the first facet identifiction method
     * @param selFacet
     * @param facet
     * @return
     */
    public boolean compareFacets(Facet selFacet, Facet facet) {
        if (selFacet.getId() == null) {
            return selFacet.getDimensionId().equals(facet.getDimensionId());
        } else {
            return selFacet.getId().equals(facet.getId());
        }
    }

    /**
     * return the axis for the facet
     * @param ctx
     * @param universe
     * @param facet
     * @return
     * @throws ScopeException if cannot interpret the facet
     */
    public Axis getFacetAxis(AppContext ctx, Universe universe, Facet facet) throws ScopeException {
        String facetID = facet.getId();
        if (facetID != null) {
            try {
                return universe.axis(facetID);
            } catch (ScopeException e) {
                logger.error("Invalid facet ID = " + facetID, e);
                throw new ScopeException("Invalid facet ID = " + facetID, e);
            }
        } else {
            // legacy
            DimensionPK facetPK = facet.getDimensionId();
            if (facetPK == null) {
                logger.error("Invalid facet ID");
                throw new ScopeException("Invalid facet ID");
            }
            try {
                facetPK.setCustomerId(ctx.getCustomerId());
                return universe.axis(facetPK);
            } catch (Exception e) {
                logger.error("Invalid facet ID = " + facetPK.toString(), e);
                throw new ScopeException("Invalid facet ID = " + facetPK.toString(), e);
            }
        }
    }

    public Axis getFacetAxis(AppContext ctx, Universe universe, String expression) throws ScopeException {
        if (expression != null) {
            try {
                return universe.axis(expression);
            } catch (ScopeException e) {
                logger.error("Invalid facet ID = " + expression, e);
                throw new ScopeException(
                        "Invalid facet ID = " + expression + " caused by " + e.getLocalizedMessage(), e);
            }
        } else {
            throw new ScopeException("Invalid facet, ID is null");
        }
    }

    /**
     * Convert the facet value into a date. 
     * If the value start with '=', it is expected to be a Expression, in which case we'll try to resolve it to a Date constant.
     * @param ctx
     * @param index
     * @param lower 
     * @param value
     * @param compareFromInterval 
     * @return
     * @throws ParseException
     * @throws ScopeException
     * @throws ComputingException 
     */
    public Date convertToDate(Universe universe, DimensionIndex index, Bound bound, String value,
            IntervalleObject compareFromInterval) throws ParseException, ScopeException, ComputingException {
        if (value.equals("")) {
            return null;
        } else if (value.startsWith("__")) {
            //
            // support hard-coded shortcuts
            if (value.toUpperCase().startsWith("__COMPARE_TO_")) {
                // for compareTo
                if (compareFromInterval == null) {
                    // invalid compare_to selection...
                    return null;
                }
                if (value.equalsIgnoreCase("__COMPARE_TO_PREVIOUS_PERIOD")) {
                    LocalDate localLower = new LocalDate(((Date) compareFromInterval.getLowerBound()).getTime());
                    if (bound == Bound.UPPER) {
                        LocalDate date = localLower.minusDays(1);
                        return date.toDate();
                    } else {
                        LocalDate localUpper = new LocalDate(
                                ((Date) compareFromInterval.getUpperBound()).getTime());
                        Days days = Days.daysBetween(localLower, localUpper);
                        LocalDate date = localLower.minusDays(1 + days.getDays());
                        return date.toDate();
                    }
                }
                if (value.equalsIgnoreCase("__COMPARE_TO_PREVIOUS_MONTH")) {
                    LocalDate localLower = new LocalDate(((Date) compareFromInterval.getLowerBound()).getTime());
                    LocalDate compareLower = localLower.minusMonths(1);
                    if (bound == Bound.LOWER) {
                        return compareLower.toDate();
                    } else {
                        LocalDate localUpper = new LocalDate(
                                ((Date) compareFromInterval.getUpperBound()).getTime());
                        Days days = Days.daysBetween(localLower, localUpper);
                        LocalDate compareUpper = compareLower.plusDays(days.getDays());
                        return compareUpper.toDate();
                    }
                }
                if (value.equalsIgnoreCase("__COMPARE_TO_PREVIOUS_YEAR")) {
                    LocalDate localLower = new LocalDate(((Date) compareFromInterval.getLowerBound()).getTime());
                    LocalDate compareLower = localLower.minusYears(1);
                    if (bound == Bound.LOWER) {
                        return compareLower.toDate();
                    } else {
                        LocalDate localUpper = new LocalDate(
                                ((Date) compareFromInterval.getUpperBound()).getTime());
                        Days days = Days.daysBetween(localLower, localUpper);
                        LocalDate compareUpper = compareLower.plusDays(days.getDays());
                        return compareUpper.toDate();
                    }
                }
            } else {
                // for regular
                // get MIN, MAX first
                Intervalle range = null;
                if (index.getDimension().getType() == Type.CONTINUOUS) {
                    if (index.getStatus() == Status.DONE) {
                        List<DimensionMember> members = index.getMembers();
                        if (!members.isEmpty()) {
                            DimensionMember member = members.get(0);
                            Object object = member.getID();
                            if (object instanceof Intervalle) {
                                range = (Intervalle) object;
                            }
                        }
                    } else {
                        try {
                            DomainHierarchy hierarchy = universe
                                    .getDomainHierarchy(index.getAxis().getParent().getDomain());
                            hierarchy.isDone(index, null);
                        } catch (ComputingException | InterruptedException | ExecutionException
                                | TimeoutException e) {
                            throw new ComputingException("failed to retrieve period interval");
                        }
                    }
                }
                if (range == null) {
                    range = IntervalleObject.createInterval(new Date(), new Date());
                }
                if (value.equalsIgnoreCase("__ALL")) {
                    if (index.getDimension().getType() != Type.CONTINUOUS) {
                        return null;
                    }
                    if (bound == Bound.UPPER) {
                        return (Date) range.getUpperBound();
                    } else {
                        return (Date) range.getLowerBound();
                    }
                }
                if (value.equalsIgnoreCase("__LAST_DAY")) {
                    if (bound == Bound.UPPER) {
                        return (Date) range.getUpperBound();
                    } else {
                        return (Date) range.getUpperBound();
                    }
                }
                if (value.equalsIgnoreCase("__LAST_7_DAYS")) {
                    if (bound == Bound.UPPER) {
                        return (Date) range.getUpperBound();
                    } else {
                        LocalDate localUpper = new LocalDate(((Date) range.getUpperBound()).getTime());
                        LocalDate date = localUpper.minusDays(6);// 6+1
                        return date.toDate();
                    }
                }
                if (value.equalsIgnoreCase("__CURRENT_MONTH")) {
                    if (bound == Bound.UPPER) {
                        return (Date) range.getUpperBound();
                    } else {
                        LocalDate localUpper = new LocalDate(((Date) range.getUpperBound()).getTime());
                        LocalDate date = localUpper.withDayOfMonth(1);
                        return date.toDate();
                    }
                }
                if (value.equalsIgnoreCase("__CURRENT_YEAR")) {
                    if (bound == Bound.UPPER) {
                        return (Date) range.getUpperBound();
                    } else {
                        LocalDate localUpper = new LocalDate(((Date) range.getUpperBound()).getTime());
                        LocalDate date = localUpper.withMonthOfYear(1).withDayOfMonth(1);
                        return date.toDate();
                    }
                }
                if (value.equalsIgnoreCase("__PREVIOUS_MONTH")) {// the previous complete month
                    if (bound == Bound.UPPER) {
                        LocalDate localUpper = new LocalDate(((Date) range.getUpperBound()).getTime());
                        LocalDate date = localUpper.withDayOfMonth(1).minusDays(1);
                        return date.toDate();
                    } else {
                        LocalDate localUpper = new LocalDate(((Date) range.getUpperBound()).getTime());
                        LocalDate date = localUpper.withDayOfMonth(1).minusMonths(1);
                        return date.toDate();
                    }
                }
                if (value.equalsIgnoreCase("__PREVIOUS_YEAR")) {// the previous complete month
                    if (bound == Bound.UPPER) {
                        LocalDate localUpper = new LocalDate(((Date) range.getUpperBound()).getTime());
                        LocalDate date = localUpper.withMonthOfYear(1).withDayOfMonth(1).minusDays(1);
                        return date.toDate();
                    } else {
                        LocalDate localUpper = new LocalDate(((Date) range.getUpperBound()).getTime());
                        LocalDate date = localUpper.withMonthOfYear(1).withDayOfMonth(1).minusYears(1);
                        return date.toDate();
                    }
                }
            }
            throw new ScopeException("undefined facet expression alias: " + value);
        } else if (value.startsWith("=")) {
            // if the value starts by equal token, this is a formula that can be
            // evaluated
            try {
                String expr = value.substring(1);
                // check if the index content is available or wait for it
                DomainHierarchy hierarchy = universe.getDomainHierarchy(index.getAxis().getParent().getDomain(),
                        true);
                hierarchy.isDone(index, null);
                // evaluate the expression
                Object defaultValue = evaluateExpression(universe, index, expr, compareFromInterval);
                // check we can use it
                if (defaultValue == null) {
                    //throw new ScopeException("unable to parse the facet expression as a constant: " + expr);
                    // T1769: it's ok to return null
                    return null;
                }
                if (!(defaultValue instanceof Date)) {
                    throw new ScopeException("unable to parse the facet expression as a date: " + expr);
                }
                // ok, it's a date
                return (Date) defaultValue;
            } catch (ComputingException | InterruptedException | ExecutionException | TimeoutException e) {
                throw new ComputingException("failed to retrieve period interval");
            }
        } else {
            Date date = ServiceUtils.getInstance().toDate(value);
            if (bound == Bound.UPPER
                    && !index.getAxis().getDefinitionSafe().getImageDomain().isInstanceOf(IDomain.TIME)) {
                // clear the timestamp
                return new LocalDate(date.getTime()).toDate();
            } else {
                return date;
            }
        }
    }

    private Object evaluateExpression(Universe universe, DimensionIndex index, String expr,
            IntervalleObject compareFromInterval) throws ScopeException {
        try {
            DimensionDefaultValueScope scope = new DimensionDefaultValueScope(universe.getContext(), index);
            if (compareFromInterval != null) {
                scope.addParam("UPPER", IDomain.DATE);
                scope.addParam("LOWER", IDomain.DATE);
            }
            ExpressionAST defaultExpression = scope.parseExpression(expr);
            ExpressionEvaluator evaluator = new ExpressionEvaluator(universe.getContext());
            // provide sensible default for MIN & MAX -- we don't want the parser to fail is not set
            Calendar calendar = Calendar.getInstance();
            Date current = calendar.getTime();
            current = new java.sql.Date(current.getTime());// in order to serialize as iso8601
            evaluator.setParameterValue("MAX", current);
            // there is no sensible default for MIN
            evaluator.setParameterValue("MIN", null);
            // handle compareFrom interval values if available
            if (compareFromInterval != null) {
                evaluator.setParameterValue("LOWER", compareFromInterval.getLowerBound());
                evaluator.setParameterValue("UPPER", compareFromInterval.getUpperBound());
            }
            return evaluator.evalSingle(defaultExpression);
        } catch (ScopeException e) {
            throw new ScopeException(
                    "unable to parse the facet expression: " + expr + "\n" + e.getLocalizedMessage(), e);
        }
    }

    /**
     * Apply a facet selection to a dashboard.
     * 
     * @param universe
     * @param dashboard
     * @param selection
     * @throws ComputingException 
     * @throws ScopeException 
     * @throws InterruptedException 
     */
    public DashboardSelection applyFacetSelection(AppContext ctx, Universe universe, List<Domain> domains,
            FacetSelection selection) throws ComputingException, ScopeException, InterruptedException {
        //
        DashboardSelection ds = new DashboardSelection();
        //
        if (selection != null) {
            addFacetSelection(ctx, universe, selection.getFacets(), ds, null);
            // T994 support the compareFacets
            if (selection.hasCompareFacets()) {
                DashboardSelection compare = new DashboardSelection();
                addFacetSelection(ctx, universe, selection.getCompareTo(), compare, ds);
                // we let the ds.compare chack that the selection is valid
                for (DomainSelection s : compare.get()) {
                    for (Axis filter : s.getFilters()) {
                        for (DimensionMember member : s.getMembers(filter)) {
                            ds.addCompareTo(filter, member);
                        }
                    }
                }
            }
        }
        //
        // krkn-61: handling dimension options
        for (Domain domain : domains) {
            DomainHierarchy hierarchy = universe.getDomainHierarchy(domain, true);
            for (DimensionIndex index : hierarchy.getDimensionIndexes()) {
                DimensionOption option = DimensionOptionUtils.computeContextOption(index.getDimension(), ctx);
                if (option != null) {
                    // handling dimension option
                    //
                    Collection<DimensionMember> sel = ds.getMembers(index.getAxis());
                    // is SingleSelection
                    if (option.isSingleSelection() && sel.size() > 1) {
                        throw new ScopeException(
                                "Dimension '" + index.getDimensionName() + "' does not allow multi-selection");
                    }
                    // is unmodifiable
                    if (option.isUnmodifiableSelection()) {
                        // if no default value, this is a bug in the meta-model
                        if (option.getDefaultSelection() == null) {
                            throw new ScopeException("Dimension '" + index.getDimensionName()
                                    + "' is unmodifiable but does not set a default value - report the isue to the application support");
                        }
                        // check if the default value is selected
                        if (!sel.isEmpty()) {
                            ds.clear(index.getAxis());
                            sel = ds.getMembers(index.getAxis());
                        }
                    }
                    // default selection
                    if (sel.isEmpty() && option.getDefaultSelection() != null) {
                        List<DimensionMember> defaultMembers = DimensionOptionUtils.computeDefaultSelection(index,
                                option, ctx);
                        if (defaultMembers.isEmpty()) {
                            // ?
                        } else {
                            ds.add(index.getAxis(), defaultMembers);
                            sel = ds.getMembers(index.getAxis());
                        }
                        //
                    }
                    // is mandatory
                    if (sel.isEmpty() && option.isMandatorySelection()) {
                        DomainFacetCompute compute = new DomainFacetCompute(universe);
                        List<DimensionMember> members = compute.populateDimensionFacets(index, ds, null, 0, 1);
                        if (!members.isEmpty()) {
                            ds.add(index.getAxis(), members.get(0));
                            sel = ds.getMembers(index.getAxis());
                        }
                        //throw new ScopeException("Dimension '"+index.getDimensionName()+"' cannot be unselected");
                    }
                }
            }
        }
        //
        return ds;
    }

    /**
     * initialise the DashboardSelection
     * @param ctx
     * @param universe
     * @param facetsSel
     * @param ds : the output selection
     * @param compareFrom : if the output selection is a compareTo, you can provide the current selection in order to resolve $LOWER and $UPPER parameters
     * @throws ScopeException
     * @throws ComputingException
     * @throws InterruptedException
     */
    private void addFacetSelection(AppContext ctx, Universe universe, List<Facet> facetsSel, DashboardSelection ds,
            DashboardSelection compareFrom) throws ScopeException, ComputingException, InterruptedException {
        for (Facet facetSel : facetsSel) {
            if (SegmentManager.isSegmentFacet(facetSel)) {
                SegmentManager.addSegmentSelection(ctx, universe, facetSel, ds);
            } else {
                Axis axis = getFacetAxis(ctx, universe, facetSel);
                if (axis != null) {
                    Domain domain = axis.getParent().getRoot();
                    DimensionIndex index = universe.getDomainHierarchy(domain, true).getDimensionIndex(axis);
                    if (index == null) {
                        if (axis.getDimension() != null) {
                            Domain shortcut = axis.getParent().getDomain();
                            index = universe.getDomainHierarchy(shortcut, true).getDimensionIndex(axis.prune());
                        } else {
                            throw new ScopeException(
                                    "unable to filter on " + axis.getDefinitionSafe().prettyPrint());
                        }
                    }
                    AccessRightsUtils.getInstance().checkRole(ctx, index.getDimension(), Role.READ);
                    for (FacetMember selectedItem : facetSel.getSelectedItems()) {
                        if (selectedItem instanceof FacetMemberInterval) {
                            IntervalleObject compareFromInterval = null;
                            if (compareFrom != null) {
                                Collection<DimensionMember> members = compareFrom.getMembers(axis);
                                if (members.size() == 1) {
                                    DimensionMember member = members.iterator().next();
                                    if (member.getID() instanceof IntervalleObject) {
                                        compareFromInterval = (IntervalleObject) member.getID();
                                    }
                                }
                            }
                            FacetMemberInterval fmi = (FacetMemberInterval) selectedItem;
                            try {
                                Date lowerDate = convertToDate(universe, index, Bound.LOWER, fmi.getLowerBound(),
                                        compareFromInterval);
                                Date upperDate = convertToDate(universe, index, Bound.UPPER, fmi.getUpperBound(),
                                        compareFromInterval);
                                // add as a Date Interval
                                // T1769: if lower&upper are null, this is no-op
                                ds.add(axis, IntervalleObject.createInterval(lowerDate, upperDate));
                            } catch (java.text.ParseException e) {
                                throw new ComputingException(e);
                            }
                        } else if (selectedItem instanceof FacetMemberString) {
                            FacetMemberString fmember = (FacetMemberString) selectedItem;
                            if (fmember.getId() != null && !fmember.getId().equals("")
                            //&& !fmember.getId().equals("-1") // support legacy drill-down // end of legacy support !
                                    && facetSel.getId() != null) {// to support legacy
                                // if we provide the ID, it's safe to use it...
                                DimensionMember member = index.getMemberByKey(fmember.getId());
                                if (member != null) {
                                    ds.add(axis, member);
                                } else if (fmember.getValue() != null) {
                                    // ticket:2992
                                    // if the value is defined, try to get a member
                                    member = index.getMemberByID(fmember.getValue());
                                    if (member != null) {
                                        fmember.setId(member.getKey());// update the facet Id
                                        ds.add(axis, member);
                                    } else {
                                        throw new ComputingException("invalid selection, unkonwn index value");
                                    }
                                } else {
                                    throw new ComputingException("invalid selection, unkonwn index reference");
                                }
                            } else if (fmember.getValue() != null) {
                                // ticket:2992
                                // if the value is defined, try to get a member
                                DimensionMember member = index.getMemberByID(fmember.getValue());
                                if (member != null) {
                                    fmember.setId(member.getKey());// update the facet Id
                                    ds.add(axis, member);
                                } else {
                                    throw new ComputingException("invalid selection, unkonwn index value");
                                }
                            } else {
                                throw new ComputingException("invalid selection, undefine index");
                            }
                        }
                    }
                } else {
                    logger.info("ignoring invalid facet selection");
                }
            }
        }
    }

}