Announcement Announcement Module
Collapse
No announcement yet.
Somethings to think about Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Somethings to think about

    About six months ago I designed and built a very lightweight framework for a Swing application. On reading through the Spring RCP User Documentation, it seems to share some ideas, although the emphases is quite different. My framework provided the application with a state machine rather than help with coding views, menus or controls.

    Commands take the machine from one state to another. The state defines the views shown and the commands that are enabled. The commands were loosely based on Struts actions, with an execute method that returns the name of the next state rather than a reference to the next JSP or action.

    The components

    Command Factory

    The command factory that manufactures command beans that the developer codes the execute method to do a particular piece of logic and return the name of the next state. Commands can also provide an isEnabled method that can disable the command even if the machine is in a state that declares that command. Controls and KeyStrokes that have disabled commands attached are disabled and controls are greyed out. The configuration wires up the keystroke and the button top text for each command. The application nominates one command as the boot command. This takes the state machine to the initial state and can be used to perform intialisation.

    The State Factory

    The state factory returns descriptions of states. A state bean holds:
    1. the set of panels,
    2. the InputMap/ActionMap for the state's commands
    3. the default button and default focus component.
    When a state becomes active, each panel will be attached to a corresponding containing panel. The application was designed with a static set of containers. For more complex applications I would have a view container factory with the state nominating a set of containers

    The Panel Factory

    The panels are coded by a developer. Panels (views) can optionally implement an interface whose methods are called when:
    1. the panel is being displayed because of a shift in state.
    2. a command is executed while a panel is shown.
    I would liked to have introduced the idea of forms being passed to panels.

    SwingAction

    An implementation of javax.swing.Action that holds a command bean as a property, and passes it to a frame's request processor when the performAction is executed. The developer gets these from the comand factory to attach commands to buttons or other controls, and the state factory uses them to attach commands to ActionMaps.

    The Request Processor

    This manages:
    1. The execution of the command
    2. The detaching and attaching of panels to containers for the new state.
    3. The attaching of the correct InputMap/ActionMap to the frame for the state.
    4. The disabling and enabling of SwingActions depending on wheither their commands are associated with the current state.
    5. The setting of the focus and the default button for the state.


    This arrangement relieves the developer of the burden of managing the display of views and coding InputMaps/ActionMaps. I'm pretty pleased with the result. It's a bit simplistic, and probably only suits certain types of application, but then it was only intended for use in a point of sale system. I understand

    The Model

    However, one thing I really feel that I got wrong was the way I dealt with the model. I went with the bound JavaBeans approach to the model. Changes to a bean's properties fire change events to any registered listeners. The listeners then re-read the property and if they are controls re-draw themselves. This sounds great, but it doesn't work well when your data model is really an object graph rather than a single bean. When your Employee bean refers to an Address bean, and you want to swap your Employee bean for another, you end up with a load of plumbing to cope with re-bind the controls that were bound to the Address bean properties. In fact what really happens (if you are as dense as me) is that you write code to get the values out of your beans and put them into controls (and visa versa). When you look at indexed properties (or the List/Map/Set properties you need for Hibernate) it just gets worse.

    I've been pondering this for a few months, and I now think I have a neat way of doing this. I have even partly coded it. It is just a shame it is all too late for the above project. However, I thought I'd share the idea. You might like it, you may think it is really old hat, or you may just laugh derisively, I don't mind.

    Here goes:

    I still using bound JavaBeans. They are generated from Hibernate mapping files, but I guess AOP could be used to fire the change events.

    Then, rather than have controls register property change listeners with the beans directly, they register with a proxy, and the proxy registers a single listener with the bean. The proxy forwards the property change events from the bean to all its listeners, who read and update the property via the proxy. Once you have a set of control classes that bind to proxies in this way, creating swing panels with bound controls becomes easy:
    Code:
    public class EmployeePanel extends JPanel {
    	private BeanProxy employeeBeanProxy;
    
    	public EmployeePanel (BeanProxy employeeBeanProxy){
    		this.employeeBeanProxy = employeeBeanProxy;
    		this.add(new TextControl(employeeBeanProxy, "forename"));
    		this.add(new TextControl(employeeBeanProxy, "surname"));
    	}
    }
    When a new bean arrives from the server, it is put into the proxy by the workflow logic.
    Code:
    	employeeBeanProxy.setBean(employeeBean);
    The proxy re-binds to the new bean and fires property change events to all the listeners to indicate that all the properties have changed.

    Here's the clever bit: to cope with a graph of beans, further proxies register listeners with the first proxy. The initial proxy becomes the root of a graph of proxies that reflects the structure of the graph of beans. When the first bean is swapped or a watched property changes, the chained proxies take the new property value (a JavaBean of course) and bind to it.
    Code:
    public class EmployeePanel extends JPanel {
    	private BeanProxy employeeBeanProxy;
    	private BeanProxy addressBeanProxy;
    
    	public EmployeePanel (BeanProxy employeeBeanProxy){
    		this.employeeBeanProxy = employeeBeanProxy;
    		this.add(new TextControl(employeeBeanProxy, "forename"));
    		this.add(new TextControl(employeeBeanProxy, "surname"));
    		this.addressBeanProxy = new BeanProxy(employeeBeanProxy, "address");
    		this.add(new TextControl(addressBeanProxy, "line1"));
    		this.add(new TextControl(addressBeanProxy, "line2"));
    		...
    	}
    }
    Still when a new employee bean arrives from the server, all the workflow logic needs to do is put it into the root bean proxy.
    Code:
    	employeeBeanProxy.setBean(employeeBean);
    This gives the controls something stable to bind to. As you can see they can be bound to proxies before any beans are created. Controls can just register with a proxy when they are created, update themselves when they get a property change event, and write any user changes back via the proxy.


    Indexed properties and Lists properties are slightly more complex, but I'm working on it. List properties require an implementation of List that fires property change events (perhaps I really should get around looking at AOP). In fact I am planning to use extend an abstract implementation of List that exposes the items in the list as an indexed property. Ultimately I want to be able to extend JTable so that it can bind to a List property like this:
    Code:
    public class CompanyPanel extends JPanel {
    	private BeanProxy companyBeanProxy;
    
    	public CompanyPanel (BeanProxy companyBeanProxy){
    		this.companyBeanProxy = companyBeanProxy;
    		this.add(new BeanTable(companyBeanProxy, "employeeList",
    			new String[]{"forename", "surname"}));
    		...
    	}
    }
    This table will display the properties from all the beans in the list, I should have then only have to do presentation coding, but nothing to do with keeping the data upto date, other that set the company bean into the proxy.

    There are further ideas circling to do with controls that look at the property type and edit it accordingly, or even including BeanInfo attributes for properties to provide validation and formatting info, but that's probably not going to fit in with RCP.

    I hope you found something interesting in this diatribe.

    Reg.

  • #2
    Reg,

    interesting post, thanks. I'd say that some form of state machine support will certainly be part of spring rich though not our initial release.

    With regards to your described model implementation we actually support all of this right now so you may want to have a closer look at our code. Check out the ValueModel and BeanPropertyAccessStrategy classes for the bean proxy functionality and BufferedCollectionValueModel for dealing with the List problem. We do automatic binding and type conversion dependent on the bound properties type (see Binding, BindingSelectionStrategy, Converter, DefaultConversionService and the various Binding implementations).

    Ollie

    Comment


    • #3
      Thanks Ollie. I will dig a bit deeper. I look forward to using Spring RCP in the future.

      Will you be providing an implementation of java.util.List that fires property change events when items in the list are changed? Hibernate's handling of relationships seems to rely on lists as properties. However, Hibernate does some naughty substitution of these lists (maps and sets) unless they implement org.hibernate.usertype.UserCollectionType. Will you provide a version for this too?

      P.S. I'm sure that with care large parts of the state machine could be made independant of Swing. So may be it could be used for say SWT (although I know little of SWT).

      Reg

      Comment


      • #4
        Will you be providing an implementation of java.util.List that fires property change events when items in the list are changed?
        Yes we do. See our ObservableList interface and ListListModel implementation.

        Ollie

        Comment


        • #5
          Concerning the state machine idea, I think this would be a very good addition to Spring-rich, but we certainly don't want to duplicate any effort. I haven't looked at Spring's WebFlow module yet, but based on its description it sounds very much like what we want. And since Keith developed it, I'm sure it is abstracted enough that we could create an implementation for Swing/UI. It would probably be less effort to adapt Spring WebFlow than develop something from scratch, but the main benefit is that we could leverage all the development and attention WebFlow gets.
          Concerning an implementation of java.util.List that fires events as the list changes, GlazedLists does this well now (and there is some integration code between GlazedLists and spring-rich already in spring-rich). GlazedLists also provides all sorts of adapter classes for both Swing and SWT UIs. Before you reinvent any wheels, please take a look at GlazedLists and see if it does what you need: http://publicobject.com/glazedlists/ - as you can see, I'm quite the GlazedLists convert.

          - Andy

          Comment


          • #6
            Thanks for the GlazedLists reference. The EventList looks like just the thing I am looking for, now I just need to get hibernate use it. Rats - single inheritance.

            I've got to say that building a gui application in terms of a state machine, can give it some much needed structure, without requiring that much actual framework code.

            Comment


            • #7
              Reg,

              I really like the ideas and concepts in your post… and it is never too late for good ideas. I just spent a week with Keith Donald to explore the possibility of using Spring Rich Client instead of our home grown framework. During this week, we had some discussions that are surprisingly close to what you have just stated, including the idea of integrating Spring Rich Client and Spring Web Flow. Keith is receptive to new ideas that strengthen the framework.

              Specifically, I agree that business applications could really benefit from being driven from a state machine similar to Spring Web Flow. We sometimes accidentally merge concepts because they are typically used together, but that can reduce the flexibility of the system (not to mention making it harder to test).

              I think that the following concepts should be completely separate.

              - The Command’s View
              - Command Events
              - Actions (commonly triggered by a Command Event)
              - Navigation (what happens after an action is complete)

              I plan to follow your lead and write up some ideas on how the architecture could work and post it to this forum for discussion. I’ll also be converting parts of my home grown rich client framework to Spring and let the code tell me what is a good idea and what isn’t. I really believe though that the navigation part of an application can be separated from the Command object and handled by a state machine.

              Jim Leask

              Comment


              • #8
                I agree. Any sizeable UI application either reinvents this wheel, or ends up a huge coding mess. If Spring-rich provided this kind of support... well... it would be unstoppable.
                In our case, our "reinvention" is that the UI is totally driven by our workflow engine (Con:cern, in our case). We use workflow for both huge processes that coordinate many resources (including people) and small flows through a UI involving a single person. One nice thing about this approach is that at any point we can hand a flow off to another user if needed. For example, you may have a workflow for setting up a new customer. This workflow drives the UI, at least until the current user is no longer able to continue with the workflow (they may not have security privaleges to determine the credit limit of the new customer, for example), so the flow gets passed off to another user (getting added to their todo list).

                - Andy

                Comment


                • #9
                  Yup - that sounds very familiar. We are doing similar things with security privaleges and complex business rules driving the navigation, as I suspect all business applications are. Our navigation problem ended up in the 'huge coding mess' category. It works (works pretty well actually), but there definitely must be a better and easier way.

                  Interestingly,some of our work flows span a web application and rich client application. I don't see any reason why we can't have both web and rich client applications driven from the same workflow engine. (throw in the mobile applications too for that matter - it is all one business workflow)

                  Jim

                  Comment


                  • #10
                    Originally posted by leask
                    Interestingly,some of our work flows span a web application and rich client application. I don't see any reason why we can't have both web and rich client applications driven from the same workflow engine.
                    Interestingly I have been pondering using the same workflow engine driving two different user interfaces, but for a slightly different reason.

                    The point-of-sale app I am working on has two UIs. One is a swing interface, the other is a two line external display. This VDU displays the name of an item being sold and it's price, or the total order amount, or the change due to the customer. (You can imagine the type of stuff).

                    So now I am thinking in terms of registering a StateListener with the state machine for each type of GUI. This would allow using the state machine with SWT or with a Web framework.

                    With this separation comes another aspect to think about. Driving the state machine, and causing the commands to be executed. Point of sale machines can be driven by external devices such as a card reader or a PIN entry keypad, but they have no UI. There are number of issues here with threads, which I am not sure how to deal with. With swing a lot of things must happen on the event dispatch thread, but these other devices will not be calling on that thread.

                    Also back with commands and states, more ideas:

                    1. Sometimes it would be good to map the result of a command to a new state outside of the command. Struts does this. Your action might indicate that the data the user entered was good, or it might indicate it was bad. The machine would go to different states depending on the result.

                    2. Lots of states share sets of commands. You don't want to be repeating them all for each state.

                    3. Some commands are available in lots of states, but at some later stage (at the end of some process), another command needs to bring us back to state it all started from. So rather than a simple "result A sends us to State X", we want to push the current state onto a stack and goto the new state. Another command result needs to be able to "pop" this state. A GUI can be affected by this. It might want to restore focus, or even text selection.

                    Comment


                    • #11
                      Wow, your project sounds exactly (exactly!) like another one I worked on a few years ago. It was POS with both a monitor (Swing UI) and 2 line display (made by a certain large company that is... blue...). This was before Spring or Spring-rich, of course, but we ended up designing an IoC XML configuration mechanism before the term IoC was in use (or even existed?) and a state machine with which to drive the UI (and inputs into the state machine could be from anything - keyboard, external device, network, etc). I didn't work on the GUI directly - my area was the XML configuration and the state machine - but your message just brought back all sorts of deja vu.

                      Comment


                      • #12
                        There is a downside to the state machine UI independance. You cannot use pop ups during commands for warnings, errors and choices without spoiling it.

                        Of course if it comes to a choice between Swing and SWT, or another local UI, then a factory can be provided for popups. However, if we are trying to reuse these operations in a web framework then you have to introduce a new state to communicate with the user.

                        This may not be a bad thing. Other developers are introducing pop ups into my commands all the time, because they can't be bothered to set up new states. So there are times that pressing a button can trigger a whole series of pop ups when the user should really be presented with another screen with all these choices on them.

                        Perhaps the points in the workflow where pop ups appear should always be configured as a 'wait for input' state. This would allow some UIs to handle them as pop ups, while a Web UI could display a whole new page.

                        Finally freeing the commands from user interaction would also mean we could introduce transactions around them, or even execute them on a server.

                        Comment


                        • #13
                          There is a downside to the state machine UI independence. You cannot use pop ups during commands for warnings, errors and choices without spoiling it.
                          I think the above is an example of the code telling us that something is wrong. In an earlier post to this thread I said
                          We sometimes accidentally merge concepts because they are typically used together…
                          I think that this happens a lot with command objects, probably because most applications only have one presentation tier and the application code accidentally gets mixed into the command objects.

                          We should keep a very clear separation between the presentation code and the application code (application service objects) – they are different. I hit this all the time because our system sometimes implements identical functionality (identical screens and operations) in both a Swing based rich client application and Struts based web application. SpringRC should live exclusively in the presentation tier of the system as should Struts.

                          Taking this view, any behavior that could be common to either the SpringRC or Struts presentation tier should be implemented in an application service object that lives at the application tier. The Struts action simply calls the application service object to do the work, and of course the SpringRC command object also simply calls the same application service object. The only thing that is left in the Spring command or Struts action is the stuff that is truly part of the presentation tier, which is surprisingly little.

                          This really doesn’t help with the original problem you mentioned about pop ups from a command spoiling the state machine. I am convinced that it can be done, but I need to try some code to explore the details of how. (please post the results if someone beats me to trying this)

                          Jim

                          Comment


                          • #14
                            More on the state machine

                            This is my first stab at how a state machine might be used. I still not clear on how I would like to define views, so I'm going to leave that out.

                            Please don't look for any sort of logical consistency in these examples, they are just random ideas to illustrate what I got in mind. Also bear in mind that I'm a spring novice who is still working through the book.

                            BTW, I've started to refer to commands/actions as operations because those terms are being used elsewhere. This should help add to the confusion :-)

                            Code:
                            <beans>
                            	<!--
                            		In an MDI application the singleton attribute would need to
                            		be set to false
                            	-->
                            
                            	<bean id="bookingStateMachine" class="org....StateMachineEngine"
                            							 singleton="false">
                            
                            		<!-- An operation to take the machine to the first state -->
                            		<property name="start">
                            			<ref bean="bootOperation"/>
                            		</property>
                            
                            		<!-- Watch for this in the context passed to operations -->
                            		<property name="document">
                            			<bean class="MyDataModel" singleton="false"/>
                            		</property>
                            	</bean>
                            
                            	<!--
                            		Wiring up an operation.  The result returned by the execute
                            		method on an operation is mapped on to the id of the next state.
                            
                            		There may be no results map, there does not have to be a
                            		change in state.
                            
                            		Somewhere in here I want to be able to push the current
                            		state onto a stack for a later pop, or even pop it off.
                            		Any ideas?
                            	 -->
                            
                            	<bean id="bookSeat" class="org....OperationMapper">
                            		<property name="operation">
                            			<bean class="example.BookSeatOperation"/>
                            		</property>
                            		<property name="results">
                            			<map>
                            				<entry key="next">
                            					<ref bean="myNextState/>
                            				</entry>
                            				<entry key="badData">
                            					<ref bean="displayErrorState/>
                            				</entry>
                            			</map>
                            		</property>
                            	</bean>
                            
                            	<!--
                            		Different states can share sets or subsets of operations.
                            	 -->
                            
                            	<bean id="operationSet1" class="org.....OperationSet">
                            		<property name="operations">
                            			<set>
                            				<ref bean="bookSeat"/>
                            				<ref bean="viewAvailability"/>
                            			</set>
                            		</property>
                            		<property name="include">
                            			<set>
                            				<ref bean="operationSubSet1"/>
                            				<ref bean="operationSubSet2"/>
                            			</set>
                            		</property>
                            	</bean>
                            
                            	<bean id="displayingWizardPage4State" class="org.....State">
                            		<property name="operationSet">
                            			<ref bean="operationSet1"/>
                            		</property>
                            	</bean>
                            
                            	.....
                            Code:
                            /**
                             * The interface implemented by the programmer to provide workflow logic.
                             *
                             * There may be more than one instance of a document requiring the same set of
                             * states and operations in an application &#40;e.g. an MDI application&#41;.
                             * So Operation and State beans should not hold references to the current
                             * document.  They should take it from the passed context.
                             */
                            public interface Operation &#123;
                            	/**
                            	 * Do some workflow logic and indicate the outcome.
                            	 * @return a string indicating the result.
                            	 */
                            	public String execute&#40;Context context&#41;;
                            	/**
                            	 * Is this operation currently possible?  Not called unless in a state
                            	 * that allows the action.
                            	 */
                            	public boolean isEnabled&#40;Context context&#41;;
                            &#125;
                            Code:
                            /**
                             * Provided by the state machine to operations.
                             */
                            public interface Context &#123;
                            
                            	public State getState&#40;&#41;;
                            	/**
                             	 * The object provided by the programmer to the state machine.
                            	 * It is a means by which operations and GUI views communicate.
                             	 */
                            	public Object getDocument&#40;&#41;;
                            	/**
                            	 * If this is called by the operation then the state machine gives any
                            	 * registered GUI the chance to display the progress.
                            	 * <P>
                            	 * Swing must not drive the state machine using the event dispatch
                            	 * thread for this to happen.
                            	 */
                            	public void showProgress&#40;int min, int max&#41;;
                            	/**
                            	 * Set the amount of progress to display.
                            	 */
                            	public void setProgress&#40;int progress, String messageKey&#41;;
                            &#125;
                            How we might add a Listener to the state machine....

                            Code:
                            <beans>
                            
                            	<bean id="myStateMachine" class="org....StateMachine"
                            							 singleton="false">
                            		.....
                            		<property name="listeners">
                            			<list>
                            				<ref bean="myGuiStateManager"/>
                            			</list>
                            		</property>
                            	</bean>
                            
                            	<!--
                            		So that in an MDI application we get a new set of views for
                            		each document, but we re-use them when working within that
                            		document, We give each it's own view factory.  Also nice
                            		to read these definitions from another file.
                            	
                            		- Does this work?
                            	-->
                            	<bean id="myGuiStateManager" class="org....GuiManager"
                            							 singleton="false">
                            		<property name="viewFactory">
                            			<bean "org....XmlViewFactory">
                            				<constructor-arg>
                            					myswingviews.xml
                            				</constructor-arg>
                            				<constructor-arg>
                            					myStateViewMap
                            				</constructor-arg>
                            			</bean>
                            		</property>
                            	</bean>
                            
                            	....
                            And that's as far as I've got.

                            I do have ideas about views, but they keep changing. I want to treat views as containers with different panels that are attached depending on state. This has worked in my current app, but I don't think it will suit everyone.

                            Reg.

                            Comment


                            • #15
                              I just skimmed an article written about webflow, and from the little I now know, I'm becoming even more convinced that Spring webflow could work with Spring-rich. Take a look at http://www.javalobby.org/articles/spring-webflow/
                              My question is, why not utilize the efforts of the webflow guys? All we would have to do is get them to drop that silly "web" from their name - and then, of course, write integration code.

                              - Andy

                              Comment

                              Working...
                              X