Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

[SM-2114] Added a krati store and a krati persistence provider that c…

…an be used with hazelcast store.

git-svn-id: https://svn.apache.org/repos/asf/servicemix/utils/trunk@1186995 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information...
commit adc45581ce598c46022a70a17fa7312f0ddfcb1c 1 parent 7e5b31b
@iocanel iocanel authored
View
5 pom.xml
@@ -151,6 +151,11 @@
<version>1.9.3</version>
</dependency>
<dependency>
+ <groupId>com.sna-projects.krati</groupId>
+ <artifactId>krati</artifactId>
+ <version>0.4.1</version>
+ </dependency>
+ <dependency>
<groupId>org.idevlab</groupId>
<artifactId>rjc</artifactId>
<version>0.6.4</version>
View
238 src/main/java/org/apache/servicemix/store/krati/KratiPersistenceProvider.java
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.servicemix.store.krati;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import com.hazelcast.core.MapLoader;
+import com.hazelcast.core.MapStore;
+import krati.core.StoreConfig;
+import krati.core.StoreFactory;
+import krati.core.segment.MemorySegmentFactory;
+import krati.core.segment.Segment;
+import krati.store.DataStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class KratiPersistenceProvider implements MapLoader<String, Object>, MapStore<String, Object>{
+
+ private static final Logger LOG = LoggerFactory.getLogger(KratiPersistenceProvider.class);
+
+ private static final String DEFAULT_STORE_DIRECTORY = "/tmp/krati/";
+ private static final Integer INIT_CAPACITY = 10000;
+
+ private DataStore<byte[], byte[]> datas;
+ private String storeDirectory = DEFAULT_STORE_DIRECTORY;
+ private int initCapacity = INIT_CAPACITY;
+ private int segmentFileSizeMB = Segment.minSegmentFileSizeMB;
+
+ public KratiPersistenceProvider(){
+ try {
+ init();
+ } catch (Exception e) {
+ LOG.error("Error during initialization",e);
+ }
+ }
+
+
+ public KratiPersistenceProvider(String storeDirectory) {
+ super();
+ this.storeDirectory = storeDirectory;
+ try {
+ init();
+ } catch (Exception e) {
+ LOG.error("Error during initialization",e);
+ }
+ }
+
+
+ public KratiPersistenceProvider(String storeDirectory, int initCapacity) {
+ super();
+ this.storeDirectory = storeDirectory;
+ this.initCapacity = initCapacity;
+ try {
+ init();
+ } catch (Exception e) {
+ LOG.error("Error during initialization",e);
+ }
+ }
+
+
+
+ public KratiPersistenceProvider(String storeDirectory,
+ int initCapacity, int segmentFileSizeMB) {
+ super();
+ this.storeDirectory = storeDirectory;
+ this.initCapacity = initCapacity;
+ this.segmentFileSizeMB = segmentFileSizeMB;
+ try {
+ init();
+ } catch (Exception e) {
+ LOG.error("Error during initialization",e);
+ }
+ }
+
+
+ public void init() throws Exception{
+ if(datas == null){
+ StoreConfig config = new StoreConfig(new File(storeDirectory), initCapacity);
+ config.setSegmentFactory(new MemorySegmentFactory());
+ config.setSegmentFileSizeMB(segmentFileSizeMB);
+ datas = StoreFactory.createDynamicDataStore(config);
+ }
+ }
+
+ public void destroy() throws Exception{
+ if(datas!=null && datas.isOpen()){
+ datas.close();
+ }
+ }
+
+ public void delete(String key) {
+ try {
+ datas.delete(key.getBytes());
+ } catch (Exception e) {
+ LOG.error("Error deleting object with id:{}", key, e);
+ }
+
+ }
+
+ public void deleteAll(Collection<String> entries) {
+ for(String key:entries){
+ try {
+ datas.delete(key.getBytes());
+ } catch (Exception e) {
+ LOG.error("Error deleting object with id:{}", key, e);
+ }
+ }
+
+ }
+
+ public void store(String key, Object value) {
+ try {
+ LOG.debug("Storing key: {}", key);
+ datas.put(key.getBytes(), writeObject(value));
+ } catch (Exception e) {
+ LOG.error("Error storing object with id:{}", key, e);
+ }
+
+ }
+
+ public void storeAll(Map<String, Object> entries) {
+ for(String key: entries.keySet()){
+ store(key, entries.get(key));
+ }
+
+ }
+
+ public Object load(String key) {
+ Object result = null;
+ try {
+ result = readObject(datas.get(key.getBytes()));
+ } catch (Exception e) {
+ LOG.error("Error loading object with id: {}", key, e);
+ }
+ return result;
+ }
+
+ public Map<String, Object> loadAll(Collection<String> keys) {
+ Map<String, Object> result = null;
+ for(String key : keys){
+ if(result == null){
+ result = new HashMap<String, Object>();
+ }
+ result.put(key, load(key));
+ }
+ return result;
+ }
+
+ public Set<String> loadAllKeys() {
+ Set<String> keys = null;
+ while(datas.keyIterator().hasNext()){
+ String key = new String(datas.keyIterator().next());
+ if(keys == null){
+ keys = new HashSet<String>();
+ }
+ keys.add(key);
+ }
+ return keys;
+ }
+
+ private Object readObject(byte[] data) throws IOException, ClassNotFoundException {
+ Object result = null;
+ if (data != null) {
+ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
+ try {
+ result = ois.readObject();
+ } finally {
+ ois.close();
+ }
+ }
+ return result;
+ }
+
+ private byte[] writeObject(Object object) throws IOException{
+ byte[] result = null;
+ ByteArrayOutputStream buffer = null;
+ ObjectOutputStream out = null;
+ if (object != null) {
+ try {
+ buffer = new ByteArrayOutputStream();
+ out = new ObjectOutputStream(buffer);
+ out.writeObject(object);
+ } finally{
+ out.close();
+ }
+ result = buffer.toByteArray();
+ }
+ return result;
+ }
+
+ public int getSegmentFileSizeMB() {
+ return segmentFileSizeMB;
+ }
+
+ public void setSegmentFileSizeMB(int segmentFileSizeMB) {
+ this.segmentFileSizeMB = segmentFileSizeMB;
+ }
+
+ public String getStoreDirectory() {
+ return storeDirectory;
+ }
+
+ public void setStoreDirectory(String storeDirectory) {
+ this.storeDirectory = storeDirectory;
+ }
+
+ public int getInitCapacity() {
+ return initCapacity;
+ }
+
+ public void setInitCapacity(int initCapacity) {
+ this.initCapacity = initCapacity;
+ }
+
+}
View
247 src/main/java/org/apache/servicemix/store/krati/KratiStore.java
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.servicemix.store.krati;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Iterator;
+import krati.store.DataStore;
+import org.apache.servicemix.id.IdGenerator;
+import org.apache.servicemix.store.Entry;
+import org.apache.servicemix.store.base.BaseStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class KratiStore extends BaseStore{
+
+
+ private static final Logger LOG = LoggerFactory.getLogger(KratiStore.class);
+
+ private DataStore<byte[], byte[]> datas;
+ private IdGenerator idGenerator = new IdGenerator();
+ private final long timeout;
+
+ /**
+ * Constructor
+ * @param store
+ */
+ public KratiStore(DataStore<byte[], byte[]> store) {
+ this.datas = store;
+ this.timeout=-1;
+ }
+
+ /**
+ * Constructor
+ * @param store
+ * @param timeout
+ */
+ public KratiStore(DataStore<byte[], byte[]> store, long timeout) {
+ this.datas = store;
+ this.timeout = timeout;
+ }
+
+ /**
+ * Constructor
+ * @param store
+ * @param timeout
+ */
+ public KratiStore(DataStore<byte[], byte[]> store, long timeout, IdGenerator idGenerator) {
+ this.datas = store;
+ this.timeout = timeout;
+ this.idGenerator = idGenerator;
+ }
+
+
+ /**
+ * <p>
+ * Returns true if feature is provided by the store (clustered), false else.
+ * </p>
+ *
+ * @param feature the feature.
+ * @return true if the given feature is provided by the store, false else.
+ */
+ public boolean hasFeature(String feature) {
+ if (CLUSTERED.equals(feature))
+ return true;
+ return false;
+ }
+
+
+ /**
+ * <p>
+ * Put an object in the store under the given id.
+ * This method must be used with caution and the behavior is
+ * unspecified if an object already exist for the same id.
+ * </p>
+ * @param id the id of the object to store
+ * @param data the object to store
+ * @throws IOException if an error occurs
+ */
+ public void store(String key, Object value) {
+ LOG.debug("Storing object with key:{}, value:{}",key, value);
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ try {
+ Entry entry = new Entry(value);
+ ObjectOutputStream out = new ObjectOutputStream(buffer);
+ out.writeObject(entry);
+ out.close();
+
+ datas.put(key.getBytes(), buffer.toByteArray());
+ fireAddedEvent(key, value);
+ } catch (IOException e) {
+ LOG.error("Error storing key:{}", key, e);
+ } catch (Exception e) {
+ LOG.error("Error storing key:{}", key, e);
+ }
+ }
+
+ /**
+ * <p>
+ * Put an object into the store and return the unique id that
+ * may be used at a later time to retrieve the object.
+ * </p>
+ * @param data the object to store
+ * @return the id of the object stored
+ * @throws IOException if an error occurs
+ */
+ public String store(Object data) throws IOException {
+ String id = idGenerator.generateId();
+ store(id, data);
+ return id;
+ }
+
+ /**
+ * <p>
+ * Loads an object that has been previously stored under the specified key.
+ * The object is removed from the store.
+ * </p>
+ * @param id the id of the object
+ * @return the object, or <code>null></code> if the object could not be found
+ * @throws IOException if an error occurs
+ */
+ public Object load(String id) throws IOException {
+ LOG.debug("Loading/Removing object with id: {}", id);
+ Object result = null;
+ if(timeout > 0) {
+ evict();
+ }
+ result = peek(id);
+ try {
+ if(result != null && datas.delete(id.getBytes())) {
+ fireRemovedEvent(id,result);
+ }
+ } catch (Exception e) {
+ LOG.error("Error deleting object with id: {}", id);
+ }
+ return result;
+ }
+
+ /**
+ * <p>
+ * Loads an object that has been previously stored under the specified key.
+ * The object is not removed from the store.
+ * </p>
+ * @param id the id of the object
+ * @return the object, or <code>null</code> if the object could not be found
+ * @throws IOException if an error occurs
+ */
+ public Object peek(String id) throws IOException {
+ LOG.debug("Peeking object with id: {}", id);
+ byte[] value = datas.get(id.getBytes());
+ Entry result = null;
+ try {
+ result = (Entry)readObject(value);
+ } catch (ClassNotFoundException e) {
+ LOG.error("Error reading object with id: {}", id, e);
+ }
+ return result != null ? result.getData() : null;
+ }
+
+ /**
+ * <p>
+ * Deletes an object that has been previously stored under the specified key.
+ * </p>
+ * @param id the id of the object
+ * @throws Exception if an error occurs
+ */
+ public void delete(String id) throws Exception{
+ LOG.debug("Deleting object with id: {}", id);
+ datas.delete(id.getBytes());
+ }
+
+ private Object readObject(byte[] data) throws IOException, ClassNotFoundException {
+ Object result = null;
+ if (data != null) {
+ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
+ try {
+ result = ois.readObject();
+ } finally {
+ ois.close();
+ }
+ }
+ return result;
+ }
+
+ private void evict() {
+ long now = System.currentTimeMillis();
+ Iterator<byte[]> iter = datas.keyIterator();
+ while(iter.hasNext()){
+ byte[] key = iter.next();
+ String keyValue = null;
+ Entry value = null;
+ try {
+ keyValue = new String(key);
+ value = (Entry)readObject(datas.get(key));
+ } catch (Exception e) {
+ LOG.error("Error reading object with id: {}", key, e);
+ }
+ long age = now - value.getTime();
+ if (age > timeout) {
+
+ LOG.debug("Removing object with id {} from store after {} ms", keyValue, age);
+ try {
+ if(datas.delete(key)){
+ fireEvictedEvent(keyValue,value.getData());
+ }
+ } catch (Exception e) {
+ LOG.error("Error deleting object with id {}", keyValue);
+ }
+ }
+ }
+ }
+
+ public DataStore<byte[], byte[]> getDatas() {
+ return datas;
+ }
+
+ public void setDatas(DataStore<byte[], byte[]> datas) {
+ this.datas = datas;
+ }
+
+ public IdGenerator getIdGenerator() {
+ return idGenerator;
+ }
+
+ public void setIdGenerator(IdGenerator idGenerator) {
+ this.idGenerator = idGenerator;
+ }
+
+}
View
123 src/main/java/org/apache/servicemix/store/krati/KratiStoreFactory.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.servicemix.store.krati;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import krati.core.StoreConfig;
+import krati.core.StoreFactory;
+import krati.core.segment.MemorySegmentFactory;
+import krati.core.segment.Segment;
+import krati.store.DataStore;
+import org.apache.servicemix.store.Store;
+import org.apache.servicemix.store.StoreListener;
+import org.apache.servicemix.store.base.BaseStoreFactory;
+import org.apache.servicemix.store.mongo.MongoStore;
+import org.slf4j.LoggerFactory;
+
+public class KratiStoreFactory extends BaseStoreFactory{
+
+ private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(MongoStore.class);
+
+ private static final String DEFAULT_STORE_DIRECTORY = "/tmp/krati/";
+ private static final Integer INIT_CAPACITY = 10000;
+
+ private Map<String, KratiStore> stores = new HashMap<String, KratiStore>();
+
+ private long timeout = -1;
+ private String storeDirectory = DEFAULT_STORE_DIRECTORY;
+ private int initCapacity = INIT_CAPACITY;
+
+ public static final String STORE_PREFIX = "org.apache.servicemix.stores";
+
+ public synchronized Store open(String name) throws IOException {
+
+ KratiStore store = stores.get(name);
+ if (store == null) {
+ DataStore<byte[], byte[]> dataStore = null;
+ try {
+ String storeName = STORE_PREFIX + "." + name;
+ StringBuilder sb = new StringBuilder(storeDirectory);
+ sb.append("/");
+
+ File storeFolder = new File(sb.toString());
+ storeFolder.mkdir();
+ File file = new File(storeFolder,name);
+
+ StoreConfig config = new StoreConfig(file, initCapacity);
+ config.setSegmentFactory(new MemorySegmentFactory());
+ config.setSegmentFileSizeMB(Segment.minSegmentFileSizeMB);
+ dataStore = StoreFactory.createDynamicDataStore(config);
+
+ if (timeout <= 0) {
+ store = new KratiStore(dataStore);
+ } else {
+ store = new KratiStore(dataStore, timeout);
+ }
+ stores.put(name, store);
+ for (StoreListener listener : storeListeners) {
+ store.addListener(listener);
+ }
+ } catch (Exception e) {
+ LOG.error("Error during store initialization, {}", e, e);
+ }
+ }
+
+ return store;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.servicemix.store.ExchangeStoreFactory#release(org.apache.servicemix.store.ExchangeStore)
+ */
+ public synchronized void close(Store store) throws IOException {
+ KratiStore kratiStore = (KratiStore)store;
+ if(kratiStore.getDatas() != null && kratiStore.getDatas().isOpen()){
+ kratiStore.getDatas().close();
+ }
+ stores.remove(store);
+ }
+
+ public long getTimeout() {
+ return timeout;
+ }
+
+ public void setTimeout(long timeout) {
+ this.timeout = timeout;
+ }
+
+ public String getStoreDirectory() {
+ return storeDirectory;
+ }
+
+ public void setStoreDirectory(String storeDirectory) {
+ this.storeDirectory = storeDirectory;
+ }
+
+ public int getInitCapacity() {
+ return initCapacity;
+ }
+
+ public void setInitCapacity(int initCapacity) {
+ this.initCapacity = initCapacity;
+ }
+
+
+
+
+}
View
51 src/test/java/org/apache/servicemix/store/hazelcast/HazelcastStoreTest.java
@@ -17,14 +17,18 @@
package org.apache.servicemix.store.hazelcast;
-import com.hazelcast.config.Config;
-import org.apache.servicemix.store.Entry;
import java.util.Map;
-import com.hazelcast.core.Hazelcast;
-import com.hazelcast.core.HazelcastInstance;
+
import junit.framework.TestCase;
+
+import org.apache.servicemix.store.Entry;
import org.apache.servicemix.store.Store;
-import static org.junit.Assert.*;
+import org.apache.servicemix.store.krati.KratiPersistenceProvider;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.config.MapStoreConfig;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
/**
*
@@ -62,7 +66,6 @@ protected void tearDown() throws Exception {
factory.close(store);
}
-
public void testTimeout() throws Exception {
String id = store.store("Any kind of data...");
Object data = store.load(id);
@@ -75,7 +78,6 @@ public void testTimeout() throws Exception {
assertNull("Data should have been removed from store after timeout", store.load(id));
}
-
public void testDistributesStoreAndLoad() throws Exception {
//Create a new Hazelcast instance
HazelcastInstance instance = Hazelcast.newHazelcastInstance(config);
@@ -88,4 +90,39 @@ public void testDistributesStoreAndLoad() throws Exception {
assertNotNull(data);
assertEquals(data, result);
}
+
+ public void testInstanceWithPersistenceProvider() throws Exception {
+
+ // Create a new HazelCast Instance configured to have persistence
+ // provider
+ Config config = new Config();
+ config.getGroupConfig().setName("testgroup-peristence");
+ config.getGroupConfig().setPassword("testpwd");
+ MapStoreConfig mapConfig = new MapStoreConfig();
+ mapConfig.setEnabled(true);
+ mapConfig.setWriteDelaySeconds(0);
+ //Instantiate the implementation provider
+ KratiPersistenceProvider provider = new KratiPersistenceProvider("target/krati");
+
+ mapConfig.setImplementation(provider);
+ config.getMapConfig(
+ HazelcastStoreFactory.STORE_PREFIX + ".test.persistence").setMapStoreConfig(mapConfig);
+ HazelcastInstance instance = Hazelcast.newHazelcastInstance(config);
+
+ String id = "testId";
+ String data = "testData";
+ Map map = instance.getMap(HazelcastStoreFactory.STORE_PREFIX + ".test.persistence");
+ map.put(id, data);
+
+ Object retObj = provider.load(id);
+ assertEquals(data, retObj);
+
+ map.remove(id);
+ assertNull(provider.load(id));
+
+ provider.destroy();
+
+ }
+
+
}
View
80 src/test/java/org/apache/servicemix/store/krati/KratiStoreTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.servicemix.store.krati;
+
+import java.io.File;
+import junit.framework.TestCase;
+import krati.store.DataStore;
+
+import org.slf4j.LoggerFactory;
+
+public class KratiStoreTest extends TestCase {
+
+ private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(KratiStoreTest.class);
+
+ private static final long TIMEOUT = 1000L;
+ private static final String TEST_CACHE_NAME = "TEST_CACHE_NAME";
+ private KratiStore store;
+ private KratiStoreFactory storeFactory;
+
+ DataStore<byte[], byte[]> dataStore = null;
+
+ public KratiStoreTest() {
+ if(storeFactory == null){
+ storeFactory = new KratiStoreFactory();
+ storeFactory.setStoreDirectory("target/krati-store/");
+ storeFactory.setTimeout(TIMEOUT);
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ if(store == null){
+ store = (KratiStore)storeFactory.open(TEST_CACHE_NAME);
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ storeFactory.close(store);
+ }
+
+ public void testStore() throws Exception {
+ String id = "1";
+ String data = "Test data";
+ store.store(id,data);
+ assertEquals(data,store.peek(id));
+ assertEquals(data,store.load(id));
+ assertEquals(null,store.load(id));
+ }
+
+ public void testTimeout() throws Exception {
+ String id = store.store("Any kind of data...");
+ Object data = store.load(id);
+ assertNotNull(data);
+ //now store it again and load it after the timeout
+ store.store(id, data);
+ synchronized (this) {
+ wait(TIMEOUT * 2);
+ }
+ assertNull("Data should have been removed from store after timeout", store.load(id));
+ }
+
+
+}
Please sign in to comment.
Something went wrong with that request. Please try again.