/*
Copyright (c) 2012 LinkedIn Corp.
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.
*/
/**
* $Id: $
*/
package com.linkedin.d2.discovery.event;
import com.linkedin.d2.discovery.stores.PropertyStore;
import com.linkedin.d2.discovery.stores.PropertyStoreException;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Steven Ihde
* @version $Revision: $
*/
public abstract class PublisherTest
{
protected static final int BUS_UPDATE_TIMEOUT = 30;
/**
*
* @return publisher being tested
*/
protected abstract PropertyEventPublisher<String> getPublisher();
/**
*
* @return a property store which can write into the same property space as the publisher
* is watching. This should be as reasonably disconnected as possible from the publisher,
* i.e., it should not be the same store that backs the publisher.
*/
protected abstract PropertyStore<String> getStore();
@Test
public void testNewProperty() throws PropertyStoreException, TimeoutException, InterruptedException
{
final String KEY = "someKey";
final String VALUE = "someValue";
PropertyEventPublisher<String> pub = getPublisher();
MockBusSink bus = new MockBusSink();
pub.setBus(bus);
// Publisher should publish an initial null
pub.startPublishing(KEY);
bus.awaitInit(KEY, null, BUS_UPDATE_TIMEOUT, TimeUnit.SECONDS);
// After updating, publisher should publish the new value
PropertyStore<String> store = getStore();
store.put(KEY, VALUE);
bus.awaitAdd(KEY, VALUE, BUS_UPDATE_TIMEOUT, TimeUnit.SECONDS);
}
@Test
public void testExistingProperty() throws PropertyStoreException, TimeoutException, InterruptedException
{
final String KEY = "someKey";
final String VALUE = "someValue";
PropertyStore<String> store = getStore();
store.put(KEY, VALUE);
Assert.assertEquals(store.get(KEY), VALUE);
MockBusSink bus = new MockBusSink();
PropertyEventPublisher<String> pub = getPublisher();
pub.setBus(bus);
pub.startPublishing(KEY);
bus.awaitInit(KEY, VALUE, BUS_UPDATE_TIMEOUT, TimeUnit.SECONDS);
store.remove(KEY);
bus.awaitRemove(KEY, BUS_UPDATE_TIMEOUT, TimeUnit.SECONDS);
}
protected static class MockBusSink implements PropertyEventBus<String>
{
private final Lock _lock = new ReentrantLock();
private final Condition _initCondition = _lock.newCondition();
private final Condition _addCondition = _lock.newCondition();
private final Condition _removeCondition = _lock.newCondition();
private Map<String,String> _currentValues = new HashMap<String, String>();
public void awaitInit(String key, String value, long timeout, TimeUnit timeoutUnit)
throws InterruptedException, TimeoutException
{
Date deadline = new Date(System.currentTimeMillis() + timeoutUnit.toMillis(timeout));
_lock.lock();
try
{
while (!compare(_currentValues.get(key), value))
{
if (!_initCondition.awaitUntil(deadline))
{
throw new TimeoutException();
}
}
}
finally
{
_lock.unlock();
}
}
public void awaitAdd(String key, String value, long timeout, TimeUnit timeoutUnit)
throws InterruptedException, TimeoutException
{
Date deadline = new Date(System.currentTimeMillis() + timeoutUnit.toMillis(timeout));
_lock.lock();
try
{
while (!compare(_currentValues.get(key), value))
{
if (!_addCondition.awaitUntil(deadline))
{
throw new TimeoutException();
}
}
}
finally
{
_lock.unlock();
}
}
public void awaitRemove(String key, long timeout, TimeUnit timeoutUnit)
throws InterruptedException, TimeoutException
{
Date deadline = new Date(System.currentTimeMillis() + timeoutUnit.toMillis(timeout));
_lock.lock();
try
{
while (!compare(_currentValues.get(key), null))
{
if (!_removeCondition.awaitUntil(deadline))
{
throw new TimeoutException();
}
}
}
finally
{
_lock.unlock();
}
}
private static boolean compare(Object a, Object b)
{
if (a == null)
{
return b == null;
}
return a.equals(b);
}
@Override
public void publishInitialize(String prop, String value)
{
_lock.lock();
try
{
_currentValues.put(prop, value);
_initCondition.signalAll();
}
finally
{
_lock.unlock();
}
}
@Override
public void publishAdd(String prop, String value)
{
_lock.lock();
try
{
_currentValues.put(prop, value);
_addCondition.signalAll();
}
finally
{
_lock.unlock();
}
}
@Override
public void publishRemove(String prop)
{
_lock.lock();
try
{
_currentValues.remove(prop);
_removeCondition.signalAll();
}
finally
{
_lock.unlock();
}
}
@Override
public void register(PropertyEventSubscriber<String> stringPropertyEventSubscriber)
{
throw new UnsupportedOperationException();
}
@Override
public void unregister(PropertyEventSubscriber<String> stringPropertyEventSubscriber)
{
throw new UnsupportedOperationException();
}
@Override
public void register(Set<String> propertyNames,
PropertyEventSubscriber<String> stringPropertyEventSubscriber)
{
throw new UnsupportedOperationException();
}
@Override
public void unregister(Set<String> propertyNames,
PropertyEventSubscriber<String> stringPropertyEventSubscriber)
{
throw new UnsupportedOperationException();
}
@Override
public void setPublisher(PropertyEventPublisher<String> stringPropertyEventPublisher)
{
throw new UnsupportedOperationException();
}
}
}