Madan Jampani

Using net.jodah.expiringmap.ExpiringMap for tracking ttl expiration of database entries.

Minor javadoc updates.
...@@ -22,6 +22,7 @@ public class ReadResult { ...@@ -22,6 +22,7 @@ public class ReadResult {
22 22
23 /** 23 /**
24 * Returns the status of the read operation. 24 * Returns the status of the read operation.
25 + * @return read operation status
25 */ 26 */
26 public ReadStatus status() { 27 public ReadStatus status() {
27 return status; 28 return status;
......
...@@ -20,11 +20,12 @@ import java.io.IOException; ...@@ -20,11 +20,12 @@ import java.io.IOException;
20 import java.util.HashMap; 20 import java.util.HashMap;
21 import java.util.Map; 21 import java.util.Map;
22 import java.util.Objects; 22 import java.util.Objects;
23 +import java.util.concurrent.TimeUnit;
23 import java.util.concurrent.atomic.AtomicBoolean; 24 import java.util.concurrent.atomic.AtomicBoolean;
24 25
25 -//import net.jodah.expiringmap.ExpiringMap; 26 +import net.jodah.expiringmap.ExpiringMap;
26 -//import net.jodah.expiringmap.ExpiringMap.ExpirationListener; 27 +import net.jodah.expiringmap.ExpiringMap.ExpirationListener;
27 -//import net.jodah.expiringmap.ExpiringMap.ExpirationPolicy; 28 +import net.jodah.expiringmap.ExpiringMap.ExpirationPolicy;
28 import net.kuujo.copycat.cluster.Member; 29 import net.kuujo.copycat.cluster.Member;
29 import net.kuujo.copycat.event.EventHandler; 30 import net.kuujo.copycat.event.EventHandler;
30 import net.kuujo.copycat.event.LeaderElectEvent; 31 import net.kuujo.copycat.event.LeaderElectEvent;
...@@ -34,19 +35,22 @@ import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService; ...@@ -34,19 +35,22 @@ import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
34 import org.onlab.onos.store.cluster.messaging.ClusterMessage; 35 import org.onlab.onos.store.cluster.messaging.ClusterMessage;
35 import org.onlab.onos.store.cluster.messaging.MessageSubject; 36 import org.onlab.onos.store.cluster.messaging.MessageSubject;
36 import org.onlab.onos.store.service.DatabaseService; 37 import org.onlab.onos.store.service.DatabaseService;
38 +import org.onlab.onos.store.service.VersionedValue;
37 import org.slf4j.Logger; 39 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory; 40 import org.slf4j.LoggerFactory;
39 41
40 /** 42 /**
41 - * Database update event handler. 43 + * Plugs into the database update stream and track the TTL of entries added to
44 + * the database. For tables with pre-configured finite TTL, this class has
45 + * mechanisms for expiring (deleting) old, expired entries from the database.
42 */ 46 */
43 -public class DatabaseUpdateEventHandler implements 47 +public class DatabaseEntryExpirationTracker implements
44 - DatabaseUpdateEventListener, EventHandler<LeaderElectEvent> { 48 + DatabaseUpdateEventListener, EventHandler<LeaderElectEvent> {
45 49
46 private final Logger log = LoggerFactory.getLogger(getClass()); 50 private final Logger log = LoggerFactory.getLogger(getClass());
47 51
48 - public static final MessageSubject DATABASE_UPDATES = 52 + public static final MessageSubject DATABASE_UPDATES = new MessageSubject(
49 - new MessageSubject("database-update-event"); 53 + "database-update-event");
50 54
51 private DatabaseService databaseService; 55 private DatabaseService databaseService;
52 private ClusterService cluster; 56 private ClusterService cluster;
...@@ -54,29 +58,32 @@ public class DatabaseUpdateEventHandler implements ...@@ -54,29 +58,32 @@ public class DatabaseUpdateEventHandler implements
54 58
55 private final Member localMember; 59 private final Member localMember;
56 private final AtomicBoolean isLocalMemberLeader = new AtomicBoolean(false); 60 private final AtomicBoolean isLocalMemberLeader = new AtomicBoolean(false);
57 - private final Map<String, Map<DatabaseRow, Void>> tableEntryExpirationMap = new HashMap<>();
58 - //private final ExpirationListener<DatabaseRow, Void> expirationObserver = new ExpirationObserver();
59 61
60 - DatabaseUpdateEventHandler(Member localMember) { 62 + private final Map<String, Map<DatabaseRow, VersionedValue>> tableEntryExpirationMap = new HashMap<>();
63 +
64 + private final ExpirationListener<DatabaseRow, VersionedValue> expirationObserver = new ExpirationObserver();
65 +
66 + DatabaseEntryExpirationTracker(Member localMember) {
61 this.localMember = localMember; 67 this.localMember = localMember;
62 } 68 }
63 69
64 @Override 70 @Override
65 public void tableModified(TableModificationEvent event) { 71 public void tableModified(TableModificationEvent event) {
66 DatabaseRow row = new DatabaseRow(event.tableName(), event.key()); 72 DatabaseRow row = new DatabaseRow(event.tableName(), event.key());
67 - Map<DatabaseRow, Void> map = tableEntryExpirationMap.get(event.tableName()); 73 + Map<DatabaseRow, VersionedValue> map = tableEntryExpirationMap
74 + .get(event.tableName());
68 75
69 switch (event.type()) { 76 switch (event.type()) {
70 case ROW_DELETED: 77 case ROW_DELETED:
71 if (isLocalMemberLeader.get()) { 78 if (isLocalMemberLeader.get()) {
72 try { 79 try {
73 - clusterCommunicator.broadcast( 80 + clusterCommunicator.broadcast(new ClusterMessage(cluster
74 - new ClusterMessage( 81 + .getLocalNode().id(), DATABASE_UPDATES,
75 - cluster.getLocalNode().id(), 82 + DatabaseStateMachine.SERIALIZER.encode(event)));
76 - DATABASE_UPDATES,
77 - DatabaseStateMachine.SERIALIZER.encode(event)));
78 } catch (IOException e) { 83 } catch (IOException e) {
79 - log.error("Failed to broadcast a database table modification event.", e); 84 + log.error(
85 + "Failed to broadcast a database table modification event.",
86 + e);
80 } 87 }
81 } 88 }
82 break; 89 break;
...@@ -94,15 +101,11 @@ public class DatabaseUpdateEventHandler implements ...@@ -94,15 +101,11 @@ public class DatabaseUpdateEventHandler implements
94 // make this explicit instead of relying on a negative value 101 // make this explicit instead of relying on a negative value
95 // to indicate no expiration. 102 // to indicate no expiration.
96 if (expirationTimeMillis > 0) { 103 if (expirationTimeMillis > 0) {
97 - tableEntryExpirationMap.put(tableName, null); 104 + tableEntryExpirationMap.put(tableName, ExpiringMap.builder()
98 - /*
99 - ExpiringMap.builder()
100 .expiration(expirationTimeMillis, TimeUnit.SECONDS) 105 .expiration(expirationTimeMillis, TimeUnit.SECONDS)
101 .expirationListener(expirationObserver) 106 .expirationListener(expirationObserver)
102 // FIXME: make the expiration policy configurable. 107 // FIXME: make the expiration policy configurable.
103 - .expirationPolicy(ExpirationPolicy.CREATED) 108 + .expirationPolicy(ExpirationPolicy.CREATED).build());
104 - .build());
105 - */
106 } 109 }
107 } 110 }
108 111
...@@ -111,27 +114,40 @@ public class DatabaseUpdateEventHandler implements ...@@ -111,27 +114,40 @@ public class DatabaseUpdateEventHandler implements
111 tableEntryExpirationMap.remove(tableName); 114 tableEntryExpirationMap.remove(tableName);
112 } 115 }
113 116
114 - /* 117 + private class ExpirationObserver implements
115 - private class ExpirationObserver implements ExpirationListener<DatabaseRow, Void> { 118 + ExpirationListener<DatabaseRow, VersionedValue> {
116 @Override 119 @Override
117 - public void expired(DatabaseRow key, Void value) { 120 + public void expired(DatabaseRow key, VersionedValue value) {
118 try { 121 try {
119 - // TODO: The safety of this check needs to be verified.
120 - // Couple of issues:
121 - // 1. It is very likely that only one member should attempt deletion of the entry from database.
122 - // 2. A potential race condition exists where the entry expires, but before its can be deleted
123 - // from the database, a new entry is added or existing entry is updated.
124 - // That means ttl and expiration should be for a given version.
125 if (isLocalMemberLeader.get()) { 122 if (isLocalMemberLeader.get()) {
126 - databaseService.remove(key.tableName, key.key); 123 + if (!databaseService.removeIfVersionMatches(key.tableName,
124 + key.key, value.version())) {
125 + log.info("Entry in the database changed before right its TTL expiration.");
126 + }
127 + } else {
128 + // If this node is not the current leader, we should never
129 + // let the expiring entries drop off
130 + // Under stable conditions (i.e no leadership switch) the
131 + // current leader will initiate
132 + // a database remove and this instance will get notified
133 + // of a tableModification event causing it to remove from
134 + // the map.
135 + Map<DatabaseRow, VersionedValue> map = tableEntryExpirationMap
136 + .get(key.tableName);
137 + if (map != null) {
138 + map.put(key, value);
139 + }
127 } 140 }
141 +
128 } catch (Exception e) { 142 } catch (Exception e) {
129 - log.warn("Failed to delete entry from the database after ttl expiration. Will retry eviction", e); 143 + log.warn(
130 - tableEntryExpirationMap.get(key.tableName).put(new DatabaseRow(key.tableName, key.key), null); 144 + "Failed to delete entry from the database after ttl expiration. Will retry eviction",
145 + e);
146 + tableEntryExpirationMap.get(key.tableName).put(
147 + new DatabaseRow(key.tableName, key.key), value);
131 } 148 }
132 } 149 }
133 } 150 }
134 - */
135 151
136 @Override 152 @Override
137 public void handle(LeaderElectEvent event) { 153 public void handle(LeaderElectEvent event) {
...@@ -140,6 +156,9 @@ public class DatabaseUpdateEventHandler implements ...@@ -140,6 +156,9 @@ public class DatabaseUpdateEventHandler implements
140 } 156 }
141 } 157 }
142 158
159 + /**
160 + * Wrapper class for a database row identifier.
161 + */
143 private class DatabaseRow { 162 private class DatabaseRow {
144 163
145 String tableName; 164 String tableName;
...@@ -160,8 +179,8 @@ public class DatabaseUpdateEventHandler implements ...@@ -160,8 +179,8 @@ public class DatabaseUpdateEventHandler implements
160 } 179 }
161 DatabaseRow that = (DatabaseRow) obj; 180 DatabaseRow that = (DatabaseRow) obj;
162 181
163 - return Objects.equals(this.tableName, that.tableName) && 182 + return Objects.equals(this.tableName, that.tableName)
164 - Objects.equals(this.key, that.key); 183 + && Objects.equals(this.key, that.key);
165 } 184 }
166 185
167 @Override 186 @Override
...@@ -169,4 +188,4 @@ public class DatabaseUpdateEventHandler implements ...@@ -169,4 +188,4 @@ public class DatabaseUpdateEventHandler implements
169 return Objects.hash(tableName, key); 188 return Objects.hash(tableName, key);
170 } 189 }
171 } 190 }
172 -}
...\ No newline at end of file ...\ No newline at end of file
191 +}
......
...@@ -237,8 +237,8 @@ public class DatabaseStateMachine implements StateMachine { ...@@ -237,8 +237,8 @@ public class DatabaseStateMachine implements StateMachine {
237 WriteResult putResult = new WriteResult(WriteStatus.OK, previousValue); 237 WriteResult putResult = new WriteResult(WriteStatus.OK, previousValue);
238 results.add(putResult); 238 results.add(putResult);
239 tableModificationEvent = (previousValue == null) ? 239 tableModificationEvent = (previousValue == null) ?
240 - TableModificationEvent.rowAdded(request.tableName(), request.key()) : 240 + TableModificationEvent.rowAdded(request.tableName(), request.key(), newValue) :
241 - TableModificationEvent.rowUpdated(request.tableName(), request.key()); 241 + TableModificationEvent.rowUpdated(request.tableName(), request.key(), newValue);
242 break; 242 break;
243 243
244 case REMOVE: 244 case REMOVE:
...@@ -249,7 +249,7 @@ public class DatabaseStateMachine implements StateMachine { ...@@ -249,7 +249,7 @@ public class DatabaseStateMachine implements StateMachine {
249 results.add(removeResult); 249 results.add(removeResult);
250 if (removedValue != null) { 250 if (removedValue != null) {
251 tableModificationEvent = 251 tableModificationEvent =
252 - TableModificationEvent.rowDeleted(request.tableName(), request.key()); 252 + TableModificationEvent.rowDeleted(request.tableName(), request.key(), removedValue);
253 } 253 }
254 break; 254 break;
255 255
......
...@@ -29,15 +29,14 @@ public interface DatabaseUpdateEventListener { ...@@ -29,15 +29,14 @@ public interface DatabaseUpdateEventListener {
29 29
30 /** 30 /**
31 * Notifies listeners of a table created event. 31 * Notifies listeners of a table created event.
32 - * @param tableName 32 + * @param tableName name of the table created
33 - * @param expirationTimeMillis 33 + * @param expirationTimeMillis TTL for entries added to the table (measured since last update time)
34 */ 34 */
35 public void tableCreated(String tableName, int expirationTimeMillis); 35 public void tableCreated(String tableName, int expirationTimeMillis);
36 36
37 /** 37 /**
38 * Notifies listeners of a table deleted event. 38 * Notifies listeners of a table deleted event.
39 - * @param tableName 39 + * @param tableName name of the table deleted
40 */ 40 */
41 public void tableDeleted(String tableName); 41 public void tableDeleted(String tableName);
42 -
43 } 42 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -33,7 +33,8 @@ public class DistributedLockManager implements LockService { ...@@ -33,7 +33,8 @@ public class DistributedLockManager implements LockService {
33 33
34 public static final String ONOS_LOCK_TABLE_NAME = "onos-locks"; 34 public static final String ONOS_LOCK_TABLE_NAME = "onos-locks";
35 35
36 - private final ArrayListMultimap<String, LockRequest> locksToAcquire = ArrayListMultimap.create(); 36 + private final ArrayListMultimap<String, LockRequest> locksToAcquire = ArrayListMultimap
37 + .create();
37 38
38 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 39 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
39 private ClusterCommunicationService clusterCommunicator; 40 private ClusterCommunicationService clusterCommunicator;
...@@ -61,11 +62,7 @@ public class DistributedLockManager implements LockService { ...@@ -61,11 +62,7 @@ public class DistributedLockManager implements LockService {
61 62
62 @Override 63 @Override
63 public Lock create(String path) { 64 public Lock create(String path) {
64 - return new DistributedLock( 65 + return new DistributedLock(path, databaseService, clusterService, this);
65 - path,
66 - databaseService,
67 - clusterService,
68 - this);
69 } 66 }
70 67
71 @Override 68 @Override
...@@ -80,21 +77,19 @@ public class DistributedLockManager implements LockService { ...@@ -80,21 +77,19 @@ public class DistributedLockManager implements LockService {
80 throw new UnsupportedOperationException(); 77 throw new UnsupportedOperationException();
81 } 78 }
82 79
83 - protected CompletableFuture<Void> lockIfAvailable( 80 + protected CompletableFuture<Void> lockIfAvailable(Lock lock,
84 - Lock lock, 81 + long waitTimeMillis, int leaseDurationMillis) {
85 - long waitTimeMillis,
86 - int leaseDurationMillis) {
87 CompletableFuture<Void> future = new CompletableFuture<>(); 82 CompletableFuture<Void> future = new CompletableFuture<>();
88 - locksToAcquire.put( 83 + locksToAcquire.put(lock.path(), new LockRequest(lock, waitTimeMillis,
89 - lock.path(), 84 + leaseDurationMillis, future));
90 - new LockRequest(lock, waitTimeMillis, leaseDurationMillis, future));
91 return future; 85 return future;
92 } 86 }
93 87
94 private class LockEventMessageListener implements ClusterMessageHandler { 88 private class LockEventMessageListener implements ClusterMessageHandler {
95 @Override 89 @Override
96 public void handle(ClusterMessage message) { 90 public void handle(ClusterMessage message) {
97 - TableModificationEvent event = DatabaseStateMachine.SERIALIZER.decode(message.payload()); 91 + TableModificationEvent event = DatabaseStateMachine.SERIALIZER
92 + .decode(message.payload());
98 if (!event.tableName().equals(ONOS_LOCK_TABLE_NAME)) { 93 if (!event.tableName().equals(ONOS_LOCK_TABLE_NAME)) {
99 return; 94 return;
100 } 95 }
...@@ -110,15 +105,20 @@ public class DistributedLockManager implements LockService { ...@@ -110,15 +105,20 @@ public class DistributedLockManager implements LockService {
110 return; 105 return;
111 } 106 }
112 107
113 - Iterator<LockRequest> existingRequestIterator = existingRequests.iterator(); 108 + synchronized (existingRequests) {
114 - while (existingRequestIterator.hasNext()) { 109 +
115 - LockRequest request = existingRequestIterator.next(); 110 + Iterator<LockRequest> existingRequestIterator = existingRequests
116 - if (request.expirationTime().isAfter(DateTime.now())) { 111 + .iterator();
117 - existingRequestIterator.remove(); 112 + while (existingRequestIterator.hasNext()) {
118 - } else { 113 + LockRequest request = existingRequestIterator.next();
119 - if (request.lock().tryLock(request.leaseDurationMillis())) { 114 + if (request.expirationTime().isAfter(DateTime.now())) {
120 - request.future().complete(null); 115 + existingRequestIterator.remove();
121 - existingRequests.remove(0); 116 + } else {
117 + if (request.lock().tryLock(
118 + request.leaseDurationMillis())) {
119 + request.future().complete(null);
120 + existingRequestIterator.remove();
121 + }
122 } 122 }
123 } 123 }
124 } 124 }
...@@ -133,14 +133,12 @@ public class DistributedLockManager implements LockService { ...@@ -133,14 +133,12 @@ public class DistributedLockManager implements LockService {
133 private final int leaseDurationMillis; 133 private final int leaseDurationMillis;
134 private final CompletableFuture<Void> future; 134 private final CompletableFuture<Void> future;
135 135
136 - public LockRequest( 136 + public LockRequest(Lock lock, long waitTimeMillis,
137 - Lock lock, 137 + int leaseDurationMillis, CompletableFuture<Void> future) {
138 - long waitTimeMillis,
139 - int leaseDurationMillis,
140 - CompletableFuture<Void> future) {
141 138
142 this.lock = lock; 139 this.lock = lock;
143 - this.expirationTime = DateTime.now().plusMillis((int) waitTimeMillis); 140 + this.expirationTime = DateTime.now().plusMillis(
141 + (int) waitTimeMillis);
144 this.leaseDurationMillis = leaseDurationMillis; 142 this.leaseDurationMillis = leaseDurationMillis;
145 this.future = future; 143 this.future = future;
146 } 144 }
......
1 package org.onlab.onos.store.service.impl; 1 package org.onlab.onos.store.service.impl;
2 2
3 +import org.onlab.onos.store.service.VersionedValue;
4 +
3 /** 5 /**
4 * A table modification event. 6 * A table modification event.
5 */ 7 */
...@@ -17,41 +19,46 @@ public final class TableModificationEvent { ...@@ -17,41 +19,46 @@ public final class TableModificationEvent {
17 19
18 private final String tableName; 20 private final String tableName;
19 private final String key; 21 private final String key;
22 + private final VersionedValue value;
20 private final Type type; 23 private final Type type;
21 24
22 /** 25 /**
23 * Creates a new row deleted table modification event. 26 * Creates a new row deleted table modification event.
24 * @param tableName table name. 27 * @param tableName table name.
25 * @param key row key 28 * @param key row key
29 + * @param value value associated with the key when it was deleted.
26 * @return table modification event. 30 * @return table modification event.
27 */ 31 */
28 - public static TableModificationEvent rowDeleted(String tableName, String key) { 32 + public static TableModificationEvent rowDeleted(String tableName, String key, VersionedValue value) {
29 - return new TableModificationEvent(tableName, key, Type.ROW_DELETED); 33 + return new TableModificationEvent(tableName, key, value, Type.ROW_DELETED);
30 } 34 }
31 35
32 /** 36 /**
33 * Creates a new row added table modification event. 37 * Creates a new row added table modification event.
34 * @param tableName table name. 38 * @param tableName table name.
35 * @param key row key 39 * @param key row key
40 + * @param value value associated with the key
36 * @return table modification event. 41 * @return table modification event.
37 */ 42 */
38 - public static TableModificationEvent rowAdded(String tableName, String key) { 43 + public static TableModificationEvent rowAdded(String tableName, String key, VersionedValue value) {
39 - return new TableModificationEvent(tableName, key, Type.ROW_ADDED); 44 + return new TableModificationEvent(tableName, key, value, Type.ROW_ADDED);
40 } 45 }
41 46
42 /** 47 /**
43 * Creates a new row updated table modification event. 48 * Creates a new row updated table modification event.
44 * @param tableName table name. 49 * @param tableName table name.
45 * @param key row key 50 * @param key row key
51 + * @param newValue value
46 * @return table modification event. 52 * @return table modification event.
47 */ 53 */
48 - public static TableModificationEvent rowUpdated(String tableName, String key) { 54 + public static TableModificationEvent rowUpdated(String tableName, String key, VersionedValue newValue) {
49 - return new TableModificationEvent(tableName, key, Type.ROW_UPDATED); 55 + return new TableModificationEvent(tableName, key, newValue, Type.ROW_UPDATED);
50 } 56 }
51 57
52 - private TableModificationEvent(String tableName, String key, Type type) { 58 + private TableModificationEvent(String tableName, String key, VersionedValue value, Type type) {
53 this.tableName = tableName; 59 this.tableName = tableName;
54 this.key = key; 60 this.key = key;
61 + this.value = value;
55 this.type = type; 62 this.type = type;
56 } 63 }
57 64
...@@ -72,6 +79,15 @@ public final class TableModificationEvent { ...@@ -72,6 +79,15 @@ public final class TableModificationEvent {
72 } 79 }
73 80
74 /** 81 /**
82 + * Returns the value associated with the key. If the event for a deletion, this
83 + * method returns value that was deleted.
84 + * @return row value
85 + */
86 + public VersionedValue value() {
87 + return value;
88 + }
89 +
90 + /**
75 * Returns the type of table modification event. 91 * Returns the type of table modification event.
76 * @return event type. 92 * @return event type.
77 */ 93 */
......
...@@ -39,30 +39,23 @@ ...@@ -39,30 +39,23 @@
39 </dependency> 39 </dependency>
40 40
41 <dependency> 41 <dependency>
42 - <groupId>net.kuujo.copycat</groupId> 42 + <groupId>net.jodah</groupId>
43 - <artifactId>copycat</artifactId> 43 + <artifactId>expiringmap</artifactId>
44 - <version>${copycat.version}</version> 44 + <version>0.3.1</version>
45 </dependency> 45 </dependency>
46 -<!-- Commented out due to Chronicle + OSGi issue 46 +
47 <dependency> 47 <dependency>
48 <groupId>net.kuujo.copycat</groupId> 48 <groupId>net.kuujo.copycat</groupId>
49 - <artifactId>copycat-chronicle</artifactId> 49 + <artifactId>copycat</artifactId>
50 <version>${copycat.version}</version> 50 <version>${copycat.version}</version>
51 </dependency> 51 </dependency>
52 ---> 52 +
53 <dependency> 53 <dependency>
54 <groupId>net.kuujo.copycat</groupId> 54 <groupId>net.kuujo.copycat</groupId>
55 <artifactId>copycat-tcp</artifactId> 55 <artifactId>copycat-tcp</artifactId>
56 <version>${copycat.version}</version> 56 <version>${copycat.version}</version>
57 </dependency> 57 </dependency>
58 58
59 -<!-- chronicle transitive dependency
60 - <dependency>
61 - <groupId>net.java.dev.jna</groupId>
62 - <artifactId>jna</artifactId>
63 - <version>4.1.0</version>
64 - </dependency>
65 --->
66 </dependencies> 59 </dependencies>
67 60
68 <build> 61 <build>
...@@ -89,20 +82,19 @@ ...@@ -89,20 +82,19 @@
89 </filter> 82 </filter>
90 83
91 <filter> 84 <filter>
92 - <artifact>net.kuujo.copycat:*</artifact> 85 + <artifact>net.jodah.expiringmap:*</artifact>
93 <includes> 86 <includes>
94 - <include>net/kuujo/copycat/**</include> 87 + <include>net/jodah/expiringmap/**</include>
95 </includes> 88 </includes>
96 </filter> 89 </filter>
97 -<!-- chronicle transitive dependency
98 90
99 <filter> 91 <filter>
100 - <artifact>net.java.dev.jna:*</artifact> 92 + <artifact>net.kuujo.copycat:*</artifact>
101 <includes> 93 <includes>
102 - <include>com/sun/jna/**</include> 94 + <include>net/kuujo/copycat/**</include>
103 </includes> 95 </includes>
104 </filter> 96 </filter>
105 ---> 97 +
106 </filters> 98 </filters>
107 </configuration> 99 </configuration>
108 <executions> 100 <executions>
...@@ -120,7 +112,7 @@ ...@@ -120,7 +112,7 @@
120 <configuration> 112 <configuration>
121 <instructions> 113 <instructions>
122 <Export-Package> 114 <Export-Package>
123 - com.googlecode.concurrenttrees.*;net.kuujo.copycat.* 115 + com.googlecode.concurrenttrees.*;net.kuujo.copycat.*;net.jodah.expiringmap.*
124 </Export-Package> 116 </Export-Package>
125 </instructions> 117 </instructions>
126 </configuration> 118 </configuration>
......