Unexpected java.util.ConcurrentModificationException in GWT emulated AbstractHashMap - gwt

We are experiencing a java.util.ConcurrentModificationException using gwt 2.8.0, while calling iter.next() in the GWT emulated AbstractHashMap class (See stack trace and our CallbackTimer class below). The lowest point in the trace involving our code is on line 118, in the method private void tick(), a call to iter.next().
Drilling into trace, I see AbstractHashMap:
#Override
public Entry<K, V> next() {
checkStructuralChange(AbstractHashMap.this, this);
checkElement(hasNext());
last = current;
Entry<K, V> rv = current.next();
hasNext = computeHasNext();
return rv;
}
calling ConcurrentModificationDetector.checkStructuralChange:
public static void checkStructuralChange(Object host, Iterator<?> iterator) {
if (!API_CHECK) {
return;
}
if (JsUtils.getIntProperty(iterator, MOD_COUNT_PROPERTY)
!= JsUtils.getIntProperty(host, MOD_COUNT_PROPERTY)) {
throw new ConcurrentModificationException();
}
}
My understanding of the purpose of ConcurrentModificationException is to avoid having the collection change while it is being iterated over. I don't think iter.next() would fall into that category. Further, the only places I see the collection changing during iteration do so through the iterator itself. Am I missing something here? Any help would be appreciated!
Our Stack Trace:
java.util.ConcurrentModificationException
at Unknown.Throwable_1_g$(Throwable.java:61)
at Unknown.Exception_1_g$(Exception.java:25)
at Unknown.RuntimeException_1_g$(RuntimeException.java:25)
at Unknown.ConcurrentModificationException_1_g$(ConcurrentModificationException.java:25)
at Unknown.checkStructuralChange_0_g$(ConcurrentModificationDetector.java:54)
at Unknown.next_79_g$(AbstractHashMap.java:106)
at Unknown.next_78_g$(AbstractHashMap.java:105)
at Unknown.next_81_g$(AbstractMap.java:217)
at Unknown.tick_0_g$(CallbackTimer.java:118)
at Unknown.run_47_g$(CallbackTimer.java:41)
at Unknown.fire_0_g$(Timer.java:135)
at Unknown.anonymous(Timer.java:139)
at Unknown.apply_65_g$(Impl.java:239)
at Unknown.entry0_0_g$(Impl.java:291)
at Unknown.anonymous(Impl.java:77)
The source code for CallbackTimer.java is here:
package com.XXXXX.common.gwt.timer;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.common.base.Optional;
import com.google.gwt.user.client.Timer;
/**
* A {#link Timer} wrapper which allows for the registration of callbacks to be invoked after a given number of ticks.
* The timer will only run if at least one {#link TickCallback} is currently registered and will stop running when all
* callbacks have been unregistered.
*
* The intent of this class is to reduce overhead by allowing all callbacks in a GWT application to use the same
* Javascript timer.
*/
public class CallbackTimer
{
private static final Logger LOGGER = Logger.getLogger(CallbackTimer.class.getName());
private static final int MILLIS_IN_SEC = 1000;
private Timer timer;
private Map<Object, TickCallback> callbackRegistry = new HashMap<>();
public CallbackTimer()
{
timer = new Timer()
{
#Override
public void run()
{
try
{
tick();
}
catch(ConcurrentModificationException concurrentModificationException)
{
LOGGER.log(Level.WARNING, "Concurrent Modification Exception in " +
"CallbackTimer.tick()", concurrentModificationException);
}
}
};
}
public void registerCallback(Object key, TickCallback callback)
{
if (callbackRegistry.containsKey(key))
{
LOGGER.fine("Key " + key.toString() + " is being overwritten with a new callback.");
}
callbackRegistry.put(key, callback);
callback.markStartTime();
LOGGER.finer("Key " + key.toString() + " registered.");
if (!timer.isRunning())
{
startTimer();
}
}
public void unregisterCallback(Object key)
{
if (callbackRegistry.containsKey(key))
{
callbackRegistry.remove(key);
LOGGER.finer("Key " + key.toString() + " unregistered.");
if (callbackRegistry.isEmpty())
{
stopTimer();
}
}
else
{
LOGGER.info("Attempted to unregister key " + key.toString() + ", but this key has not been registered.");
}
}
private void unregisterCallback(Iterator<Object> iter, Object key)
{
iter.remove();
LOGGER.finer("Key " + key.toString() + " unregistered.");
if (callbackRegistry.isEmpty())
{
stopTimer();
}
}
public boolean keyIsRegistered(Object key)
{
return callbackRegistry.containsKey(key);
}
public TickCallback getCallback(Object key)
{
if (keyIsRegistered(key))
{
return callbackRegistry.get(key);
}
else
{
LOGGER.fine("Key " + key.toString() + " is not registered; returning null.");
return null;
}
}
private void tick()
{
long fireTimeMillis = System.currentTimeMillis();
Iterator<Object> iter = callbackRegistry.keySet().iterator();
while (iter.hasNext())
{
Object key = iter.next();//Lowest point in stack for our code
TickCallback callback = callbackRegistry.get(key);
if (callback.isFireTime(fireTimeMillis))
{
if (Level.FINEST.equals(LOGGER.getLevel()))
{
LOGGER.finest("Firing callback for key " + key.toString());
}
callback.onTick();
callback.markLastFireTime();
}
if (callback.shouldTerminate())
{
LOGGER.finer("Callback for key " + key.toString() +
" has reached its specified run-for-seconds and will now be unregistered.");
unregisterCallback(iter, key);
}
}
}
private void startTimer()
{
timer.scheduleRepeating(MILLIS_IN_SEC);
LOGGER.finer(this + " started.");
}
private void stopTimer()
{
timer.cancel();
LOGGER.finer(this + " stopped.");
}
/**
* A task to run on a given interval, with the option to specify a maximum number of seconds to run.
*/
public static abstract class TickCallback
{
private long intervalMillis;
private long startedAtMillis;
private long millisRunningAtLastFire;
private Optional<Long> runForMillis;
/**
* #param intervalSeconds
* The number of seconds which must elapse between each invocation of {#link #onTick()}.
* #param runForSeconds
* An optional maximum number of seconds to run for, after which the TickCallback will be eligible
* to be automatically unregistered. Pass {#link Optional#absent()} to specify that the TickCallback
* must be manually unregistered. Make this value the same as {#param intervalSeconds} to run the
* callback only once.
*/
public TickCallback(int intervalSeconds, Optional<Integer> runForSeconds)
{
this.intervalMillis = intervalSeconds * MILLIS_IN_SEC;
this.runForMillis = runForSeconds.isPresent() ?
Optional.of((long)runForSeconds.get() * MILLIS_IN_SEC) : Optional.<Long>absent();
}
private void markStartTime()
{
millisRunningAtLastFire = 0;
startedAtMillis = System.currentTimeMillis();
}
private void markLastFireTime()
{
millisRunningAtLastFire += intervalMillis;
}
private boolean isFireTime(long nowMillis)
{
return nowMillis - (startedAtMillis + millisRunningAtLastFire) >= intervalMillis;
}
private boolean shouldTerminate()
{
return runForMillis.isPresent() && System.currentTimeMillis() - startedAtMillis >= runForMillis.get();
}
/**
* A callback to be run every time intervalSeconds seconds have past since this callback was registered.
*/
public abstract void onTick();
}
}
Update 2017-06-08
I ended up following walen's first suggestion. I did not see where SimpleEventBus was the right tool for this specific job. I did, however, shamelessly steal SEBs method of integrating newly added/removed callbacks:
package com.XXXXX.common.gwt.timer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.common.base.Optional;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Timer;
/**
* A {#link Timer} wrapper which allows for the registration of callbacks to be invoked after a given number of ticks.
* The timer will only run if at least one {#link TickCallback} is currently registered and will stop running when all
* callbacks have been unregistered.
*
* The intent of this class is to reduce overhead by allowing all callbacks in a GWT application to use the same
* Javascript timer.
*/
public class CallbackTimer
{
private static final Logger LOGGER = Logger.getLogger(CallbackTimer.class.getName());
private static final int MILLIS_IN_SEC = 1000;
private Timer timer;
private Map<Object, TickCallback> callbackRegistry = new HashMap<>();
private List<Command> deferredDeltas = new ArrayList<>();
public CallbackTimer()
{
timer = new Timer()
{
#Override
public void run()
{
tick();
}
};
}
public void registerCallback(final Object key, final TickCallback callback)
{
deferredDeltas.add(new Command()
{
#Override
public void execute()
{
activateCallback(key, callback);
}
});
if (!timer.isRunning())
{
startTimer();
}
}
private void activateCallback(Object key, TickCallback callback)
{
if (callbackRegistry.containsKey(key))
{
LOGGER.fine("Key " + key.toString() + " is being overwritten with a new callback.");
}
callbackRegistry.put(key, callback);
callback.markStartTime();
LOGGER.finer("Key " + key.toString() + " registered.");
}
public void unregisterCallback(final Object key)
{
deferredDeltas.add(new Command()
{
#Override
public void execute()
{
deactivateCallback(key);
}
});
}
private void deactivateCallback(Object key)
{
if (callbackRegistry.containsKey(key))
{
callbackRegistry.remove(key);
LOGGER.fine("Key " + key.toString() + " unregistered.");
if (callbackRegistry.isEmpty())
{
stopTimer();
}
}
else
{
LOGGER.info("Attempted to unregister key " + key.toString() + ", but this key has not been registered.");
}
}
private void handleQueuedAddsAndRemoves()
{
for (Command c : deferredDeltas)
{
c.execute();
}
deferredDeltas.clear();
}
public boolean keyIsRegistered(Object key)
{
return callbackRegistry.containsKey(key);
}
private void tick()
{
handleQueuedAddsAndRemoves();
long fireTimeMillis = System.currentTimeMillis();
for (Map.Entry<Object, TickCallback> objectTickCallbackEntry : callbackRegistry.entrySet())
{
Object key = objectTickCallbackEntry.getKey();
TickCallback callback = objectTickCallbackEntry.getValue();
if (callback.isFireTime(fireTimeMillis))
{
if (Level.FINEST.equals(LOGGER.getLevel()))
{
LOGGER.finest("Firing callback for key " + key.toString());
}
callback.onTick();
callback.markLastFireTime();
}
if (callback.shouldTerminate())
{
LOGGER.finer("Callback for key " + key.toString() +
" has reached its specified run-for-seconds and will now be unregistered.");
unregisterCallback(key);
}
}
}
private void startTimer()
{
timer.scheduleRepeating(MILLIS_IN_SEC);
LOGGER.finer(this + " started.");
}
private void stopTimer()
{
timer.cancel();
LOGGER.finer(this + " stopped.");
}
/**
* A task to run on a given interval, with the option to specify a maximum number of seconds to run.
*/
public static abstract class TickCallback
{
private long intervalMillis;
private long startedAtMillis;
private long millisRunningAtLastFire;
private Optional<Long> runForMillis;
/**
* #param intervalSeconds The number of seconds which must elapse between each invocation of {#link #onTick()}.
* #param runForSeconds An optional maximum number of seconds to run for, after which the TickCallback will be
* eligible
* to be automatically unregistered. Pass {#link Optional#absent()} to specify that the TickCallback
* must be manually unregistered. Make this value the same as {#param intervalSeconds} to run the
* callback only once.
*/
protected TickCallback(int intervalSeconds, Optional<Integer> runForSeconds)
{
this.intervalMillis = intervalSeconds * MILLIS_IN_SEC;
this.runForMillis = runForSeconds.isPresent() ?
Optional.of((long) runForSeconds.get() * MILLIS_IN_SEC) : Optional.<Long>absent();
}
private void markStartTime()
{
millisRunningAtLastFire = 0;
startedAtMillis = System.currentTimeMillis();
}
private void markLastFireTime()
{
millisRunningAtLastFire += intervalMillis;
}
private boolean isFireTime(long nowMillis)
{
return nowMillis - (startedAtMillis + millisRunningAtLastFire) >= intervalMillis;
}
private boolean shouldTerminate()
{
return runForMillis.isPresent() && System.currentTimeMillis() - startedAtMillis >= runForMillis.get();
}
/**
* A callback to be run every time intervalSeconds seconds have past since this callback was registered.
*/
public abstract void onTick();
}
}

