Version 4: Filtering Capabilities

Tuesday, January 24, 2012
You can download this version of the sample, and an improved version of the component library from here.


Screenshot of the sample application showing a filtered query and filter editor.
The logic used in previous versions to generate MDX expressions for query axis (ROWS, COLUMNS, CHAPTERS, etc.) cannot be used against the FILTER axis. One of the reasons for this is that it's based on the drill state of displayed members. Furthermore the semantics associated to those axes are completely different from the semantics of the slicer axis. Specifically the operator argument to include and exclude methods in the QueryHierarchy class has no sense in a slicer axis, as including a MEMBER will aggregate the measures of its DESCENDANTS, and is equivalent to aggregate all of its CHILDREN.
So I've decide to change the behavior of some QueryHierarchy's methods when the instance lives in a slicer axis (hierarchy.geAxis().getLocation() == Axis.FILTER):
  • include(Member m, Operator op), exclude(Member m, Operator op)Both methods ignore the op argument and assume Operator.DESCENDANTS instead.
  • isIncluded(Member m)Returns true if and only if m and all of its descendants are included.
  • isExcluded(Member m)
    (new)
    Returns true if and only if m and all of its descendants are excluded. Equivalente to !isIncluded(m) for instances living in query axes.
I've also changed the <olap:queryHierarchyEditor> faces component, changing its appearance and behavior when editing a query hierarchy living in a slicer axis:
  • The operator dropdown menu disappears
  • The include/exclude buttons are replaced by tri-state checkbox buttons, checked when the member is included, unchecked when it's excluded, or mixed if neither isIncluded(m)
    nor isExcluded(m) return true.
I've created a new faces composite component to show the contents of the slicer axis (active filters for the current query) and allow them to remove hierarchies from that axe. It' a table with a row for each hierarchy showing the hierarchy's caption in the first column and a descriptive text of the filter in the second column. This text lists the included members for simple query expressions and shows an informative label to indicate a filter too complex to be detailed.

MDX Generation for WHERE clause

The package private method toOlap4j in the QueryAxis class delegates in the new toOlap4jFilter() method when generating the MDX expression for a slicer axis. This method generates a cross join of the expressions returned by the olap4jFilter() method for each hierarchy in the axis:
    private AxisNode toOlap4jFilter() {

        CrossJoinBuilder xJoin = new CrossJoinBuilder();

        for(QueryHierarchy h : hierarchies) {

            xJoin.join(h.toOlap4jFilter());

        }

        return
new AxisNode(null, false, axis, null, xJoin.getJoinNode());

    }



The MDX expression for a silcer QueryHierarchy is computed recursively based on the tree of included/excluded members:
  • Simple case: current node has no overriding children.The resulting expression is the MemberNode for the current member if it's included, void expression if it's not included.
  • Recursive case: current node has overriding children.
    • If the current node is excluded, return the union of the recursive expressions for every overriding children
    • If the current node is included, return the union of
      • computing the set of non-overriding children as EXCEPT(<currentNode>.CHILDREN, <set of overriding children>)
      • computing the union of the recursive expression for every overriding children
This is the current implementation; with an immersion parameter to carry the current descendants include/exclude sign.
    private
ParseTreeNode toOlap4jFilter(SelectionTree selectionNode,


            Sign defaultSign) {

        Sign selectionSign = selectionNode.getStatus().getEffectiveSign(

                Operator.DESCENDANTS, defaultSign);



        if (!selectionNode.hasOverridingChildren()) {

            // Current node has no overriding children, its filter expression is

            // the corresponding MemberNode if the member is included, void in

            // other case.

            if (selectionSign == Sign.INCLUDE)

                return Mdx.member(selectionNode.getMember());

            else

                return
null;

        } else {

            // Current node has overriding children



            UnionBuilder finalExpression = new UnionBuilder();

            if (selectionSign == Sign.INCLUDE) {

                // Current node is included, so overriding children are excluded

                // or have excluded descendants.



                UnionBuilder overridingChildren = new UnionBuilder();

                for (SelectionTree overriding : selectionNode

                        .getOverridingChildren()) {

                    overridingChildren.add(Mdx.member(overriding.getMember()));

                    finalExpression.add(toOlap4jFilter(overriding,

                            selectionSign));

                }



                // Return the set of non overriding children plus recursive

                // expression evaluations

                finalExpression.add(Mdx.except(

                        Mdx.children(selectionNode.getMember()),

                        overridingChildren));

            } else {

                // Current node is excluded, returns the union of recursive

                // evaluation for overriding children.

                for (SelectionTree overriding : selectionNode

                        .getOverridingChildren()) {

                    finalExpression.add(toOlap4jFilter(overriding,

                            selectionSign));

                }

            }

            return finalExpression.getUnionNode();

        }

    }

FilterAxisInfo Composite Faces Component

The component to display the filter axis for the current query hides a standard <h:dataTable> and relays on a managed bean to compute the filter expression to generate the textual description of the filter and adapt the call to remove a hierarchy from the filter axis. This component has the following attributes:
  • value (instance of AbstractQueryBean)
    The query containing the axis to display (used to remove a hierarchy from the axis)
  • style, styleClass
    CSS style and CSS style class to be applied to the component
  • headerClassCSS style class to be applied to the column showing the hierarchy captions.
  • expressionClassCSS style class to be applied to the column showing the filter descriptions.
It generates a client event when the user removes a hierarchy from the filter axis. This code snippet is from the sample test page:
        <h:panelGroup
id="filterAxisInfo"
>

            <olap:filterAxisInfo
value="#{queryBean}">

                <f:ajax
render=":form:filterAxisInfo :form:table :form:hierarchyEditor"/>

            </olap:filterAxisInfo>

        </h:panelGroup>


It uses default style classes and uses the client event to re-render itself, the result set table and the query hierarchy editor after filter hierarchy removal.