This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-cache-ehcache.git
commit 465cc6ac909cd187f6fc16403d88686f6aa3aa9b Author: Ian Boston <[email protected]> AuthorDate: Fri Nov 9 05:22:15 2012 +0000 SLING-2555 moving into the right place this time, sorry. git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1407364 13f79535-47bb-0310-9956-ffa450edef68 --- README.txt | 9 + pom.xml | 177 +++++++++++++++++++ .../sling/commons/cache/ehcache/CacheImpl.java | 194 +++++++++++++++++++++ .../cache/ehcache/CacheManagerServiceImpl.java | 179 +++++++++++++++++++ .../sling/commons/cache/ehcache/ehcacheConfig.xml | 31 ++++ .../commons/cache/ehcache/CacheConfigTest.java | 127 ++++++++++++++ .../sling/commons/cache/ehcache/CacheIT.java | 172 ++++++++++++++++++ .../sling/commons/cache/ehcache/MapBuilder.java | 40 +++++ .../sling/commons/cache/ehcache/TestCache.java | 117 +++++++++++++ .../resources/testconfig/cluster-ehcacheConfig.xml | 165 ++++++++++++++++++ .../resources/testconfig/simple-ehcacheConfig.xml | 31 ++++ 11 files changed, 1242 insertions(+) diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..eb188cd --- /dev/null +++ b/README.txt @@ -0,0 +1,9 @@ +EhCache Implementation of the CacheManagerService. + +This bundle contains an implementation of the CacheManagerService based on EhCache. + +Acknowledgments + +This code was based on a module from Sparse Content Map 29 September 2012 (git:sha1:a222df1937434ad3f07bf6c4f60b19524a158bcb), which itself was based on a snapshot of a +module in Sakai Nakamura 27 Jan 2011 (git:sha1:b9d8e65b733ec7c35a3d194c9a5dc12acf13cb34). All know contributors to code in this module have been +contacted for permission to grant license to the Apache Foundation. \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b3e1055 --- /dev/null +++ b/pom.xml @@ -0,0 +1,177 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>13</version> + <relativePath>../../../parent/pom.xml</relativePath> + </parent> + + <artifactId>org.apache.sling.commons.cache.ehcache</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>bundle</packaging> + + <name>Apache Sling Cache API EhCache implementation</name> + <description> + This bundle provides an implementation of the Cache API using ehcache. + </description> + + <scm> + <connection>scm:svn:http://svn.apache.org/repos/asf/sling/whiteboard/ieb/cache/ehcache</connection> + <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/whiteboard/ieb/cache/ehcache</developerConnection> + <url>http://svn.apache.org/viewvc/sling/whiteboard/ieb/cache/ehcache</url> + </scm> + + <properties> + <bundle.build.name> + ${basedir}/target + </bundle.build.name> + <bundle.file.name> + ${bundle.build.name}/${project.build.finalName}.jar + </bundle.file.name> + </properties> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-scr-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <version>2.3.6</version> + <extensions>true</extensions> + <configuration> + <instructions> + <Import-Package> + !sun.misc, + !org.jgroups.*, + !org.hibernate.cache, + * + </Import-Package> + <Private-Package> + org.apache.sling.commons.cache.ehcache.*, + org.apache.sling.commons.cache.impl.* + </Private-Package> + <DynamicImport-Package>sun.misc.*</DynamicImport-Package> + <Embed-Transitive>true</Embed-Transitive> + <Embed-Dependency>ehcache,jsr107cache,backport-util-concurrent</Embed-Dependency> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + <profiles> + <profile> + <id>java6</id> + <activation> + <jdk>1.6</jdk> + </activation> + <build> + <plugins> + <!-- integration tests run with pax-exam --> + <plugin> + <artifactId>maven-failsafe-plugin</artifactId> + <version>2.12</version> + <executions> + <execution> + <goals> + <goal>integration-test</goal> + <goal>verify</goal> + </goals> + </execution> + </executions> + <configuration> + <systemProperties> + <property> + <name>project.bundle.file</name> + <value>${bundle.file.name}</value> + </property> + </systemProperties> + <includes> + <include>**/*IT.java</include> + </includes> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + + + <dependencies> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.commons.cache.api</artifactId> + <version>0.1-SNAPSHOT</version> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.commons.cache.impl</artifactId> + <version>0.1-SNAPSHOT</version> + </dependency> + <dependency> + <groupId>net.sf.ehcache</groupId> + <artifactId>ehcache</artifactId> + <version>1.5.0</version> + </dependency> + <dependency> + <groupId>net.sf.jsr107cache</groupId> + <artifactId>jsr107cache</artifactId> + <version>1.0</version> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.scr.annotations</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-all</artifactId> + <version>1.8.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.commons.cache.container-test</artifactId> + <version>0.1-SNAPSHOT</version> + <scope>test</scope> + </dependency> + + </dependencies> +</project> diff --git a/src/main/java/org/apache/sling/commons/cache/ehcache/CacheImpl.java b/src/main/java/org/apache/sling/commons/cache/ehcache/CacheImpl.java new file mode 100644 index 0000000..9b36a78 --- /dev/null +++ b/src/main/java/org/apache/sling/commons/cache/ehcache/CacheImpl.java @@ -0,0 +1,194 @@ +/* + * 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.sling.commons.cache.ehcache; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import net.sf.ehcache.CacheManager; +import net.sf.ehcache.Element; + +import org.apache.sling.commons.cache.api.Cache; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + */ +public class CacheImpl<V> implements Cache<V> { + + private static final Logger LOGGER = LoggerFactory + .getLogger(CacheImpl.class); + private String cacheName; + private net.sf.ehcache.Cache cache; + private long miss; + private long hits; + private long gets; + + /** + * @param cacheManager + * @param name + */ + public CacheImpl(CacheManager cacheManager, String name) { + if (name == null) { + cacheName = "default"; + } else { + cacheName = name; + } + synchronized (cacheManager) { + cache = cacheManager.getCache(cacheName); + if (cache == null) { + cacheManager.addCache(cacheName); + cache = cacheManager.getCache(cacheName); + if (cache == null) { + throw new RuntimeException( + "Failed to create Cache with name " + cacheName); + } + } + } + } + + /** + * {@inherit-doc} + * + * @see org.apache.sling.commons.cache.api.Cache#clear() + */ + public void clear() { + cache.removeAll(); + } + + /** + * {@inherit-doc} + * + * @see org.apache.sling.commons.cache.api.Cache#containsKey(java.lang.String) + */ + public boolean containsKey(String key) { + return cache.isKeyInCache(key); + } + + /** + * {@inherit-doc} + * + * @see org.apache.sling.commons.cache.api.Cache#get(java.lang.String) + */ + public V get(String key) { + Element e = cache.get(key); + if (e == null) { + return stats(null); + } + return stats(e.getObjectValue()); + } + + /** + * Records stats + * + * @param objectValue + * @return the Object Value + */ + @SuppressWarnings("unchecked") + private V stats(Object objectValue) { + if (objectValue == null) { + miss++; + } else { + hits++; + } + gets++; + if (gets % 1000 == 0) { + long hp = (100 * hits) / gets; + long mp = (100 * miss) / gets; + LOGGER.info( + "{} Cache Stats hits {} ({}%), misses {} ({}%), calls {}", + new Object[] { cacheName, hits, hp, miss, mp, gets }); + } + return (V) objectValue; + } + + /** + * {@inherit-doc} + * + * @see org.apache.sling.commons.cache.api.Cache#put(java.lang.String, + * java.lang.Object) + */ + @SuppressWarnings("unchecked") + public V put(String key, V payload) { + V previous = null; + if (cache.isKeyInCache(key)) { + Element e = cache.get(key); + if (e != null) { + previous = (V) e.getObjectValue(); + } + } + cache.put(new Element(key, payload)); + return previous; + } + + /** + * {@inherit-doc} + * + * @see org.apache.sling.commons.cache.api.Cache#remove(java.lang.String) + */ + public boolean remove(String key) { + return cache.remove(key); + } + + /** + * {@inheritDoc} + * + * @see org.apache.sling.commons.cache.api.Cache#removeChildren(java.lang.String) + */ + public void removeChildren(String key) { + cache.remove(key); + if (!key.endsWith("/")) { + key = key + "/"; + } + List<?> keys = cache.getKeys(); + for (Object k : keys) { + if (((String) k).startsWith(key)) { + cache.remove(k); + } + } + } + + /** + * {@inheritDoc} + * + * @see org.apache.sling.commons.cache.api.Cache#list() + */ + @SuppressWarnings("unchecked") + public Collection<V> values() { + List<String> keys = cache.getKeys(); + List<V> values = new ArrayList<V>(); + for (String k : keys) { + Element e = cache.get(k); + if (e != null) { + values.add((V) e.getObjectValue()); + } + } + return values; + } + + + @SuppressWarnings("unchecked") + public Collection<String> keys() { + return cache.getKeys(); + } + +} diff --git a/src/main/java/org/apache/sling/commons/cache/ehcache/CacheManagerServiceImpl.java b/src/main/java/org/apache/sling/commons/cache/ehcache/CacheManagerServiceImpl.java new file mode 100644 index 0000000..b0b5db5 --- /dev/null +++ b/src/main/java/org/apache/sling/commons/cache/ehcache/CacheManagerServiceImpl.java @@ -0,0 +1,179 @@ +/* + * 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.sling.commons.cache.ehcache; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.lang.management.ManagementFactory; +import java.lang.ref.WeakReference; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.management.MBeanServer; + +import net.sf.ehcache.CacheManager; +import net.sf.ehcache.management.ManagementService; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.commons.cache.api.Cache; +import org.apache.sling.commons.cache.api.CacheManagerService; +import org.apache.sling.commons.cache.impl.AbstractCacheManagerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The <code>CacheManagerServiceImpl</code> + */ +@Component(immediate = true, metatype = true) +@Service(value = CacheManagerService.class) +public class CacheManagerServiceImpl extends AbstractCacheManagerService { + + public static final String DEFAULT_CACHE_CONFIG = "sling/ehcacheConfig.xml"; + + @Property(value = DEFAULT_CACHE_CONFIG) + public static final String CACHE_CONFIG = "cache-config"; + + @Property(value = "The Apache Software Foundation") + static final String SERVICE_VENDOR = "service.vendor"; + + @Property(value = "Cache Manager Service Implementation") + static final String SERVICE_DESCRIPTION = "service.description"; + + @Property() + public static final String BIND_ADDRESS = "bind-address"; + + @Property(value = "sling/ehcache/data") + public static final String CACHE_STORE = "cache-store"; + + private static final String CONFIG_PATH = "org/apache/sling/commons/cache/ehcache/ehcacheConfig.xml"; + + private static final Logger LOGGER = LoggerFactory + .getLogger(CacheManagerServiceImpl.class); + private CacheManager cacheManager; + private Map<String, Cache<?>> caches = new ConcurrentHashMap<String, Cache<?>>(); + + public CacheManagerServiceImpl() throws IOException { + } + + @Activate + public void activate(Map<String, Object> properties) + throws FileNotFoundException, IOException { + String config = toString(properties.get(CACHE_CONFIG), + DEFAULT_CACHE_CONFIG); + File configFile = new File(config); + if (configFile.exists()) { + LOGGER.info("Configuring Cache from {} ", + configFile.getAbsolutePath()); + InputStream in = null; + try { + in = processConfig(new FileInputStream(configFile), properties); + cacheManager = new CacheManager(in); + } finally { + if (in != null) { + in.close(); + } + } + } else { + LOGGER.info("Configuring Cache from Classpath Default {} ", + CONFIG_PATH); + InputStream in = processConfig(this.getClass().getClassLoader() + .getResourceAsStream(CONFIG_PATH), properties); + if (in == null) { + throw new IOException( + "Unable to open config at classpath location " + + CONFIG_PATH); + } + cacheManager = new CacheManager(in); + in.close(); + } + + final WeakReference<CacheManagerServiceImpl> ref = new WeakReference<CacheManagerServiceImpl>( + this); + /* + * Add in a shutdown hook, for safety + */ + Runtime.getRuntime().addShutdownHook(new Thread() { + /* + * (non-Javadoc) + * + * @see java.lang.Thread#run() + */ + @Override + public void run() { + try { + CacheManagerServiceImpl cm = ref.get(); + if (cm != null) { + cm.deactivate(); + } + } catch (Throwable t) { + LOGGER.debug(t.getMessage(), t); + } + } + }); + + // register the cache manager with JMX + MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); + ManagementService.registerMBeans(cacheManager, mBeanServer, true, true, + true, true); + + } + + + /** + * perform a shutdown + */ + @Deactivate + public void deactivate() { + if (cacheManager != null) { + cacheManager.shutdown(); + cacheManager = null; + } + } + + + /** + * @param name + * @return + */ + @Override + protected <V> Cache<V> getInstanceCache(String name) { + if (name == null) { + return new CacheImpl<V>(cacheManager, null); + } else { + @SuppressWarnings("unchecked") + Cache<V> c = (Cache<V>) caches.get(name); + if (c == null) { + c = new CacheImpl<V>(cacheManager, name); + caches.put(name, c); + } + return c; + } + } + + + +} diff --git a/src/main/resources/org/apache/sling/commons/cache/ehcache/ehcacheConfig.xml b/src/main/resources/org/apache/sling/commons/cache/ehcache/ehcacheConfig.xml new file mode 100644 index 0000000..8f1184e --- /dev/null +++ b/src/main/resources/org/apache/sling/commons/cache/ehcache/ehcacheConfig.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/* + * 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. + */ + --> +<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="ehcache.xsd"> + <!-- + Mandatory Default Cache configuration. These settings will be applied to caches created + programmtically using CacheManager.add(String cacheName). The defaultCache has an implicit name + "default" which is a reserved cache name. + --> + <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="600" + timeToLiveSeconds="600" overflowToDisk="false" diskPersistent="false" memoryStoreEvictionPolicy="LRU" /> +</ehcache> \ No newline at end of file diff --git a/src/test/java/org/apache/sling/commons/cache/ehcache/CacheConfigTest.java b/src/test/java/org/apache/sling/commons/cache/ehcache/CacheConfigTest.java new file mode 100644 index 0000000..245354a --- /dev/null +++ b/src/test/java/org/apache/sling/commons/cache/ehcache/CacheConfigTest.java @@ -0,0 +1,127 @@ +/* + * 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.sling.commons.cache.ehcache; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.Map; + +import org.apache.sling.commons.cache.api.Cache; +import org.apache.sling.commons.cache.api.CacheScope; +import org.apache.sling.commons.cache.api.ThreadBound; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class CacheConfigTest { + + private CacheManagerServiceImpl cacheManagerService; + + @Before + public void setUp() throws IOException, InstantiationException, + IllegalAccessException, ClassNotFoundException { + cacheManagerService = new CacheManagerServiceImpl(); + Map<String, Object> properties = MapBuilder.getMap(CacheManagerServiceImpl.CACHE_STORE, + (Object) "target/ehcache/store", + CacheManagerServiceImpl.BIND_ADDRESS, "127.0.0.1", + CacheManagerServiceImpl.CACHE_CONFIG, "src/test/resources/testconfig/simple-ehcacheConfig.xml" + ); + cacheManagerService.activate(properties); + } + + + @After + public void tearDown() { + cacheManagerService.deactivate(); + } + + private void exerciseCache(String cacheName, CacheScope scope) { + Cache<String> cache = cacheManagerService.getCache(cacheName, scope); + cache.put("fish", "cat"); + assertTrue("Expected element to be in cache", cache.containsKey("fish")); + Cache<String> sameCache = cacheManagerService + .getCache(cacheName, scope); + assertEquals("Expected cache to work", "cat", sameCache.get("fish")); + sameCache.put("fish", "differentcat"); + assertEquals("Expected cache value to propogate", "differentcat", + cache.get("fish")); + sameCache.remove("fish"); + sameCache.remove("another"); + assertNull("Expected item to be removed from cache", cache.get("fish")); + cache.put("foo", "bar"); + cache.clear(); + assertNull("Expected cache to be empty", cache.get("foo")); + cacheManagerService.unbind(scope); + } + + @Test + public void testCacheStorage() { + for (CacheScope scope : CacheScope.values()) { + exerciseCache("TestCache", scope); + } + } + + @Test + public void testNullCacheNames() { + for (CacheScope scope : CacheScope.values()) { + exerciseCache(null, scope); + } + } + + @Test + public void testCacheWithChildKeys() { + for (CacheScope scope : CacheScope.values()) { + String cacheName = "SomeTestCache"; + Cache<String> cache = cacheManagerService + .getCache(cacheName, scope); + cache.put("fish", "cat"); + assertTrue("Expected element to be in cache", + cache.containsKey("fish")); + cache.put("fish/child", "childcat"); + cache.put("fish/child/child", "childcatchild"); + Cache<String> sameCache = cacheManagerService.getCache(cacheName, + scope); + sameCache.removeChildren("fish/child/child"); + assertNull("Expected key to be removed", + cache.get("fish/child/child")); + sameCache.removeChildren("fish"); + assertNull("Expected key to be removed", cache.get("fish")); + assertNull("Expected key to be removed", cache.get("fish/child")); + } + } + + @Test + public void testThreadUnbinding() { + ThreadBound testItem = Mockito.mock(ThreadBound.class); + Cache<ThreadBound> threadBoundCache = cacheManagerService.getCache( + "testCache", CacheScope.THREAD); + threadBoundCache.put("testItem", testItem); + threadBoundCache.remove("testItem"); + threadBoundCache.put("testItem", testItem); + threadBoundCache.clear(); + + Mockito.verify(testItem, Mockito.times(2)).unbind(); + } + +} diff --git a/src/test/java/org/apache/sling/commons/cache/ehcache/CacheIT.java b/src/test/java/org/apache/sling/commons/cache/ehcache/CacheIT.java new file mode 100644 index 0000000..43a5c69 --- /dev/null +++ b/src/test/java/org/apache/sling/commons/cache/ehcache/CacheIT.java @@ -0,0 +1,172 @@ +/* + * 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.sling.commons.cache.ehcache; + +import static org.ops4j.pax.exam.CoreOptions.mavenBundle; +import static org.ops4j.pax.exam.CoreOptions.options; +import static org.ops4j.pax.exam.CoreOptions.provision; + +import javax.inject.Inject; + +import org.apache.sling.commons.cache.api.Cache; +import org.apache.sling.commons.cache.api.CacheManagerService; +import org.apache.sling.commons.cache.api.CacheScope; +import org.apache.sling.test.AbstractOSGiRunner; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.Option; +import org.ops4j.pax.exam.junit.ExamReactorStrategy; +import org.ops4j.pax.exam.junit.JUnit4TestRunner; +import org.ops4j.pax.exam.spi.reactors.EagerSingleStagedReactorFactory; + +/** + * Spin the cache up in a container to verify we have a working system. + */ +@RunWith(JUnit4TestRunner.class) +@ExamReactorStrategy(EagerSingleStagedReactorFactory.class) // we want to spin up the container once only. +public class CacheIT extends AbstractOSGiRunner { + + + @Inject + private CacheManagerService cacheManagerService; + + + @Override + protected String getImports() { + return "org.apache.sling.commons.cache.api"; + } + + public Option[] getOptions() { + return options( + provision( + mavenBundle("org.apache.sling", "org.apache.sling.commons.cache.api") + )); + } + + protected String getArtifactName() { + return "org.apache.sling.commons.cache.ehcache"; + } + + + + @Test + public void testCacheManagerReplicated() { + String cacheName = "string-cache-replicated"; + Assert.assertNotNull(cacheManagerService); + Cache<String> c = cacheManagerService.getCache(cacheName, + CacheScope.CLUSTERREPLICATED); + Assert.assertNotNull(c); + String v = cacheName + String.valueOf(System.currentTimeMillis()); + c.put("key", v); + Assert.assertEquals(v, c.get("key")); + cacheManagerService.unbind(CacheScope.CLUSTERREPLICATED); + cacheManagerService.unbind(CacheScope.CLUSTERINVALIDATED); + cacheManagerService.unbind(CacheScope.INSTANCE); + cacheManagerService.unbind(CacheScope.REQUEST); + cacheManagerService.unbind(CacheScope.THREAD); + c = cacheManagerService.getCache(cacheName, + CacheScope.CLUSTERREPLICATED); + Assert.assertNotNull(c); + Assert.assertEquals(v, c.get("key")); + + } + + @Test + public void testCacheManagerInvalidated() { + String cacheName = "string-cache-invalidated"; + Assert.assertNotNull(cacheManagerService); + Cache<String> c = cacheManagerService.getCache(cacheName, + CacheScope.CLUSTERINVALIDATED); + Assert.assertNotNull(c); + String v = cacheName + String.valueOf(System.currentTimeMillis()); + c.put("key", v); + Assert.assertEquals(v, c.get("key")); + cacheManagerService.unbind(CacheScope.CLUSTERREPLICATED); + cacheManagerService.unbind(CacheScope.CLUSTERINVALIDATED); + cacheManagerService.unbind(CacheScope.INSTANCE); + cacheManagerService.unbind(CacheScope.REQUEST); + cacheManagerService.unbind(CacheScope.THREAD); + c = cacheManagerService.getCache(cacheName, + CacheScope.CLUSTERINVALIDATED); + Assert.assertNotNull(c); + Assert.assertEquals(v, c.get("key")); + } + + //@Test + public void testCacheManagerInstance() { + String cacheName = "string-cache-instance"; + Assert.assertNotNull(cacheManagerService); + Cache<String> c = cacheManagerService.getCache(cacheName, + CacheScope.INSTANCE); + Assert.assertNotNull(c); + String v = cacheName + String.valueOf(System.currentTimeMillis()); + c.put("key", v); + Assert.assertEquals(v, c.get("key")); + cacheManagerService.unbind(CacheScope.CLUSTERREPLICATED); + cacheManagerService.unbind(CacheScope.CLUSTERINVALIDATED); + cacheManagerService.unbind(CacheScope.INSTANCE); + cacheManagerService.unbind(CacheScope.REQUEST); + cacheManagerService.unbind(CacheScope.THREAD); + c = cacheManagerService.getCache(cacheName, CacheScope.INSTANCE); + Assert.assertNotNull(c); + Assert.assertEquals(v, c.get("key")); + } + + @Test + public void testCacheManagerRequest() { + String cacheName = "string-cache-request"; + Assert.assertNotNull(cacheManagerService); + Cache<String> c = cacheManagerService.getCache(cacheName, + CacheScope.REQUEST); + Assert.assertNotNull(c); + String v = cacheName + String.valueOf(System.currentTimeMillis()); + c.put("key", v); + Assert.assertEquals(v, c.get("key")); + cacheManagerService.unbind(CacheScope.CLUSTERREPLICATED); + cacheManagerService.unbind(CacheScope.CLUSTERINVALIDATED); + cacheManagerService.unbind(CacheScope.INSTANCE); + cacheManagerService.unbind(CacheScope.REQUEST); + cacheManagerService.unbind(CacheScope.THREAD); + c = cacheManagerService.getCache(cacheName, CacheScope.REQUEST); + Assert.assertNotNull(c); + Assert.assertNull(c.get("key")); + } + + @Test + public void testCacheManagerThread() { + String cacheName = "string-cache-thread"; + Assert.assertNotNull(cacheManagerService); + Cache<String> c = cacheManagerService.getCache(cacheName, + CacheScope.THREAD); + Assert.assertNotNull(c); + String v = cacheName + String.valueOf(System.currentTimeMillis()); + c.put("key", v); + Assert.assertEquals(v, c.get("key")); + cacheManagerService.unbind(CacheScope.CLUSTERREPLICATED); + cacheManagerService.unbind(CacheScope.CLUSTERINVALIDATED); + cacheManagerService.unbind(CacheScope.INSTANCE); + cacheManagerService.unbind(CacheScope.REQUEST); + cacheManagerService.unbind(CacheScope.THREAD); + c = cacheManagerService.getCache(cacheName, CacheScope.THREAD); + Assert.assertNotNull(c); + Assert.assertNull(c.get("key")); + } + +} diff --git a/src/test/java/org/apache/sling/commons/cache/ehcache/MapBuilder.java b/src/test/java/org/apache/sling/commons/cache/ehcache/MapBuilder.java new file mode 100644 index 0000000..52b9e4d --- /dev/null +++ b/src/test/java/org/apache/sling/commons/cache/ehcache/MapBuilder.java @@ -0,0 +1,40 @@ +/* + * 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.sling.commons.cache.ehcache; + +import java.util.HashMap; +import java.util.Map; + +/** + * A very simple class that avoids needing Guava as a dependency (aka Google Collections) + * + */ +public class MapBuilder { + + @SuppressWarnings("unchecked") + public static <K, V> Map<K, V> getMap(Object ... kv) { + Map<K,V> m = new HashMap<K, V>(); + for ( int i = 0; i < kv.length; i+=2 ) { + m.put((K)kv[i], (V)kv[i+1]); + } + return m; + } + +} diff --git a/src/test/java/org/apache/sling/commons/cache/ehcache/TestCache.java b/src/test/java/org/apache/sling/commons/cache/ehcache/TestCache.java new file mode 100644 index 0000000..601cbb3 --- /dev/null +++ b/src/test/java/org/apache/sling/commons/cache/ehcache/TestCache.java @@ -0,0 +1,117 @@ +/* + * 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.sling.commons.cache.ehcache; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.Map; + +import org.apache.sling.commons.cache.api.Cache; +import org.apache.sling.commons.cache.api.CacheScope; +import org.apache.sling.commons.cache.api.ThreadBound; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class TestCache { + + private CacheManagerServiceImpl cacheManagerService; + + @Before + public void setUp() throws IOException, InstantiationException, IllegalAccessException, + ClassNotFoundException { + cacheManagerService = new CacheManagerServiceImpl(); + Map<String, Object> properties = MapBuilder.getMap("cache-store",(Object)"target/ehcache/store", "bind-address", "127.0.0.1"); + cacheManagerService.activate(properties); + } + + @After + public void tearDown() { + cacheManagerService.deactivate(); + } + + private void exerciseCache(String cacheName, CacheScope scope) { + Cache<String> cache = cacheManagerService.getCache(cacheName, scope); + cache.put("fish", "cat"); + assertTrue("Expected element to be in cache", cache.containsKey("fish")); + Cache<String> sameCache = cacheManagerService.getCache(cacheName, scope); + assertEquals("Expected cache to work", "cat", sameCache.get("fish")); + sameCache.put("fish", "differentcat"); + assertEquals("Expected cache value to propogate", "differentcat", cache.get("fish")); + sameCache.remove("fish"); + sameCache.remove("another"); + assertNull("Expected item to be removed from cache", cache.get("fish")); + cache.put("foo", "bar"); + cache.clear(); + assertNull("Expected cache to be empty", cache.get("foo")); + cacheManagerService.unbind(scope); + } + + @Test + public void testCacheStorage() { + for (CacheScope scope : CacheScope.values()) { + exerciseCache("TestCache", scope); + } + } + + @Test + public void testNullCacheNames() { + for (CacheScope scope : CacheScope.values()) { + exerciseCache(null, scope); + } + } + + @Test + public void testCacheWithChildKeys() { + for (CacheScope scope : CacheScope.values()) { + String cacheName = "SomeTestCache"; + Cache<String> cache = cacheManagerService.getCache(cacheName, scope); + cache.put("fish", "cat"); + assertTrue("Expected element to be in cache", cache.containsKey("fish")); + cache.put("fish/child", "childcat"); + cache.put("fish/child/child", "childcatchild"); + Cache<String> sameCache = cacheManagerService.getCache(cacheName, scope); + sameCache.removeChildren("fish/child/child"); + assertNull("Expected key to be removed", cache.get("fish/child/child")); + sameCache.removeChildren("fish"); + assertNull("Expected key to be removed", cache.get("fish")); + assertNull("Expected key to be removed", cache.get("fish/child")); + } + } + + @Test + public void testThreadUnbinding() { + ThreadBound testItem = Mockito.mock(ThreadBound.class); + Cache<ThreadBound> threadBoundCache = cacheManagerService.getCache("testCache", + CacheScope.THREAD); + threadBoundCache.put("testItem", testItem); + threadBoundCache.remove("testItem"); + threadBoundCache.put("testItem", testItem); + threadBoundCache.clear(); + + Mockito.verify(testItem, Mockito.times(2)).unbind(); + } + +} diff --git a/src/test/resources/testconfig/cluster-ehcacheConfig.xml b/src/test/resources/testconfig/cluster-ehcacheConfig.xml new file mode 100644 index 0000000..dfc8a49 --- /dev/null +++ b/src/test/resources/testconfig/cluster-ehcacheConfig.xml @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/* + * 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. + */ + --> +<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="ehcache.xsd"> + + + <diskStore path="${cache-store}"/> + + <!-- the group address and muticast port may need changing, I am assuming + all the cluster hosts are on the same subnet and no forwarding is required. + The group address and port have been changed from the default just incase + there are any other ehcache instances on the same subnet. The hostName must + be customsed --> + <cacheManagerPeerProviderFactory + class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" + properties="hostName=${bind-address}, + peerDiscovery=automatic, + multicastGroupAddress=230.0.0.2, + multicastGroupPort=4450, timeToLive=1" + propertySeparator="," /> + + <cacheManagerPeerListenerFactory + class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory" + properties="hostName=${bind-address}, + port=40001, + remoteObjectPort=40002, + socketTimeoutMillis=120000" + propertySeparator="," /> + <!-- Mandatory Default Cache configuration. These settings will be applied + to caches created programmtically using CacheManager.add(String cacheName). + The defaultCache has an implicit name "default" which is a reserved cache + name. --> + <defaultCache maxElementsInMemory="10000" eternal="false" + timeToIdleSeconds="600" timeToLiveSeconds="600" overflowToDisk="false" + diskPersistent="false" memoryStoreEvictionPolicy="LRU" /> + + <!-- cluster invalidated --> + <cache name="cacheA" maxElementsInMemory="100000" + eternal="false" overflowToDisk="false" + timeToIdleSeconds="300" timeToLiveSeconds="600" + memoryStoreEvictionPolicy="LFU" > + <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" + properties="replicateAsynchronously=true, + replicatePuts=false, + replicatePutsViaCopy=false, + replicateUpdates=false, + replicateUpdatesViaCopy=true, + replicateRemovals=true, + asynchronousReplicationIntervalMillis=500" + propertySeparator="," /> + </cache> + + <!-- small objects, cluster invalidated --> + <cache name="cacheB" maxElementsInMemory="100000" + eternal="false" overflowToDisk="false" + timeToIdleSeconds="300" timeToLiveSeconds="600" + memoryStoreEvictionPolicy="LFU" > + <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" + properties="replicateAsynchronously=true, + replicatePuts=false, + replicatePutsViaCopy=false, + replicateUpdates=false, + replicateUpdatesViaCopy=true, + replicateRemovals=true, + asynchronousReplicationIntervalMillis=500" + propertySeparator="," /> + </cache> + + <!-- larger objects, cluster invalidated --> + <cache name="cacheC" maxElementsInMemory="50000" + eternal="false" overflowToDisk="false" + timeToIdleSeconds="300" timeToLiveSeconds="600" + memoryStoreEvictionPolicy="LFU" > + <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" + properties="replicateAsynchronously=true, + replicatePuts=false, + replicatePutsViaCopy=false, + replicateUpdates=false, + replicateUpdatesViaCopy=false, + replicateRemovals=true, + asynchronousReplicationIntervalMillis=500" + propertySeparator="," /> + </cache> + + <!-- replicated. --> + <cache name="cacheD" maxElementsInMemory="10000" + eternal="false" overflowToDisk="false" + timeToIdleSeconds="300" timeToLiveSeconds="600" + memoryStoreEvictionPolicy="LFU" > + <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" + properties="replicateAsynchronously=true, + replicatePuts=true, + replicatePutsViaCopy=true, + replicateUpdates=true, + replicateUpdatesViaCopy=true, + replicateRemovals=true, + asynchronousReplicationIntervalMillis=500" + propertySeparator="," /> + <bootstrapCacheLoaderFactory + class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" + properties="bootstrapAsynchronously=true, maximumChunkSizeBytes=5000000" + propertySeparator="," /> + </cache> + + <cache name="cacheE" maxElementsInMemory="10000" + eternal="false" overflowToDisk="false" + timeToIdleSeconds="300" timeToLiveSeconds="600" + memoryStoreEvictionPolicy="LFU" > + <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" + properties="replicateAsynchronously=true, + replicatePuts=true, + replicatePutsViaCopy=true, + replicateUpdates=true, + replicateUpdatesViaCopy=true, + replicateRemovals=true, + asynchronousReplicationIntervalMillis=500" + propertySeparator="," /> + <bootstrapCacheLoaderFactory + class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" + properties="bootstrapAsynchronously=true, maximumChunkSizeBytes=5000000" + propertySeparator="," /> + </cache> + + <!-- the Token store must be saved to disk localy, normally will only need 5x number of servers. + Fully replicated, synchronously --> + <cache name="TokenStore" + maxElementsInMemory="10000" maxElementsOnDisk="100000" eternal="true" + overflowToDisk="true" diskSpoolBufferSizeMB="20" memoryStoreEvictionPolicy="LFU" + diskPersistent="true" > + <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" + properties="replicateAsynchronously=false, + replicatePuts=true, + replicatePutsViaCopy=true, + replicateUpdates=true, + replicateUpdatesViaCopy=true, + replicateRemovals=true" + propertySeparator="," /> + <bootstrapCacheLoaderFactory + class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" + properties="bootstrapAsynchronously=false, maximumChunkSizeBytes=5000000" + propertySeparator="," /> + + </cache> + + +</ehcache> \ No newline at end of file diff --git a/src/test/resources/testconfig/simple-ehcacheConfig.xml b/src/test/resources/testconfig/simple-ehcacheConfig.xml new file mode 100644 index 0000000..8f1184e --- /dev/null +++ b/src/test/resources/testconfig/simple-ehcacheConfig.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/* + * 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. + */ + --> +<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="ehcache.xsd"> + <!-- + Mandatory Default Cache configuration. These settings will be applied to caches created + programmtically using CacheManager.add(String cacheName). The defaultCache has an implicit name + "default" which is a reserved cache name. + --> + <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="600" + timeToLiveSeconds="600" overflowToDisk="false" diskPersistent="false" memoryStoreEvictionPolicy="LRU" /> +</ehcache> \ No newline at end of file -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