Your problem seems to be that new items (new keys) are being added to the map at the same time that the tick() method is trying to traverse its keySet.
Modifying a collection in any way while traversing it will throw a ConcurrentModificationException.
Using an iterator only lets you avoid that when removing items, but since there's no iterator.add() method, you can't add items safely.
If this was server-side code, you could use a ConcurrentHashMap, which guarantees that its iterator won't throw an exception in such case (at the expense of not guaranteeing that every item will be traversed, if it was added after iterator creation).
But ConcurrentHashMap is not (yet) supported by GWT's JRE emulation library, so you can't use it in client-side code.
You'll need to come up with a different way of adding items to your CallbackRegistry.
You could, for example, change your registerCallback() method so new items are added to a list / queue instead of the map, and then have the tick() method move those items from the queue to the map after it's done traversing the existing ones.
Alternatively, you could use SimpleEventBus as pointed out in Thomas Broyer's comment.

Related

RxJava adjust backpressure avoiding observeOn buffer

In the code below I would like the subscriber to control when the Flowable emits an event by holding a reference to the Subscription inside subscribe() and requesting the number of elements I want to be produced.
What I am experiencing is that observeOn()'s buffer with size 2 is hiding my call to subscription.request(3) as the producer is producing 2 elements at a time instead of 3.
public class FlowableExamples {
public static void main(String[] args) throws InterruptedException {
long start = new Date().getTime();
Flowable<Integer> flowable = Flowable
.generate(() -> 0, (Integer state, Emitter<Integer> emitter) -> {
int newValue = state + 1;
log("Producing: " + newValue);
emitter.onNext(newValue);
return newValue;
})
.take(30);
flowable
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation(), false, 2)
.subscribe(new Subscriber<Integer>() {
Subscription subscription;
#Override
public void onSubscribe(Subscription subscription) {
this.subscription = subscription;
subscription.request(5);
}
#Override
public void onNext(Integer integer) {
log("\t\treceived: " + integer);
if (integer >= 5) {
sleep(500);
log("Requesting 3 should produce 3, but actually produced 2");
subscription.request(3);
sleep(1000);
}
}
#Override
public void onError(Throwable throwable) {}
#Override
public void onComplete() {
log("Subscription Completed!!!!!!!!");
}
});
sleep(40_000);
System.out.println("Exit main after: " + (new Date().getTime() - start) + " ms");
}
private static void log(String msg) {
System.out.println(Thread.currentThread().getName() + ": " + msg);
}
private static void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {}
}
}
How could I accomplish this?

