0

Solving "IllegalStateException: Components can be accessed only in event listeners" in zk 5.0.x

asked 2010-08-19 04:28:32 +0800

thrane gravatar image thrane
43 3

I have implemented a tree view which is updated dynamically by the business logic. The implementation follows the example described here at: http://www.zkoss.org/forum/listComment/11699

But I receive the exception:

java.lang.IllegalStateException: Components can be accessed only in event listeners
at org.zkoss.zk.ui.impl.UiEngineImpl.getCurrentVisualizer(UiEngineImpl.java:235)
at org.zkoss.zk.ui.impl.UiEngineImpl.addMoved(UiEngineImpl.java:273)
at org.zkoss.zk.ui.AbstractComponent.addMoved(AbstractComponent.java:562)
at org.zkoss.zk.ui.AbstractComponent.setParent(AbstractComponent.java:1053)
at org.zkoss.zul.Treeitem.setParent(Treeitem.java:436)
at org.zkoss.zk.ui.AbstractComponent.insertBefore(AbstractComponent.java:1178)
at org.zkoss.zul.Tree.onTreeDataInsert(Tree.java:1299)
at org.zkoss.zul.Tree.onTreeDataChange(Tree.java:1262)
at org.zkoss.zul.Tree.access$400(Tree.java:67)
at org.zkoss.zul.Tree$4.onChange(Tree.java:1387)
at org.zkoss.zul.AbstractTreeModel.fireEvent(AbstractTreeModel.java:67)

Scanning the forums I found this answers:
http://www.zkoss.org/forum/listComment/11370
www.zkoss.org/forum/listComment/7682

which basically says: use server push. But then again, 5.0.3 server push example at
http://docs.zkoss.org/wiki/ZK_5:_Chat_with_Event_Queue
states: The best way to use the server push is not to know the server push at all.

How do i solve my problem with "Components can be accessed only in event listeners"?
Does anybody know of an example to look at?

delete flag offensive retag edit

13 Replies

Sort by ยป oldest newest

answered 2010-08-19 04:59:32 +0800

SimonPai gravatar image SimonPai
1696 1

Hi thrane,

What the Smalltalk meant was "it's recommended to use EventQueue, which requires no knowlegde about server push to use". The reason you can't update the component is because HTTP works in a request-response fashion, so you can't aggressively send a message from server to client unless handled by some special mechanism, which is what we call server push. To solve your problem you simply need to follow the example given in the Smalltalk. For example,

subscribe to EventQueue after the component is created. (For example, in doAfterCompose() of your GenericForwardComposer)

EventQueues.lookup("myQueue", EventQueues.APPLICATION, true).subscribe(
	new EventListener() {
		public void onEvent(Event evt) {
			if("onSomething".equals(evt.getName())) {
				// what to do with the event (maybe update your tree)
			}
		}
	}
});

and in your business logic, fire an event like this: (this can be done in anytime, anywhere)

EventQueues.lookup("myQueue", EventQueues.APPLICATION, true)
	.publish(new Event("onSomething", null, new SomeDataBean()));

Regards,
Simon

link publish delete flag offensive edit

answered 2010-08-19 06:22:49 +0800

thrane gravatar image thrane
43 3

Thanks very much for the fast answer!

I will try this out, but still one thing that I'm unsure about. The event is fired from the model in this fashion:

	
       public void add(SimpleTreeNode parentNode, SimpleTreeNode childNode ){		
		List<SimpleTreeNode> children = parentNode.getChildren();
		int indexFrom = children.size();
		int indexTo = indexFrom+1;
		children.add(childNode);
		fireEvent(parentNode,indexFrom,indexTo,TreeDataEvent.INTERVAL_ADDED);
	}

Should it still be fired using the fireEvent method or should it be fired using the publish method that you used in your example? If the publish method should be used, would you be so kind to give an example?

link publish delete flag offensive edit

answered 2010-08-21 11:29:34 +0800

