You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by il...@apache.org on 2018/06/14 02:14:58 UTC

[incubator-dubbo] branch master updated: add an ExpiryCache for CacheFilter (#1707)

This is an automated email from the ASF dual-hosted git repository.

iluo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-dubbo.git


The following commit(s) were added to refs/heads/master by this push:
     new 928292b  add an ExpiryCache  for CacheFilter (#1707)
928292b is described below

commit 928292b1a1938dad96d3965e7f48da5dc0a09373
Author: 煦胤 <xi...@foxmail.com>
AuthorDate: Thu Jun 14 10:14:50 2018 +0800

    add an ExpiryCache  for CacheFilter (#1707)
    
    * ExpiryCache - With the characteristic of expiration time
    
    * ExpiryCache - With the characteristic of expiration time
    
    * fix 1 Checkstyle violation
    
    * add destroy method for cache to release resources
    
    * add destroy method for cache to release resources
    
    * fix a Checkstyle violation
    
    * fix a Checkstyle violation
    
    * add expiryCacheFactoryTest
    
    * delete unused comments code
    
    * fix ShutdownHook Close a non-SPI cache
    
    * delete test code
    
    * delete ExpiringMap's deamon thread release and rename Expiry to Expiring
    
    * rename package expiry to expiring
    
    * remove lock for ExpiryObject
    
    * move unused import
    
    *  rollback my change made against CacheFilterTest
    
    * add expiring into CacheFilterTest
---
 .../com/alibaba/dubbo/common/utils/LRUCache.java   |  12 +-
 .../alibaba/dubbo/config/DubboShutdownHook.java    |   8 +-
 .../cache/support/expiring/ExpiringCache.java      |  50 +++
 .../support/expiring/ExpiringCacheFactory.java     |  32 ++
 .../dubbo/cache/support/expiring/ExpiringMap.java  | 373 +++++++++++++++++++++
 .../internal/com.alibaba.dubbo.cache.CacheFactory  |   3 +-
 .../dubbo/cache/filter/CacheFilterTest.java        |   6 +-
 .../support/expiring/ExpiringCacheFactoryTest.java |  38 +++
 8 files changed, 512 insertions(+), 10 deletions(-)

diff --git a/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/LRUCache.java b/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/LRUCache.java
index 9365d65..835772b 100644
--- a/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/LRUCache.java
+++ b/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/LRUCache.java
@@ -46,8 +46,8 @@ public class LRUCache<K, V> extends LinkedHashMap<K, V> {
 
     @Override
     public boolean containsKey(Object key) {
+        lock.lock();
         try {
-            lock.lock();
             return super.containsKey(key);
         } finally {
             lock.unlock();
@@ -56,8 +56,8 @@ public class LRUCache<K, V> extends LinkedHashMap<K, V> {
 
     @Override
     public V get(Object key) {
+        lock.lock();
         try {
-            lock.lock();
             return super.get(key);
         } finally {
             lock.unlock();
@@ -66,8 +66,8 @@ public class LRUCache<K, V> extends LinkedHashMap<K, V> {
 
     @Override
     public V put(K key, V value) {
+        lock.lock();
         try {
-            lock.lock();
             return super.put(key, value);
         } finally {
             lock.unlock();
@@ -76,8 +76,8 @@ public class LRUCache<K, V> extends LinkedHashMap<K, V> {
 
     @Override
     public V remove(Object key) {
+        lock.lock();
         try {
-            lock.lock();
             return super.remove(key);
         } finally {
             lock.unlock();
@@ -86,8 +86,8 @@ public class LRUCache<K, V> extends LinkedHashMap<K, V> {
 
     @Override
     public int size() {
+        lock.lock();
         try {
-            lock.lock();
             return super.size();
         } finally {
             lock.unlock();
@@ -96,8 +96,8 @@ public class LRUCache<K, V> extends LinkedHashMap<K, V> {
 
     @Override
     public void clear() {
+        lock.lock();
         try {
-            lock.lock();
             super.clear();
         } finally {
             lock.unlock();
diff --git a/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/DubboShutdownHook.java b/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/DubboShutdownHook.java
index 348c5e6..0da42da 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/DubboShutdownHook.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/DubboShutdownHook.java
@@ -68,6 +68,13 @@ public class DubboShutdownHook extends Thread {
         // destroy all the registries
         AbstractRegistryFactory.destroyAll();
         // destroy all the protocols
+        destroyProtocols();
+    }
+
+    /**
+     * Destroy all the protocols.
+     */
+    private void destroyProtocols() {
         ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
         for (String protocolName : loader.getLoadedExtensions()) {
             try {
@@ -81,5 +88,4 @@ public class DubboShutdownHook extends Thread {
         }
     }
 
-
 }
diff --git a/dubbo-filter/dubbo-filter-cache/src/main/java/com/alibaba/dubbo/cache/support/expiring/ExpiringCache.java b/dubbo-filter/dubbo-filter-cache/src/main/java/com/alibaba/dubbo/cache/support/expiring/ExpiringCache.java
new file mode 100644
index 0000000..570d98a
--- /dev/null
+++ b/dubbo-filter/dubbo-filter-cache/src/main/java/com/alibaba/dubbo/cache/support/expiring/ExpiringCache.java
@@ -0,0 +1,50 @@
+/*
+ * 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 com.alibaba.dubbo.cache.support.expiring;
+
+import com.alibaba.dubbo.cache.Cache;
+import com.alibaba.dubbo.common.URL;
+
+import java.util.Map;
+
+/**
+ * ExpiringCache - With the characteristic of expiration time.
+ */
+public class ExpiringCache implements Cache {
+    private final Map<Object, Object> store;
+
+    public ExpiringCache(URL url) {
+        // cache time (second)
+        final int secondsToLive = url.getParameter("cache.seconds", 180);
+        // Cache check interval (second)
+        final int intervalSeconds = url.getParameter("cache.interval", 4);
+        ExpiringMap<Object, Object> expiringMap = new ExpiringMap<Object, Object>(secondsToLive, intervalSeconds);
+        expiringMap.getExpireThread().startExpiryIfNotStarted();
+        this.store = expiringMap;
+    }
+
+    @Override
+    public void put(Object key, Object value) {
+        store.put(key, value);
+    }
+
+    @Override
+    public Object get(Object key) {
+        return store.get(key);
+    }
+
+}
diff --git a/dubbo-filter/dubbo-filter-cache/src/main/java/com/alibaba/dubbo/cache/support/expiring/ExpiringCacheFactory.java b/dubbo-filter/dubbo-filter-cache/src/main/java/com/alibaba/dubbo/cache/support/expiring/ExpiringCacheFactory.java
new file mode 100644
index 0000000..e8e27f2
--- /dev/null
+++ b/dubbo-filter/dubbo-filter-cache/src/main/java/com/alibaba/dubbo/cache/support/expiring/ExpiringCacheFactory.java
@@ -0,0 +1,32 @@
+/*
+ * 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 com.alibaba.dubbo.cache.support.expiring;
+
+import com.alibaba.dubbo.cache.Cache;
+import com.alibaba.dubbo.cache.support.AbstractCacheFactory;
+import com.alibaba.dubbo.common.URL;
+
+/**
+ * ExpiringCacheFactory
+ */
+public class ExpiringCacheFactory extends AbstractCacheFactory {
+    
+    @Override
+    protected Cache createCache(URL url) {
+        return new ExpiringCache(url);
+    }
+}
diff --git a/dubbo-filter/dubbo-filter-cache/src/main/java/com/alibaba/dubbo/cache/support/expiring/ExpiringMap.java b/dubbo-filter/dubbo-filter-cache/src/main/java/com/alibaba/dubbo/cache/support/expiring/ExpiringMap.java
new file mode 100644
index 0000000..8f05412
--- /dev/null
+++ b/dubbo-filter/dubbo-filter-cache/src/main/java/com/alibaba/dubbo/cache/support/expiring/ExpiringMap.java
@@ -0,0 +1,373 @@
+/*
+ * 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 com.alibaba.dubbo.cache.support.expiring;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * can be expired map
+ * Contains a background thread that periodically checks if the data is out of date
+ */
+public class ExpiringMap<K, V> implements Map<K, V> {
+
+    /**
+     * default time to live (second)
+     */
+    private static final int DEFAULT_TIME_TO_LIVE = 180;
+
+    /**
+     * default expire check interval (second)
+     */
+    private static final int DEFAULT_EXPIRATION_INTERVAL = 1;
+
+    private static volatile int expireCount = 1;
+
+    private final ConcurrentHashMap<K, ExpiryObject> delegateMap;
+
+    private final ExpireThread expireThread;
+
+    public ExpiringMap() {
+        this(DEFAULT_TIME_TO_LIVE, DEFAULT_EXPIRATION_INTERVAL);
+    }
+
+    /**
+     * Constructor
+     *
+     * @param timeToLive time to live (second)
+     */
+    public ExpiringMap(int timeToLive) {
+        this(timeToLive, DEFAULT_EXPIRATION_INTERVAL);
+    }
+
+    public ExpiringMap(int timeToLive, int expirationInterval) {
+        this(new ConcurrentHashMap<K, ExpiryObject>(), timeToLive, expirationInterval);
+    }
+
+    private ExpiringMap(ConcurrentHashMap<K, ExpiryObject> delegateMap, int timeToLive, int expirationInterval) {
+        this.delegateMap = delegateMap;
+        this.expireThread = new ExpireThread();
+        expireThread.setTimeToLive(timeToLive);
+        expireThread.setExpirationInterval(expirationInterval);
+    }
+
+    @Override
+    public V put(K key, V value) {
+        ExpiryObject answer = delegateMap.put(key, new ExpiryObject(key, value, System.currentTimeMillis()));
+        if (answer == null) {
+            return null;
+        }
+        return answer.getValue();
+    }
+
+    @Override
+    public V get(Object key) {
+        ExpiryObject object = delegateMap.get(key);
+        if (object != null) {
+            object.setLastAccessTime(System.currentTimeMillis());
+            return object.getValue();
+        }
+        return null;
+    }
+
+    @Override
+    public V remove(Object key) {
+        ExpiryObject answer = delegateMap.remove(key);
+        if (answer == null) {
+            return null;
+        }
+        return answer.getValue();
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+        return delegateMap.containsKey(key);
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+        return delegateMap.containsValue(value);
+    }
+
+    @Override
+    public int size() {
+        return delegateMap.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return delegateMap.isEmpty();
+    }
+
+    @Override
+    public void clear() {
+        delegateMap.clear();
+        expireThread.stopExpiring();
+    }
+
+    @Override
+    public int hashCode() {
+        return delegateMap.hashCode();
+    }
+
+    @Override
+    public Set<K> keySet() {
+        return delegateMap.keySet();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return delegateMap.equals(obj);
+    }
+
+    @Override
+    public void putAll(Map<? extends K, ? extends V> inMap) {
+        for (Entry<? extends K, ? extends V> e : inMap.entrySet()) {
+            this.put(e.getKey(), e.getValue());
+        }
+    }
+
+    @Override
+    public Collection<V> values() {
+        List<V> list = new ArrayList<V>();
+        Set<Entry<K, ExpiryObject>> delegatedSet = delegateMap.entrySet();
+        for (Entry<K, ExpiryObject> entry : delegatedSet) {
+            ExpiryObject value = entry.getValue();
+            list.add(value.getValue());
+        }
+        return list;
+    }
+
+    @Override
+    public Set<Entry<K, V>> entrySet() {
+        throw new UnsupportedOperationException();
+    }
+
+    public ExpireThread getExpireThread() {
+        return expireThread;
+    }
+
+    public int getExpirationInterval() {
+        return expireThread.getExpirationInterval();
+    }
+
+    public void setExpirationInterval(int expirationInterval) {
+        expireThread.setExpirationInterval(expirationInterval);
+    }
+
+    public int getTimeToLive() {
+        return expireThread.getTimeToLive();
+    }
+
+    public void setTimeToLive(int timeToLive) {
+        expireThread.setTimeToLive(timeToLive);
+    }
+
+    @Override
+    public String toString() {
+        return "ExpiringMap{" +
+                "delegateMap=" + delegateMap.toString() +
+                ", expireThread=" + expireThread.toString() +
+                '}';
+    }
+
+    /**
+     * can be expired object
+     */
+    private class ExpiryObject {
+        private K key;
+        private V value;
+        private AtomicLong lastAccessTime;
+
+        ExpiryObject(K key, V value, long lastAccessTime) {
+            if (value == null) {
+                throw new IllegalArgumentException("An expiring object cannot be null.");
+            }
+            this.key = key;
+            this.value = value;
+            this.lastAccessTime = new AtomicLong(lastAccessTime);
+        }
+
+        public long getLastAccessTime() {
+            return lastAccessTime.get();
+        }
+
+        public void setLastAccessTime(long lastAccessTime) {
+            this.lastAccessTime.set(lastAccessTime);
+        }
+
+        public K getKey() {
+            return key;
+        }
+
+        public V getValue() {
+            return value;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return value.equals(obj);
+        }
+
+        @Override
+        public int hashCode() {
+            return value.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            return "ExpiryObject{" +
+                    "key=" + key +
+                    ", value=" + value +
+                    ", lastAccessTime=" + lastAccessTime +
+                    '}';
+        }
+    }
+
+    /**
+     * Background thread, periodically checking if the data is out of date
+     */
+    public class ExpireThread implements Runnable {
+        private long timeToLiveMillis;
+        private long expirationIntervalMillis;
+        private volatile boolean running = false;
+        private final Thread expirerThread;
+
+        @Override
+        public String toString() {
+            return "ExpireThread{" +
+                    ", timeToLiveMillis=" + timeToLiveMillis +
+                    ", expirationIntervalMillis=" + expirationIntervalMillis +
+                    ", running=" + running +
+                    ", expirerThread=" + expirerThread +
+                    '}';
+        }
+
+        public ExpireThread() {
+            expirerThread = new Thread(this, "ExpiryMapExpire-" + expireCount++);
+            expirerThread.setDaemon(true);
+        }
+
+        @Override
+        public void run() {
+            while (running) {
+                processExpires();
+                try {
+                    Thread.sleep(expirationIntervalMillis);
+                } catch (InterruptedException e) {
+                    running = false;
+                }
+            }
+        }
+
+        private void processExpires() {
+            long timeNow = System.currentTimeMillis();
+            for (ExpiryObject o : delegateMap.values()) {
+                if (timeToLiveMillis <= 0) {
+                    continue;
+                }
+                long timeIdle = timeNow - o.getLastAccessTime();
+                if (timeIdle >= timeToLiveMillis) {
+                    delegateMap.remove(o.getKey());
+                }
+            }
+        }
+
+        /**
+         * start expiring Thread
+         */
+        public void startExpiring() {
+            if (!running) {
+                running = true;
+                expirerThread.start();
+            }
+        }
+
+        /**
+         * start thread
+         */
+        public void startExpiryIfNotStarted() {
+            if (running) {
+                return;
+            }
+            startExpiring();
+        }
+
+        /**
+         * stop thread
+         */
+        public void stopExpiring() {
+            if (running) {
+                running = false;
+                expirerThread.interrupt();
+            }
+        }
+
+        /**
+         * get thread state
+         *
+         * @return thread state
+         */
+        public boolean isRunning() {
+            return running;
+        }
+
+        /**
+         * get time to live
+         *
+         * @return time to live
+         */
+        public int getTimeToLive() {
+            return (int) timeToLiveMillis / 1000;
+        }
+
+        /**
+         * update time to live
+         *
+         * @param timeToLive time to live
+         */
+        public void setTimeToLive(long timeToLive) {
+            this.timeToLiveMillis = timeToLive * 1000;
+        }
+
+        /**
+         * get expiration interval
+         *
+         * @return expiration interval (second)
+         */
+        public int getExpirationInterval() {
+            return (int) expirationIntervalMillis / 1000;
+        }
+
+        /**
+         * set expiration interval
+         *
+         * @param expirationInterval expiration interval (second)
+         */
+        public void setExpirationInterval(long expirationInterval) {
+            this.expirationIntervalMillis = expirationInterval * 1000;
+        }
+    }
+}
+
+
+
diff --git a/dubbo-filter/dubbo-filter-cache/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.cache.CacheFactory b/dubbo-filter/dubbo-filter-cache/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.cache.CacheFactory
index 6e3ddd0..65185d8 100644
--- a/dubbo-filter/dubbo-filter-cache/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.cache.CacheFactory
+++ b/dubbo-filter/dubbo-filter-cache/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.cache.CacheFactory
@@ -1,3 +1,4 @@
 threadlocal=com.alibaba.dubbo.cache.support.threadlocal.ThreadLocalCacheFactory
 lru=com.alibaba.dubbo.cache.support.lru.LruCacheFactory
-jcache=com.alibaba.dubbo.cache.support.jcache.JCacheFactory
\ No newline at end of file
+jcache=com.alibaba.dubbo.cache.support.jcache.JCacheFactory
+expiring=com.alibaba.dubbo.cache.support.expiring.ExpiringCacheFactory
\ No newline at end of file
diff --git a/dubbo-filter/dubbo-filter-cache/src/test/java/com/alibaba/dubbo/cache/filter/CacheFilterTest.java b/dubbo-filter/dubbo-filter-cache/src/test/java/com/alibaba/dubbo/cache/filter/CacheFilterTest.java
index 760605c..582f022 100644
--- a/dubbo-filter/dubbo-filter-cache/src/test/java/com/alibaba/dubbo/cache/filter/CacheFilterTest.java
+++ b/dubbo-filter/dubbo-filter-cache/src/test/java/com/alibaba/dubbo/cache/filter/CacheFilterTest.java
@@ -17,6 +17,7 @@
 package com.alibaba.dubbo.cache.filter;
 
 import com.alibaba.dubbo.cache.CacheFactory;
+import com.alibaba.dubbo.cache.support.expiring.ExpiringCacheFactory;
 import com.alibaba.dubbo.cache.support.jcache.JCacheFactory;
 import com.alibaba.dubbo.cache.support.lru.LruCacheFactory;
 import com.alibaba.dubbo.cache.support.threadlocal.ThreadLocalCacheFactory;
@@ -33,7 +34,7 @@ import org.junit.runners.Parameterized;
 import java.util.Arrays;
 import java.util.List;
 
-import static org.junit.runners.Parameterized.*;
+import static org.junit.runners.Parameterized.Parameters;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.Mockito.mock;
 
@@ -59,7 +60,8 @@ public class CacheFilterTest {
         return Arrays.asList(new Object[][]{
                 {"lru", new LruCacheFactory()},
                 {"jcache", new JCacheFactory()},
-                {"threadlocal", new ThreadLocalCacheFactory()}
+                {"threadlocal", new ThreadLocalCacheFactory()},
+                {"expiring", new ExpiringCacheFactory()}
         });
     }
 
diff --git a/dubbo-filter/dubbo-filter-cache/src/test/java/com/alibaba/dubbo/cache/support/expiring/ExpiringCacheFactoryTest.java b/dubbo-filter/dubbo-filter-cache/src/test/java/com/alibaba/dubbo/cache/support/expiring/ExpiringCacheFactoryTest.java
new file mode 100644
index 0000000..cbb7bc5
--- /dev/null
+++ b/dubbo-filter/dubbo-filter-cache/src/test/java/com/alibaba/dubbo/cache/support/expiring/ExpiringCacheFactoryTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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 com.alibaba.dubbo.cache.support.expiring;
+
+import com.alibaba.dubbo.cache.Cache;
+import com.alibaba.dubbo.cache.support.AbstractCacheFactory;
+import com.alibaba.dubbo.cache.support.AbstractCacheFactoryTest;
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public class ExpiringCacheFactoryTest extends AbstractCacheFactoryTest {
+    @Test
+    public void testLruCacheFactory() throws Exception {
+        Cache cache = super.constructCache();
+        assertThat(cache instanceof ExpiringCache, is(true));
+    }
+
+    @Override
+    protected AbstractCacheFactory getCacheFactory() {
+        return new ExpiringCacheFactory();
+    }
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
iluo@apache.org.