Select immediate predecessor by date

I am new to Drools and I'm using Drools 7.12.0 to try and validate a set of meter readings, which look like
public class MeterReading() {
private long id;
private LocalDate readDate;
private int value;
private String meterId
private boolean valid;
/* Getters & Setters omitted */
}
As part of the validation I need to compare the values of each MeterReading with its immediate predecessor by readDate.
I first tried using 'accumulate'
when $mr: MeterReading()
$previousDate: LocalDate() from accumulate(MeterReading($pdate: readDate < $mr.readDate ), max($pdate))
then
System.out.println($mr.getId() + ":" + $previousDate);
end
but then discovered that this only returns the date of the previous meter read, not the object that contains it. I then tried a custom accumulate with
when
$mr: MeterReading()
$previous: MeterReading() from accumulate(
$p: MeterReading(id != $mr.id),
init( MeterReading prev = null; ),
action( if( prev == null || $p.readDate < prev.readDate) {
prev = $p;
}),
result(prev))
then
System.out.println($mr.getId() + ":" + $previous.getId() + ":" + $previous.getReadDate());
end
but this selects the earliest read in the set of meter readings, not the immediate predecessor. Can someone point me in the right direction as to what I should be doing or reading to be able to select the immediate predecessor to each individual meter read.
Regards
After further research I found this article http://planet.jboss.org/post/how_to_implement_accumulate_functions which I used to write my own accumulate function;\
public class PreviousReadFinder implements AccumulateFunction {
#Override
public Serializable createContext() {
return new PreviousReadFinderContext();
}
#Override
public void init(Serializable context) throws Exception {
PreviousReadFinderContext prfc = (PreviousReadFinderContext) context;
prfc.list.clear();
}
#Override
public void accumulate(Serializable context, Object value) {
PreviousReadFinderContext prfc = (PreviousReadFinderContext) context;
prfc.list.add((MeterReading) value);
}
#Override
public void reverse(Serializable context, Object value) throws Exception {
PreviousReadFinderContext prfc = (PreviousReadFinderContext) context;
prfc.list.remove((MeterReading) value);
}
#Override
public Object getResult(Serializable context) throws Exception {
PreviousReadFinderContext prfc = (PreviousReadFinderContext) context;
return prfc.findLatestReadDate();
}
#Override
public boolean supportsReverse() {
return true;
}
#Override
public Class<?> getResultType() {
return MeterReading.class;
}
#Override
public void writeExternal(ObjectOutput out) throws IOException {
}
#Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
private static class PreviousReadFinderContext implements Serializable {
List<MeterReading> list = new ArrayList<>();
public Object findLatestReadDate() {
Optional<MeterReading> optional = list.stream().max(Comparator.comparing(MeterReading::getReadDate));
if (optional.isPresent()) {
MeterReading to = optional.get();
return to;
}
return null;
}
}
}
and my rule is now
rule "Opening Read With Previous"
dialect "mvel"
when $mr: MeterReading()
$pmr: MeterReading() from accumulate($p: MeterReading(readDate < $mr.readDate ), previousReading($p))
then
System.out.println($mr.getId() + ":" + $pmr.getMeterReadDate());
end
How do I write a rule to select the eatliest meter reading in the set which does not have a previous read?