SimonPai gravatar image SimonPai
1696 1

thrane,

You should only access your components and models in subscription (i.e. the first code block in my previous response). The "event" which fireEvent method does is to inform Tree component the change you made in the model. The event in the EventQueue is to let your business logic tell UI controller to do something when the event happens. They are independent issues.

Regards,
Simon

link publish delete flag offensive edit

answered 2010-08-23 02:57:30 +0800

thrane gravatar image thrane
43 3

What I have implemented now is this:

1) A tree model named ExecutionResultTreeModel.
The class extends AbstractTreeModel and implement these two methods:

	public void add(SimpleTreeNode parentNode, SimpleTreeNode childNode ){		
		List<SimpleTreeNode> children = parentNode.getChildren();
		int indexFrom = children.size();
		int indexTo = indexFrom+1;
		children.add(childNode);
		
		// create event
		final TreeDataEvent treeEvent = new TreeDataEvent(this,TreeDataEvent.INTERVAL_ADDED, parentNode, indexFrom,indexTo);
		EventQueue queue = EventQueues.lookup(RESULT_TREE_QUEUE, EventQueues.APPLICATION, true);
		queue.publish(new Event(ADDED_NODE_EVENT,null,treeEvent));							
	}

and

	public void fireEvent(TreeDataEvent event) {
		fireEvent(event);
	}

2) A composer named ExecuteModuleComposer.
The class extends GenericAutowireComposer and contains the method:

	public void doAfterCompose(Component comp) throws Exception {
		super.doAfterCompose(comp);

		// get tree model
		treeModel = executeModuleController.getModel();		
				
		// set renderer
		TreeitemRenderer renderer = new ExecutionResultTreeItemRenderer();
		tree.setTreeitemRenderer(renderer);
		
		// set tree model
		tree.setModel(treeModel);		
		
		// get event queue
		EventQueue eventQueue = EventQueues.lookup(ExecutionResultTreeModel.RESULT_TREE_QUEUE, EventQueues.APPLICATION, true);
		
		// create event listener 
		EventListener listener = new EventListener() {
			public void onEvent(Event event) throws Exception {
				
				if(ExecutionResultTreeModel.ADDED_NODE_EVENT.equals(event.getName())) {
															
					// get tree event
					TreeDataEvent treeEvent = (TreeDataEvent) event.getData();					
										
					// type cats to result tree
					ExecutionResultTreeModel resultTree = (ExecutionResultTreeModel) treeModel;
										
					// fire event
					resultTree.fireEvent(treeEvent);
				}				
			}
		};
			
		// set event listener
		eventQueue.subscribe(listener);		
	}

the update sequence is this:
1) Some objects invokes ExecutionResultTreeModel.add(...);
2) add() adds a new node to the tree model
3) add() publishes an event on the event queue.
4) The event listener registered by the ExecuteModuleComposer recieves the event
5) The event listener the invokes the fireEvent method on the tree model (to avoid the initial "IllegalStateException: Components can be accessed only in event listeners" exception which started this forum thread). So now the component access take place within an eventl listener.

But now I get this exception:

java.lang.IllegalStateException: Not in an execution
	at org.zkoss.zk.ui.event.impl.EventQueueProviderImpl.lookup(EventQueueProviderImpl.java:40)
	at org.zkoss.zk.ui.event.EventQueues.lookup(EventQueues.java:101)
	at com.alpha.pineapple.web.ui.tree.ExecutionResultTreeModel.add(ExecutionResultTreeModel.java:74)

Please advise, how I can get past this exception?
Also, I think the above code is somewhat convoluted, can it be simplified?

Regards,
thrane

link publish delete flag offensive edit

answered 2010-08-30 06:13:16 +0800

SimonPai gravatar image SimonPai
1696 1

Thrane,

Sorry for late response. From your code I cannot tell what exactly you want, but I wrote a simple example that uses EventQueue to update a TreeModel. In this example, you can open display.zul and control.zul, and when you cilck on the button in control.zul, a new node will be added into the tree in display.zul at a random position.

