Re: [Olap4j-devel] Listener pattern implementation for Query selections

20 views
Skip to first unread message

Luc Boudreau

unread,
Jun 15, 2009, 11:59:35 AM6/15/09
to olap4j...@lists.sourceforge.net, wabit-de...@googlegroups.com
After some research, Here is the actual solution I propose.

The current Query Model is composed of three hierarchically organized classes: Query, QueryAxis and QueryDimension. All these classes will extend a newly created abstract superclass, QueryNode. A QueryNode is basically this :

class QueryNode extends java.util.Observable

It might seem redundant at first, but I expect we'll need to override some functions of the Observable class to support properly concurrent access to the list of observers.

At first I was considering event-driven implementations, but they are all dependent on their respective technologies. We don't want olap4j to depend on AWT, SWT, Swing, or any other GUI technology. We cannot assume that users of the Query Model will use any of these. Even if they are all included in the basic JDK distributions, choosing AWT events would create lots of problems for Swing applications and vice-versa. The Observer class is the foundation of the most popular GUI libraries event handling classes, so it is a safe choice. The calling code will be responsible for bridging his GUI libraries with the Query Model's events by implementing the java.util.Observer interface.

The java.util.Observable class is very straight forward and is used with the java.util.Observer interface. The Observer interface is described here : http://java.sun.com/javase/6/docs/api/java/util/Observer.html

It has one method signature, that takes two parameters. The first parameter is a reference to the observed object. The second is any arbitrary object that the observer needs to be aware of. In our case, we'll pass a value of the newly created QueryEvent enumeration. This enumeration will contain event names that relate to the QueryModel, such as SELECTIONS_CHANGED or AXIS_CHANGED and such.

One last thing is uncertain. If an event is triggered in one of the lower objects of the QueryModel hierarchy, let's say a member selection was performed on a QueryDimension object,  the parent classes would also trigger an event? Simply put, should the parent classes also trigger an event if one of their child does? This could facilitate a lot the usage, but trigger many redundant events. We can't really expect calling code to register as a listener on every dimension of the query, right? Yet we need to be able to observe a given QueryDimension if this is really what we want. Some GUI would rather observe individual QueryDimension and some would be simpler if they observed the query as a whole. As long as the QueryEvent object passed as a parameter reflects the actual operation and can easily point to the originating context, it would be very easy for observers to ignore irrelevant events. 

This solution is the least intrusive and more flexible I could think of, and it should fulfill it's role properly.

Insights?

_____________________________
Luc Boudreau


On Thu, Jun 11, 2009 at 5:15 AM, Julian Hyde <jh...@pentaho.com> wrote:
Sounds good to me -- but I'm not much of an expert on UI libraries. Can you provide a sketch of the intended API before you do the work? I don't know whether there are design decisions to be made -- e.g. about how to chain listeners, what happens when a listener throws an exception -- but it would help if you state 'I'm aiming to make this look like AWT listeners' (choose a UI library that you think does the job well).


From: Luc Boudreau [mailto:lucbo...@gmail.com]
Sent: Wednesday, June 10, 2009 1:36 PM
To: olap4j...@lists.sourceforge.net
Subject: [Olap4j-devel] Listener pattern implementation for Query selections


We've had a few requests to implement the listener pattern for the Query Model's selections. This is due to users developing thick clients over olap4j's Query Model. This issue was not raised previously, simply because most of our users were developing thin web clients.

So I started browsing the code and analysing the situation. Here are my findings.

The current Query Model objects require the calling code to :
  • Obtain a Member object from whatever means. This part is not done by the Query Model. The usual way of doing this is to use a Cube object and invoke it's lookupMember() method.
  • Take the Query object, find in it's collection of QueryDimension objects the proper dimension that contains the member we searched for in the earlier step, and invoke it's createSelection() method that returns a Selection object.
  • Access directly the QueryDimension selections collection (absolute yuck...) and add the Selection object we obtained at the previous step.
