Re: [Olap4j-devel] Listener pattern implementation for Query selections
Open Java API for OLAP
Brought to you by:
jhyde,
lucboudreau
From: Jonathan F. <fu...@sq...> - 2009-06-22 20:39:52
|
On Sun, Jun 21, 2009 at 2:29 PM, Luc Boudreau<luc...@gm...> 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 > > Luc > > > > > On 16-Jun-09, at 12:57 PM, Jonathan Fuerth wrote: > >> 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 >> >> On Tue, Jun 16, 2009 at 10:23 AM, Luc Boudreau<luc...@gm...> >> wrote: >>> >>> 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 >>> >>> >>> On Mon, Jun 15, 2009 at 6:37 PM, Luc Boudreau <luc...@gm...> >>> wrote: >>>> >>>> Thanks for your reply. I'll hold back on the commit and will investigate >>>> further tomorrow or Wednesday. >>>> >>>> Luc >>>> >>>> >>>> >>>> On 15-Jun-09, at 17:27, Jonathan Fuerth <fu...@sq...> wrote: >>>> >>>>> >>>>> On Mon, Jun 15, 2009 at 11:59 AM, Luc Boudreau<luc...@gm...> >>>>> 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 >>>>> >>>>> --~--~---------~--~----~------------~-------~--~----~ >>>>> You received this message because you are subscribed to the Google >>>>> Groups >>>>> "Wabit Developers" group. >>>>> To post to this group, send email to wab...@go... >>>>> To unsubscribe from this group, send email to >>>>> wab...@go... >>>>> For more options, visit this group at >>>>> http://groups.google.com/group/wabit-developers?hl=en >>>>> -~----------~----~----~----~------~----~------~--~--- >>>>> >>> >>> >>> >>> ------------------------------------------------------------------------------ >>> 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 >>> ola...@li... >>> https://lists.sourceforge.net/lists/listinfo/olap4j-devel >>> >>> > > |