I used TreeModelA.java from the small talk.

display.zul

<?page title="Display Tree" contentType="text/html;charset=UTF-8"?>
<zk>
	<window title="Tree with EventQueue" border="normal" 
		apply="samples.eventqueue.tree.TreeDisplayController">
		<tree id="tree" />
	</window>
</zk>

control.zul

<?page title="Update Tree" contentType="text/html;charset=UTF-8"?>
<zk>
	<window border="normal">
		<hlayout>
			<textbox id="newNode" />
			<button label="Add Node">
				<attribute name="onClick"><![CDATA[
					// publish
					EventQueues.lookup("TreeDisplay", EventQueues.APPLICATION, true)
						.publish(new Event("AddNode", null, newNode.getValue()));
				]]></attribute>
			</button>
		</hlayout>
	</window>
</zk>

TreeDisplayController.java

public class TreeDisplayController extends GenericForwardComposer {
	
	private Tree tree;
	private TreeModelA model;
	
	@Override
	public void doAfterCompose(Component comp) throws Exception {
		super.doAfterCompose(comp);
		
		tree.setTreeitemRenderer(new TreeitemRenderer() {
			@Override
			public void render(Treeitem item, Object data) throws Exception {
				Treerow r = new Treerow();
				Treecell c = new Treecell();
				item.appendChild(r);
				r.appendChild(c);
				c.appendChild(new Label(data instanceof List? "" : data.toString()));
			}
		});
		
		model = prepareTreeModel();
		tree.setModel(model);
		
		// subscribe
		EventQueues.lookup("TreeDisplay", EventQueues.APPLICATION, true)
				.subscribe(new EventListener() {
			public void onEvent(Event event) throws Exception {
				System.out.println("subscribe");
				if("AddNode".equals(event.getName())) {
					ArrayList parent = getRandomNode((ArrayList) model.getRoot());
					int position = (int) (Math.random() * parent.size());
					Object[] newNodes = {event.getData()};
					model.insert(parent, position, position, newNodes);
				}
			}
		});
	}
	
	private TreeModelA prepareTreeModel(){
		ArrayList mother = new ArrayList();
		
		//Create a branch "child1" and assign children to it
		ArrayList child1 = new ArrayList();
		child1.add("Tommy");
		child1.add("Juile");
		
		//Create a branch "child2" and assign children to it
		ArrayList child2 = new ArrayList();
		child2.add("Gray");
		
		//Create a branch "grandchild" and assign children to it
		ArrayList grandChild = new ArrayList();
		grandChild.add("Paul");
		grandChild.add("Eric");
		
		//Assign branch "grandchild" to be branch "child2"'s child
		child2.add(grandChild);
		
		//Assign branch "grand2" to be branch "child1"'s child
		child1.add(child2);
		
		//Assign children to root "mother"
		mother.add("Andy");
		mother.add("Davis");
		mother.add(child1);
		mother.add("Matter");
		mother.add("Kitty");
		
		return new TreeModelA(mother);
	}
	
	private ArrayList getRandomNode(ArrayList parent){
			if(Math.random() > 0.7) return parent;
			ArrayList nodeCache = new ArrayList();
			
			for(Object obj : parent)
				if(obj instanceof ArrayList) 
					nodeCache.add(obj);
			if(nodeCache.size() == 0) return parent;
			
			return getRandomNode((ArrayList)nodeCache.get(
					(int)(nodeCache.size() * Math.random())));
	}
}

Hope this is helpful.

Regards,
Simon

link publish delete flag offensive edit

answered 2010-11-12 10:36:02 +0800

tchavrie gravatar image tchavrie
18 1

Hello thrane, did you find the solution to your "java.lang.IllegalStateException: Not in an execution" problem ?
Got the same here, can't find the solution :/

link publish delete flag offensive edit

answered 2010-11-18 05:27:23 +0800

