/**
* Copyright 2010 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.drools.guvnor.server;
import org.drools.guvnor.client.rpc.PushResponse;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CountDownLatch;
/**
* This is the backchannel to send "push" messages to the browser.
* Here be dragons. Would like to convert this to using actors one day.
* TODO: convert to executor architecture. Only one instance needed.
* @author Michael Neale
*/
public class Backchannel {
final List<CountDownLatch> waiting = Collections.synchronizedList(new ArrayList<CountDownLatch>());
final Map<String, List<PushResponse>> mailbox = Collections.synchronizedMap(new HashMap<String, List<PushResponse>>());
private Timer timer;
public Backchannel() {
//using a timer to make sure awaiting subs are flushed every now and then, otherwise web threads could be consumed.
timer = new Timer(true);
timer.schedule(new TimerTask() {
public void run() {
unlatchAllWaiting();
}
}, 20000, 30000);
}
public List<PushResponse> await(String userName) throws InterruptedException {
List<PushResponse> messages = fetchMessageForUser(userName);
if (messages != null && messages.size() > 0) {
return messages;
} else {
CountDownLatch latch = new CountDownLatch(1);
waiting.add(latch);
/**
* Now PAUSE here for a while....
*/
latch.await();
/** In the meantime... response has been set, and then it will be unlatched, and message sent back... */
return fetchMessageForUser(userName);
}
}
/**
* Fetch the list of messages waiting, if there are some, replace it with an empty list.
*/
private List<PushResponse> fetchMessageForUser(String userName) {
List<PushResponse> msgs = mailbox.get(userName);
mailbox.put(userName, new ArrayList<PushResponse>());
return msgs;
}
/** Push out a message to the specific client */
public synchronized void push(String userName, PushResponse message) {
//need to queue this up in the users mailbox, and then wake it all up
List<PushResponse> resp = mailbox.get(userName);
if (resp == null) {
resp = new ArrayList<PushResponse>();
resp.add(message);
mailbox.put(userName,resp);
} else {
resp.add(message);
}
unlatchAllWaiting();
}
/**
* Push out a message to all currently connected clients
*/
public synchronized void publish(PushResponse message) {
for(Map.Entry<String, List<PushResponse>> e : mailbox.entrySet()) {
if (e.getValue() == null) e.setValue(new ArrayList<PushResponse>());
e.getValue().add(message);
}
unlatchAllWaiting();
}
private void unlatchAllWaiting() {
synchronized (waiting) {
Iterator<CountDownLatch> it = waiting.iterator();
while (it.hasNext()) {
CountDownLatch l = it.next();
l.countDown();
it.remove();
}
}
}
}