/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2011 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.server.headlessclient.eventthread;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.servoy.j2db.server.headlessclient.EventsRunnable;
import com.servoy.j2db.server.headlessclient.WebClient;
import com.servoy.j2db.util.Debug;
/**
* Runnable of the ScriptThread that executes {@link Event} objects.
*
* @author jcompagner
*
* @since 6.1
*/
public class WicketEventDispatcher implements Runnable, IEventDispatcher<WicketEvent>
{
private final ConcurrentMap<Object, Event> suspendedEvents = new ConcurrentHashMap<Object, Event>();
private final List<Event> events = new ArrayList<Event>();
private final LinkedList<Event> stack = new LinkedList<Event>();
private volatile boolean exit = false;
private volatile Thread scriptThread = null;
private final WebClient client;
/**
*
*/
public WicketEventDispatcher(WebClient client)
{
this.client = client;
}
public void run()
{
scriptThread = Thread.currentThread();
while (!exit)
{
dispatch();
}
}
/**
*
*/
private void dispatch()
{
try
{
Event event = null;
synchronized (events)
{
while (event == null)
{
if (events.size() > 0)
{
event = events.remove(0);
}
else
{
events.wait();
}
}
}
stack.add(event);
event.execute();
if (stack.getLast() != event)
{
throw new Exception("State not expected");
}
stack.remove(event);
synchronized (events)
{
events.notifyAll();
}
}
catch (Exception e)
{
Debug.error(e);
}
}
@Override
public boolean isEventDispatchThread()
{
return scriptThread == Thread.currentThread();
}
/**
* @param event
*/
public void addEvent(WicketEvent event)
{
if (scriptThread == Thread.currentThread())
{
event.execute();
}
else
{
synchronized (events)
{
events.add(event);
events.notifyAll();
while (!(event.isExecuted() || event.isSuspended() || event.isExecutingInBackground()))
{
try
{
events.wait();
}
catch (InterruptedException e)
{
Debug.error(e);
}
}
}
}
// now execute all request events that this event did generate.
List<Runnable> requestEvents = event.getEvents();
if (requestEvents.size() > 0)
{
addEvent(new WicketEvent(client, new EventsRunnable(requestEvents)));
}
}
/**
* @param object
*/
public void suspend(Object object)
{
// TODO should this one be called in the execute event thread, should an check be done??
if (Thread.currentThread() != scriptThread)
{
Debug.error("suspend called in another thread then the script thread: " + Thread.currentThread(), new RuntimeException());
return;
}
Event event = stack.getLast();
if (event != null)
{
suspendedEvents.put(object, event);
event.willSuspend();
synchronized (events)
{
events.notifyAll();
}
while (suspendedEvents.containsKey(object) && !exit)
{
dispatch();
}
event.willResume();
}
}
public void resume(Object object)
{
Event event = suspendedEvents.remove(object);
if (event != null)
{
client.invokeLater(new Runnable()
{
@Override
public void run()
{
// empty event so that the current http request will run this and will wait on the resume of the above event.
// or when that above event is suspended again (so a script that shows a few modal dialogs in in method after each other)
// it will get the waiting events from this one.
}
});
if (event instanceof WicketEvent && Thread.currentThread() == scriptThread)
{
Event last = stack.getLast();
// make sure the resumed event has the current http thread, so that when it resumes it can get the locks back.
((WicketEvent)event).updateHttpThread(last);
}
}
}
public IEventProgressMonitor getEventProgressMonitor()
{
// TODO should this one be called in the execute event thread, should an check be done??
if (Thread.currentThread() != scriptThread)
{
Debug.error("run in background called in another thread then the script thread: " + Thread.currentThread(), new RuntimeException());
return null;
}
final Event event = stack.getLast();
if (event != null)
{
return new IEventProgressMonitor()
{
public boolean isExecuting()
{
return !event.isExecuted();
}
public void runInBackground()
{
event.executeInBackground();
synchronized (events)
{
events.notifyAll();
}
}
};
}
else
{
throw new IllegalStateException("No current event to run in the background");
}
}
/**
*
*/
private void addEmptyEvent()
{
synchronized (events)
{
// add a nop event so that the dispatcher is triggered.
events.add(new Event(null));
events.notifyAll();
}
}
/**
*
*/
public void destroy()
{
exit = true;
addEmptyEvent();
}
}