There really is no point in expecting the client code to perform all those operations. Not only is it tedious work that could be pushed down inside the QueryDimension code, it is a terrible violation of the encapsulation principle. Add to this the fact that implementing listeners with such a flawed design would be pretty much useless and would not be triggered half of the time.

My proposition is this.

  • I will refactor and encapsulate properly all operations required to perform selections and push them down into the QueryDimension object; where it belongs.
  • Create an enumeration of events that describe the lifecycle of the selection mechanism.
  • Add method hooks for potential listeners.
  • Refector the selection code and notify the registered listeners.
Insights?
_____________________________
Luc Boudreau

Luc Boudreau

unread,
Jun 15, 2009, 4:24:27 PM6/15/09
to olap4j...@lists.sourceforge.net, wabit-de...@googlegroups.com

I'm about to commit the changes listed in my previous emails. I had to do a major cleanup of the OlapTest class. It used to perform some calls that are now restricted to package-only classes. It won't really matter, because those calls were used to generate debug output that was only printed to the StdOut.

Performing selections on a query object is now *way* simpler, but I could not preserve retro-compatibility. Before I commit my changes, I'll give everyone using the SNAPSHOT builds a grace period. I won't do the actual commit before tomorrow. Mostly anything that used the Query package should be broken afterwards, but fear not, it's for the better.

If you need examples of the new way to perform selections, here are a few examples.

Select the Store Sales member on a QueryDimension

QueryDimension measuresDimension = query.getDimension("Measures");
measuresDimension.select("Measures", "Store Sales");


Select the children of a member on a QueryDimension

QueryDimension productDimension = query.getDimension("Product");
productDimension.select(Selection.Operator.CHILDREN, "Product", "Drink");



I also added hooks for listeners. Query, QueryAxis and QueryDimension are now all subclasses of java.util.Observable. Any object that implements java.util.Observer can register himself as a listener. After a bit of diggin' arround, I found out that java.util.Observable itself is thread-safe. That does not mean that implementations of java.util.Observer are, so it is the responsability of the observers to make sure thay conform to it.

Cheers!
_____________________________
Luc Boudreau

Luc Boudreau

unread,
Jun 15, 2009, 4:39:41 PM6/15/09
to olap4j...@lists.sourceforge.net, wabit-de...@googlegroups.com

I'll try to attach to this email the modified API specification. I have no idea how big attachments can be with this mailing list, so here goes nothing...

_____________________________
Luc Boudreau
olap4j_api.pdf

Jonathan Fuerth

unread,
Jun 15, 2009, 5:27:02 PM6/15/09
to wabit-de...@googlegroups.com
On Mon, Jun 15, 2009 at 11:59 AM, Luc Boudreau<lucbo...@gmail.com> wrote:
> After some research, Here is the actual solution I propose.

Hi Luc,

Thanks for putting the thought and effort into this problem. It's
definitely something that will make "thick client" tools like Wabit
easier to write. Our current use of the olap4j query package in Wabit
essentially adds a wrapper around the Query object that adds event
functionality much like you've described here (also hiding the mutable
list of children!).

Here's my feedback on your proposal:

> The current Query Model is composed of three hierarchically organized
> classes: Query, QueryAxis and QueryDimension. All these classes will extend
> a newly created abstract superclass, QueryNode. A QueryNode is basically
> this :
>
> class QueryNode extends java.util.Observable
>
> It might seem redundant at first, but I expect we'll need to override some
> functions of the Observable class to support properly concurrent access to
> the list of observers.

Giving these three classes a common interface or base class does make
sense to me too, but actual use of the Observable/Observer interfaces
has been out of fashion for a number of years now. Following the newer
idiom, the QueryNode interface would look something like this:

abstract class QueryNode {
public abstract String getName();

// this can be in addition to the more type-specific
// child list accessors in subclasses
public abstract List<QueryNode> getChildNodes();

public void addQueryNodeListener(QueryNodeListener l);
public void removeQueryNodeListener(QueryNodeListener l);

protected final void fireChildNodesAdded(QueryNode addedNode) {
// create a QueryEvent object and deliver it to all listeners...
}
protected final void fireChildNodesRemoved(QueryNode removedNode) { ... }
}

There's a bit of a wrinkle here if Selection isn't a QueryNode, since
Selections can be the children in these events. I don't see an issue
with making Selection a QueryNode, though.

Listeners (observers) would implement the QueryListener interface:

interface QueryListener {
void childNodeAdded(QueryEvent e);
void childNodeRemoved(QueryEvent e);
}

The actual event object should be immutable. It would look like this:

class QueryEvent {
private final QueryNode source;
private final QueryEventType eventType;
private final QueryNode childAddedOrRemoved;

// constructor and public getters
}

The key here is that listeners know *which* child(ren) were just added
to or removed from the QueryNode. Otherwise, listeners would have to
contain error-prone code that caches the previous state of each object
they're listening to, and then perform diffs against that state to
recover the changes. Not a good situation.

Further to that, it's often essential to know *where* the children
were added within the child list. This information can be recovered in
a listener by looking for the index of each added child, but it's
harder to recover the indices of removed objects. This leads back to
the situation of error-prone code in listeners.

To me, there's an important decision to make about the event
structure: either include the child objects and their position,
issuing an insert or remove event for each child, OR provide an API
where multiple insertions or deletions can be reported in a single
event. I think the former is the better choice.

It is possible to combine the two (provide the child objects and their
indices), but in my experience this type of API is sufficiently
complicated that it invites junior programmers to cultivate bugs. I'd
only take this approach if the individual notifications became a
noticeable (and then measured and proven) performance issue.

> At first I was considering event-driven implementations, but they are all
> dependent on their respective technologies. We don't want olap4j to depend
> on AWT, SWT, Swing, or any other GUI technology. We cannot assume that users
> of the Query Model will use any of these. Even if they are all included in
> the basic JDK distributions, choosing AWT events would create lots of
> problems for Swing applications and vice-versa. The Observer class is the
> foundation of the most popular GUI libraries event handling classes, so it
> is a safe choice. The calling code will be responsible for bridging his GUI
> libraries with the Query Model's events by implementing the
> java.util.Observer interface.

I agree 100% that pulling in pieces of AWT and/or Swing would be a
mistake. Even something as seemingly-harmless as using
java.awt.Dimension (a simple (width,height) tuple) has caused us pain
on headless production servers. Its constructors secretly load native
GUI libraries!

> The java.util.Observable class is very straight forward and is used with the
> java.util.Observer interface. The Observer interface is described here :
> http://java.sun.com/javase/6/docs/api/java/util/Observer.html

I think there are two main things that have led people to abandon Observable:

1. Your object has to extend Observable to use it
2. It doesn't provide type safety in its notifications (no prescribed
type of event object)

The reusable event system which has stood the test of time is the one
in java.beans (PropertyChangeListener and PropertyChangeEvent). This
system is certainly not without its drawbacks, but its pattern is
familiar to AWT and Swing developers, since all the events in those
packages follow its example.

The showstopper for PropertyChangeEvents from the olap4j point of view
is that they don't work very well for "compound" properties (like
lists and sets), which is really the only thing olap4j wants from an
event system.

> One last thing is uncertain. If an event is triggered in one of the lower
> objects of the QueryModel hierarchy, let's say a member selection was
> performed on a QueryDimension object,  the parent classes would also trigger
> an event? Simply put, should the parent classes also trigger an event if one
> of their child does? This could facilitate a lot the usage, but trigger many
> redundant events. We can't really expect calling code to register as a
> listener on every dimension of the query, right? Yet we need to be able to
> observe a given QueryDimension if this is really what we want. Some GUI
> would rather observe individual QueryDimension and some would be simpler if
> they observed the query as a whole. As long as the QueryEvent object passed
> as a parameter reflects the actual operation and can easily point to the
> originating context, it would be very easy for observers to ignore
> irrelevant events.

