Live Demo!!

Saturday, February 25, 2012
I've just set installed a live demo running the current SNAPSHOT version of the project.

http://demo.desarrolloagil.es/olap-faces/test.jsf

Version 6: Hierarchy Expansion, let’s drill-up

Wednesday, February 22, 2012
You can download this version of the sample, and an improved version of the component library from here.

This version includes the capability to expand/collapse a hierarchy in the cellset table. An expanded hierarchy initially shows all of its members, and the user can drill-up to hide details. A collapsed hierarchy initially shows only its root members, and the user can drill-down to show further detail.

Expanded Hierarchy: Store Type hierarchy expanded with some undrilled members

I've modified the queryCellSet composite component (and QueryCellSetBean, its associated managed bean) to include a hierarchy expansion button in the header of both cellset axes, allowing the user toggling the expand state for a hierarchy.

This functionality is supported by three methods in the QueryAxis class:

  • void expandHierarchy(QueryHierarchy h)Expands the hierarchy, invalidating any previous drill/undrill operation on every appearance of h in this axis
  • void collapseHierarchy(QueryHierarchy h)
    Collapses the hierarchy, invalidating any previous drill/undrill operation on every appearance of h in this axis
  • boolean isExpanded(QueryHierarchy h)
    Tests if the provided hierarchy is expanded in this axis.

QueryHierarchy Expression Generation

The strategy to MDX generation for an expanded hierarchy is as follows:
  • A fully expanded hierarchy generates this MDX:
    DESCENDANTS(<root members set>, 0, SELF_AND_AFTER)
  • Member undrill is implemented by an external EXCEPT:
    EXCEPT(
        DESCENDANTS(<root members set>, 0, SELF_AND_AFTER),
        DESCENDANTS(<undrilled members set>, 0, AFTER)
    )

Refactoring Axis Set Expression Generation
Previously, axis MDX expression generation was driven by the drill tree. I've refactored this logic to let the axis expression generation be driven by the QueryHierarchy inclusion/exclusion tree.
This is a simplified version of the function to generate an axis set expression for a QueryHierarchy

  /**
   * Recursively generates the set expression for a Query Hierarchy.
   *
   *
@param current
   *            currently visited node
   *
@param expander
   *            helper object to execute drills/expansions
   *
@param drillList
   *            members to drill/undrill
   *
@param expression
   *            generated expression
   */
 
@SuppressWarnings("unused")
 
private void toOlap4jQueryDidactic(VisitingInfo current,
      HierarchyExpander expander, List<Member> drillList,
      AxisExpression expression
) {
   
boolean isMemberIncluded =
        current.getEffectiveSign
(Operator.MEMBER) == Sign.INCLUDE;
   
boolean areChildrenIncluded =
        current.getEffectiveSign
(Operator.CHILDREN) == Sign.INCLUDE;
   
boolean areDescendantsIncluded =
        current.getEffectiveSign
(Operator.DESCENDANTS) == Sign.INCLUDE;


   
// Processes current member, including or excluding it from the
    // expression as necessary.
   
Member currentMember = current.getMember();
   
if (isMemberIncluded) {
     
expression.include(currentMember);
     
if (expander.isDrilled(currentMember, drillList)) {
       
// Current member is included in the hierarchy but collapsed.
        // Ends this visit as we should not include any descendant.
       
return;
     
}
    }

   
// Recursively calls toOlap4jQueryDidactic on overrided children
   
List<Member> overridedChildren = new ArrayList<Member>();
   
for (SelectionTree overridedChild : current.getNode()
                       
.getOverridingChildren()) {
     
overridedChildren.add(overridedChild.getMember());

      VisitingInfo childVisit = current.visitChild
(overridedChild);
      toOlap4jQueryDidactic
(
         
childVisit,
          expander,
          drillList,
          expression
);
   
}

   
if (areDescendantsIncluded) {
     
// Expand/undrill descendants
     
MemberSet expansionBase;
     
if (areChildrenIncluded) {
       
expansionBase = new ChildrenMemberSet(currentMember,
            overridedChildren
);
     
} else {
       
expansionBase = new GrandchildrenSet(currentMember,
            overridedChildren
);
     
}
     
expander.expand(expansionBase, drillList, expression);
   
} else {
     
// Include children if necessary
     
if (areChildrenIncluded) {
       
MemberSet nonOverridingChildren
          =
new ChildrenMemberSet(currentMember, overridedChildren);
        expression.include
(nonOverridingChildren.getMdx());
     
}
    }
  }
}

Version 5: Refining Hierarchy Selection UI and Query Axis Editing

Thursday, February 02, 2012

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



Sample page including the <olap:queryAxisEditor> component
This version replaces the previous listbox and buttons used to add hierarchies to the query axes with a new composite component that:
  • Displays the current cube's hierarchies organized by the axis where they are used. Unused hierarchies are displayed in an "Unused"
    axis.
  • Allows for hierarchy sorting (within the ROWS and COLUMN axes).
  • Allows hierarchy removal from an axis.
  • Allows moving used hierarchies to other query axes.
  • Keeps a selected hierarchy (in the sample page this selected hierarchy is the one displayed by the query hierarchy editor)
  • Includes a button to swap rows and columns axes.
  • Allows selecting of the non-empty state for ROWS and COLUMNS axes.