SimonPai gravatar image SimonPai
1696 1

Hi tchavrie,

Natively HTTP works in a pulling fashion, which means the server side is passive, only responding to the client request. If you want to update information to client actively by server, you need to enable Server Push.

In ZK you cannot modify component while not in an Execution for the same reason. However, ZK provides a technology called EventQueue to handle this situation easily.

Regards,
Simon

link publish delete flag offensive edit

answered 2010-11-18 07:55:25 +0800

tchavrie gravatar image tchavrie
18 1

updated 2010-11-18 07:56:12 +0800

Hello Simon, thank you for you answer

I found a solution to half of my problem : i can now subscribe to EventQueue . I subscride to the queue using the "doAftercompose" method applied on the window i use to display my tree. But here is the thing : impossible to publish an event. Error is : not in an Execution. The thing is , refering to this article, "publish can be invoked anytime, anywhere". How comes it asks for an execution ?

The method vmAdded(VMItem vm) (in which i try tu publish something in my EventQueue) is called when some event is fired somewhere else in my business logic.

Here is my code : TreeDisplayController.java

package fr.chavrier.entropyWebManager.tree;

public class TreeDisplayController extends GenericForwardComposer implements ModelListener {
	
	//All imports here

        Tree tree;
	EntropyTreeModel model;
	EntropyTreeitemRenderer treeRenderer;
	
	@Override
	public void doAfterCompose(Component comp) throws Exception {
		super.doAfterCompose(comp);
		
		treeRenderer = new EntropyTreeitemRenderer();
		tree.setTreeitemRenderer(treeRenderer);
		
		TreeFactory treeFactory = new TreeFactory();
		model = new EntropyTreeModel(treeFactory.computeJasmineTree(this));
		tree.setModel(model);
		
		tree.setWidth("100%");
		tree.setStyle("border:0px");
		EventListener el = new EventListener() {
			public void onEvent(Event arg0) throws Exception {
				onSelect();
			}
		};
		tree.addEventListener("onSelect", el);
		
		EventQueues.lookup("TreeDisplay", EventQueues.APPLICATION, true)
				.subscribe(new EventListener() {
			public void onEvent(Event event) throws Exception {
				//System.out.println("subscribe");
				if("AddNode".equals(event.getName())) {
					System.out.println("node added");
				}
			}
		});
	}
	
	public void setPageToDisplay(String page){
		//whatever, not relevant
	
	public void onSelect(){
		//whatever, not relevant
	}

	public void vmAdded(VMItem vm) {
		EventQueues.lookup("TreeDisplay", EventQueues.APPLICATION, true).publish(new Event("AddNode", null, vm));
	}
}

But when i try to publish from a zul page, using a button in my web browser, it works.

Code of the Button :

<button label="Add Node">
	<attribute name="onClick"><![CDATA[
		// publish
		EventQueues.lookup("TreeDisplay", EventQueues.APPLICATION, true)
			.publish(new Event("AddNode", null, newNode.getValue()));
	]]></attribute>
</button>

link publish delete flag offensive edit

answered 2010-11-19 09:04:58 +0800

tchavrie gravatar image tchavrie
18 1

anyone ?

link publish delete flag offensive edit

answered 2011-03-23 01:31:01 +0800

frik gravatar image frik
84 1

updated 2011-03-23 01:32:31 +0800

I had the same problem and found the solution to use this lookup instead:

EventQueue eq = EventQueues.lookup(TYPE, webApp, true);

Where:
webApp = WebManager.getWebApp(servletContext);

To get servletContext, you will need to look at the specific java web server you are using.

Hope that helps.

Frik

link publish delete flag offensive edit
Your reply
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!

[hide preview]

Question tools

Follow

RSS

Stats

Asked: 2010-08-19 04:28:32 +0800

Seen: 1,479 times

Last updated: Nov 05 '13

Support Options
  • Email Support
  • Training
  • Consulting
  • Outsourcing
Learn More