We've had to make the same decision over and over. On each occasion,
we've chosen not to propagate the events up the tree. I think we've
been making the right call. I can provide supporting arguments if
necessary. :)

> This solution is the least intrusive and more flexible I could think of, and
> it should fulfill it's role properly.
>
> Insights?

I think the best way to check if the system serves its purpose is to
try to use it. In this case, since the query model is a tree-like
structure, making an implementation of javax.swing.tree.TreeModel that
properly translate QueryNode events into TreeModelEvents would be a
valuable exercise. Similarly, representing the current contents of a
QueryAxis in a ListModel (again, propagating changes as
ListDataEvents) would be worth the time. We will also refactor Wabit
to use the new features of the query API, and provide further
feedback.

Thanks again for taking a crack at this! I see you've posted a new
message that the Observer-based implementation is ready since I
started working on this response. I'll take a look.

-Jonathan

Luc Boudreau

unread,
Jun 15, 2009, 6:37:13 PM6/15/09
to wabit-de...@googlegroups.com, olap4j...@lists.sourceforge.net
Thanks for your reply. I'll hold back on the commit and will
investigate further tomorrow or Wednesday.

Luc

Luc Boudreau

unread,
Jun 16, 2009, 10:23:11 AM6/16/09
to wabit-de...@googlegroups.com, olap4j...@lists.sourceforge.net
Hello,

Your approach to this problem is interesting. My implementation of listeners was, maybe naively, much simpler than that. The way I coded it, there is no way of knowing exactly which children was moved, added or removed. It simply notifies the listeners that something has changed and they should refresh and figure out what it is. Along with this notification comes an enumeration value that describes in a coarse way what is the nature of the change.

I preferred to keep the code in olap4j as simple and dumb as possible so not to create too much overhead. I really don't want the query model to become too entangled with the requirements of think clients, yet still provide a accessors that allow client code to figure out the best possible way to keep in sync with the Query Model. Trying to solve all synchronization challenges in olap4j is not the right approach in my opinion. I've browsed through many projects source code and there is really no clear consensus on this issue. Everyone handles events in his own way and adds bridges between the query model and the GUI, using many different event patterns and frameworks. Olap4j's query model is only required to notify the listeners that "something has changed", and it exposes enough information on it's internal structure to allow client code to figure out what it is. I might be wrong though, maybe it IS the logical place to handle this. Maybe the query model should keep a track of every object in it's structure and notify properly the listeners.

About static typing of events, you're absolutely right. I hacked through the Observable code to see if there was a way to make it more "generic", and there really isn't. I would not mind dropping this object altogether for that. It's a terrible flaw. I'd rather write my own code, but I needed some insights first.

About propagating the events up the tree, I'd really like to know what kind of problems are created by this feature. I can understand that client code would have to ignore many events that are generated downstream and do not concern the higher levels of the tree, but is this still a problem if the events are very simple in their nature? I mean, consider my previous assumption that the event system at olap4j's level has to be as simple as possible (the events are of the sort SELECTION_CHANGED or QUERY_CHANGED values), is it still necessary to block the events of making it up the tree?

_____________________________
Luc Boudreau

Jonathan Fuerth

unread,
Jun 16, 2009, 12:57:37 PM6/16/09
to Luc Boudreau, wabit-de...@googlegroups.com, olap4j...@lists.sourceforge.net
Hi again Luc.

I don't want to come across as overstating my case, but the event
model you've described is too weak to be of practical use for a Swing
GUI. I think if you go through the exercise of implementing a
TreeModel or ListModel on top of it, you will come to one of two
outcomes:

1. you haven't used the coarse-grained events at all, and you've
essentially wrapped the query model in something that implements what
I described yesterday.

2. your implementation will be maintaining a copy of the query model's
state, performing diff operations between its copy and the real model
every time an event arrives.