All this functionality is added to the sample page with the following facelet snippet
<olap:queryAxisEditor
id="axisEditor"
query="#{queryBean}"
value="#{queryBean.selectedHierarchyName}">
<f:ajax
event="change"
execute=":form:axisEditor"
render=":form:axisEditor :form:hierarchyEditor"/>
<f:ajax
event="edit"
render="@all"/>
</olap:queryAxisEditor>

<olap:queryAxisEditor> Composite Component Internals

The queryAxisEditor composite component takes the following parameters:
  • query: AbstractQueryBean derived managed bean keeping the query state
  • value: backing bean property used to keep the current selected hierarchy name
This component can fire two client side events:
  • change: when the user changes the currently selected hierarchy
  • edit: when the user clicks on any action button (remove, add to rows, add to columns, add to filter, push up or push down) changing the current query.
This is the facelet used to implement this composite component
<composite:interface>
<composite:attribute
name="query"
/>
<composite:attribute
name="value"
/>
<composite:attribute
name="styleClass"
/>
<composite:attribute
name="headerClass"
/>
<composite:attribute
name="rowClasses"
/>
<composite:clientBehavior
name="change"
default="true"
event="click"
targets="columns rows filter unused"
/>
<composite:clientBehavior
name="edit"
event="edit"
targets="columns rows filter unused"
/>
<composite:clientBehavior
name="edit"
targets="swap"
/>
</composite:interface>
<composite:implementation>
<div
id="#{cc.attrs.clientId}">
<h:commandButton
id="swap"
styleClass="cgaofHierarchyListSwapBtn"
value="Swap Axes"
action="#{queryAxisEditorBean.swapAxes(cc.attrs.query)}"/>
<h:inputHidden
id="selected"
value="#{cc.attrs.value}"/>
<olap:queryAxisEditorBase
query="#{cc.attrs.query}"
value="#{cc.attrs.value}"
valueControl="#{cc.attrs.clientId}:selected"
axisName="COLUMNS"
id="columns"
styleClass="#{cc.attrs.styleClass}"
headerClass="#{cc.attrs.headerClass}"
rowClasses="#{cc.attrs.rowClasses}"/>
<olap:queryAxisEditorBase
query="#{cc.attrs.query}"
value="#{cc.attrs.value}"
valueControl="#{cc.attrs.clientId}:selected"
axisName="ROWS"
id="rows"
styleClass="#{cc.attrs.styleClass}"
headerClass="#{cc.attrs.headerClass}"
rowClasses="#{cc.attrs.rowClasses}"/>
<olap:queryAxisEditorBase
query="#{cc.attrs.query}"
value="#{cc.attrs.value}"
valueControl="#{cc.attrs.clientId}:selected"
axisName="FILTER"
id="filter"
rendered="#{cc.attrs.query.slicerHierarchies.size() > 0}"
styleClass="#{cc.attrs.styleClass}"
headerClass="#{cc.attrs.headerClass}"
rowClasses="#{cc.attrs.rowClasses}"/>
<olap:queryAxisEditorBase
query="#{cc.attrs.query}"
value="#{cc.attrs.value}"
valueControl="#{cc.attrs.clientId}:selected"
id="unused"
styleClass="#{cc.attrs.styleClass}"
headerClass="#{cc.attrs.headerClass}"
rowClasses="#{cc.attrs.rowClasses}"/>
</div>
</composite:implementation>

I'm using an inputHidden control to keep the selected hierarchy name: its value is changed by the inner queryAxisEditorBase controls, responsible for displaying the contents of a single axis. Those controls receive the query bean, the current selected hierarchy name, the client id of the hidden input field and the name of the axis they are displaying. Each axis is displayed using a table:
  • The axis name and the non-empty toggle button are displayed within the table's header.
  • Each hierarchy within the axis is displayed in a row with three columns:
    • A column for the remove button.
    • A column for the hierarchy's caption. The caption includes javascript to change the selected hierarchy on clicking.
    • A column for the other action buttons: rows, columns, filter, push up and push down.
Both composite components (queryAxisEditor and queryAxisEditorBase) relay on the QueryAxisEditorBean managed bean to execute actions and simplify facelets.

Required Query Package Improvements

The improvements implemented in the query package have been straightforward and all of them consisted in implementing methods already defined by the org.olap4j.query package:
  • Query.swapAxes to allow axes swapping
  • QueryHierarchy.isNonEmpty(), QueryHierarchy.setNonEmpty() to check/set the flag controlling the generation of NON EMPTY axis.
  • QueryHierarchy.pushUp(), QueryHierarchy.pushDown()to allow moving a hierarchy within an axis.