Open Perspective programmatically

I am trying to provide a command/ handler to switch to a specific Perspective.
I came up with the following class:
public class OpenPerspectiveHandler {
private static final Logger logger = Logger.getLogger(OpenPerspectiveHandler.class);
#Inject
private MApplication application;
#Inject
private EModelService modelService;
#Inject
private EPartService partService;
private final String perspectiveId;
public OpenPerspectiveHandler(String perspectiveId) {
super();
this.perspectiveId = perspectiveId;
}
public void changePerspective(String perspectiveId) {
Optional<MPerspective> perspective = findPerspective();
if(perspective.isPresent()) {
partService.switchPerspective(perspective.get());
} else {
logger.debug("Perspective not found (" + perspectiveId + ")");
}
}
#Execute
public void execute() {
changePerspective(perspectiveId);
}
private Optional<MPerspective> findPerspective() {
MUIElement element = modelService.find(perspectiveId, application);
if(element instanceof MPerspective) {
return Optional.of((MPerspective)element);
} else {
logger.debug("Wrong type " + element);
}
return Optional.empty();
}
#Override
public String toString() {
return "OpenPerspectiveHandler [application=" + application + ", modelService=" + modelService + ", partService=" + partService + ", perspectiveId=" + perspectiveId + "]";
}
}
Interestingly, this works only once. A workaround is to cache MPerspective once it was found and not to use modelService.find(perspectiveId, application) again.
Why does it work only once? modelService.find(perspectiveId, application) returns null after the first execution.
EDIT:
Another approach (as suggested by greg-449) is the following:
public class OpenPerspectiveHandler {
private static final Logger logger = Logger.getLogger(OpenPerspectiveHandler.class);
private final String perspectiveId;
public OpenPerspectiveHandler(String perspectiveId) {
super();
this.perspectiveId = perspectiveId;
}
#Execute
public void changePerspective(MApplication application, EModelService modelService, EPartService partService) {
Optional<MPerspective> perspective = findPerspective(application, modelService);
if(perspective.isPresent()) {
partService.switchPerspective(perspective.get());
} else {
logger.debug("Perspective not found (" + perspectiveId + ")");
}
}
private Optional<MPerspective> findPerspective(MApplication application, EModelService modelService) {
MUIElement element = modelService.find(perspectiveId, application);
if(element instanceof MPerspective) {
return Optional.of((MPerspective)element);
} else {
logger.debug("Wrong type " + element);
}
return Optional.empty();
}
}
But this approach also changes the perspective only once. modelService.find(perspectiveId, application); returns null after the first execution.
The EPartService varies depending on the context that the handler runs in. In some cases you get the Application part service which only works if there is an active window.
You can get the part service for that window using something like:
MWindow window = (MWindow)modelService.find("top window id", application);
IEclipseContext windowContext = window.getContext();
EPartService windowPartService = windowContext.get(EPartService.class);