The best way to do this (for overall simplicity) is for the adapter
(TreeModel, ListModel, etc) to use the query model directly without
creating its own copy of the state.

If you believe the "fancy" event model is too heavy for olap4j, my
suggestion would be to leave it as-is without any event model, and let
each client who needs events to invent their own event wrapper. This
will lead more users to outcome #1 above, whereas the availability of
the coarse-grained events will guide people into the less desirable
outcome #2.

-Jonathan
> ------------------------------------------------------------------------------
> Crystal Reports - New Free Runtime and 30 Day Trial
> Check out the new simplified licensing option that enables unlimited
> royalty-free distribution of the report engine for externally facing
> server and web deployment.
> http://p.sf.net/sfu/businessobjects
> _______________________________________________
> olap4j-devel mailing list
> olap4j...@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/olap4j-devel
>
>

Luc Boudreau

unread,
Jun 21, 2009, 2:29:15 PM6/21/09
to Jonathan Fuerth, wabit-de...@googlegroups.com, olap4j...@lists.sourceforge.net

Hello,

I spent a few hours trying to mock up a tree control that utilizes the
Query model with the slimmed down implementation. You were absolutely
right on this. The code produced on the client side is qwerky at best
and is awfully error prone. I just had to try it by myself to believe
it ;-)

That said, I think there is a way to implement the listener pattern
while still maintaining it "stateless" and thus reduce the inherent
overhead. The best way seems to create event objects that contain all
the information necessary to "track back" the changes and send the
notifications to the listeners in a synchronous manner. The Query
Model would not keep an array of past modifications and thus would
generate a minimum overhead.

I'm really no expert in GUI architecture, so the dumb question of the
day is this: does that sound sensible? Do the Query Model need to keep
a track record of all the modifications it underwent, or would a
synchronous and stateless implementation answer the calling code needs?

Luc

Jonathan Fuerth

unread,
Jun 22, 2009, 3:25:17 PM6/22/09
to Luc Boudreau, wabit-de...@googlegroups.com, olap4j...@lists.sourceforge.net
On Sun, Jun 21, 2009 at 2:29 PM, Luc Boudreau<lucbo...@gmail.com> wrote:
> I spent a few hours trying to mock up a tree control that utilizes the Query
> model with the slimmed down implementation. You were absolutely right on
> this. The code produced on the client side is qwerky at best and is awfully
> error prone. I just had to try it by myself to believe it ;-)

I'm the same way. It's much easier to understand (and believe!) when
you've tried it yourself.

> That said, I think there is a way to implement the listener pattern while
> still maintaining it "stateless" and thus reduce the inherent overhead. The
> best way seems to create event objects that contain all the information
> necessary to "track back" the changes and send the notifications to the
> listeners in a synchronous manner. The Query Model would not keep an array
> of past modifications and thus would generate a minimum overhead.

That's right--there's no need to remember past state. And that's why
it's critical that events are delivered synchronously.

> I'm really no expert in GUI architecture, so the dumb question of the day is
> this: does that sound sensible? Do the Query Model need to keep a track
> record of all the modifications it underwent, or would a synchronous and
> stateless implementation answer the calling code needs?

The latter. An event model with the following qualities is "normal"
and completely sufficient for gluing the data model to the GUI:

-events are delivered just after each modification has been made
-the event object describes the state of the affected part(s) of the
model before and after the modification

These qualities let the model<->GUI adapter be a completely stateless
wrapper around the model, which is definitely what you want if bugs
are not your thing. :)

The questions that don't have such cut-and-dried answers are about
whether to include the ability for a single event to describe multiple
child additions or deletions on the same parent at the same time, as
well as whether events from further down the model to bubble up to
ancestors. There are tradeoffs to consider with each of these choices.

Thanks for continuing to work on this. I really appreciate all the
time and thought you're putting into it!

-Jonathan
Reply all
Reply to author
Forward
0 new messages