How to use Retry rule along with Errorcollector rule in junit

I am using Error collector rule in my application( selenium web driver). I am able to thrown exception and continue next line of code with help of error collector rule. But right now i want to re run failed test again ( 3 times) to ensure they are really failed. hence i am using Retry rule. But this rule when applied individually it get executed ( Retry rule with Assert command) `but when written with error collector is doesn't get executed any reason....
Please help me with sample code.
TestBase.java:
public class TestBase {
#Rule
public ErrorCollector collector = new ErrorCollector();
private boolean fatal;
public TestBase() {
fatal=true;
}
public void assertEquals( String msg, Object expected, Object actual) {
if(getFatal()) {
Assert.assertEquals(msg,expected, actual);
} else {
collector.checkThat(msg, actual, CoreMatchers.is(expected));
}
}
public void setFatal(boolean fatalFlag) {
fatal = fatalFlag;
}
public boolean getFatal() {
return fatal;
}
}
BFMNew.java
public class BFMNew extends TestBase {
#Rule
public Retry retry = new Retry(3);
#Rule
public ErrorCollector errocol = new ErrorCollector();
#Before
public void setUp() throws Exception {
System.out.println(" in before");
}
// ===========Re run fail test custom====
public class Retry implements TestRule {
private int retryCount;
public Retry(int retryCount) {
this.retryCount = retryCount;
}
public Statement apply(Statement base, Description description) {
return statement(base, description);
}
private Statement statement(final Statement base,
final Description description) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
Throwable caughtThrowable = null;
// implement retry logic here
for (int i = 0; i < retryCount; i++) {
try {
base.evaluate();
return;
} catch (Throwable t) {
caughtThrowable = t;
System.err.println(description.getDisplayName()
+ ": run " + (i + 1) + " failed");
}
}
System.err.println(description.getDisplayName()
+ ": giving up after " + retryCount + " failures");
throw caughtThrowable;
}
};
}
}
#Test
public void one() {
setFatal(false);
Boolean IsLogin = true; //Here function will come for login
Boolean IsPost = null;
Boolean IsStnComment = null;
Boolean IsPhotoUpload = false;
if( IsLogin ) {
IsPost = false;
assertEquals("Failed to Insert Post", true, IsPost);
}
System.out.println(" After Post ");
assertEquals("Failed to upload photo", true, IsPhotoUpload);
if( IsPost ) {
IsStnComment = false;
//assertEquals("Failed to Insert Comment", true, IsStnComment);
}
System.out.println("After comment");
}
The problem is with rules ordering. You should make ErrorCollector to be outer rule and Retry inner rule. Starting from junit 4.10 use this
class YourTest {
private ErrorCollector collector = new ErrorCollector();
private Retry retry = Retry(3);
#Rule
public TestRule chain= RuleChain
.outerRule(collector)
.around(retry);
// tests using collector go here
}

Cometd : It seems that ServerChannel lose some subscribers

I used cometd to realize notification push, but i found out the following issue :
After log in the system, at the beginning, the client can receive message from server, but after wait pretty long time or do some other operation, the client may not receive message from server any more. Did anyone else encountered this problem? Thanks in Advance.
Blow is my code :
1. Client Code
var cometd = dojox.cometd;cometd.websocketEnabled = false;
cometd.init(url);
cometd.subscribe("/foo/new", function(message) {
......The business logic......
}
);
2. The ServletContextAttributeListener that integrate with AbstractService
public class BayeuxInitializerListener implements ServletContextAttributeListener {
private static final String CLIENT_CHANNEL = "/foo/new";
#Override
public void attributeAdded(ServletContextAttributeEvent event) {
if(BayeuxServer.ATTRIBUTE.equals(event.getName())) {
BayeuxServer bayeuxServer = (BayeuxServer) event.getValue();
boolean isCreated = bayeuxServer.createIfAbsent(CLIENT_CHANNEL, new ConfigurableServerChannel.Initializer() {
#Override
public void configureChannel(ConfigurableServerChannel channel) {
channel.setPersistent(true);
}
});
new MyService(bayeuxServer);
}
}
3. Service
public class MyService extends AbstractService {
private static final Logger logger = Logger.getLogger(MyService .class);
private static final String CLIENT_CHANNEL = "/foo/new";
private static final String LISTENER_CHANNEL = "/service/notification";
public MyService(BayeuxServer bayeuxServer) {
super(bayeuxServer, "notification");
this.addService(LISTENER_CHANNEL, "processNotification");
}
public void processNotification(ServerSession serverSession, Map<String, Object> data) {
LocalSession localSession = this.getLocalSession();
if(logger.isDebugEnabled()) {
logger.debug("Local Session : " + localSession.getId() + ".");
}
ServerChannel serverChannel = this.getBayeux().getChannel(CLIENT_CHANNEL)
Set<ServerSession> subscribers = serverChannel.getSubscribers();
if(0 == subscribers.size()) {
logger.info("There are no subcribers for " + CLIENT_CHANNEL + ".");
}
for(ServerSession subscriber : subscribers) {
logger.info("The subscriber for " + CLIENT_CHANNEL + " : " + subscriber.getId() + ".");
}
serverChannel.publish(localSession, data, null);
}