You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by fp...@apache.org on 2023/01/29 18:21:29 UTC
[shiro] 01/03: Adds cache module for JCache
This is an automated email from the ASF dual-hosted git repository.
fpapon pushed a commit to branch jcache-1.11
in repository https://gitbox.apache.org/repos/asf/shiro.git
commit 46dfc427859da3d8a805143f5dd33cc9810de009
Author: Brian Demers <bd...@apache.org>
AuthorDate: Fri Jan 14 18:25:44 2022 -0500
Adds cache module for JCache
This will allow for any jcache implementation to work with Shiro, as well as new erversions of EhCache & Hazelcast.
Fixes: SHIRO-816
Fixes: SHIRO-813
---
support/jcache/pom.xml | 88 +++++++
.../apache/shiro/cache/jcache/JCacheManager.java | 255 +++++++++++++++++++++
support/jcache/src/main/resources/META-INF/NOTICE | 15 ++
.../shiro/cache/jcache/JCacheManagerTest.groovy | 146 ++++++++++++
support/pom.xml | 1 +
5 files changed, 505 insertions(+)
diff --git a/support/jcache/pom.xml b/support/jcache/pom.xml
new file mode 100644
index 00000000..6e45484c
--- /dev/null
+++ b/support/jcache/pom.xml
@@ -0,0 +1,88 @@
+<?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.
+ -->
+<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 https://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.apache.shiro</groupId>
+ <artifactId>shiro-support</artifactId>
+ <version>1.8.1-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>shiro-jcache</artifactId>
+ <name>Apache Shiro :: Support :: JCache</name>
+ <packaging>bundle</packaging>
+
+ <properties>
+ <jcache.osgi.importRange>[1.1,2)</jcache.osgi.importRange>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.shiro</groupId>
+ <artifactId>shiro-cache</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.cache</groupId>
+ <artifactId>cache-api</artifactId>
+ <version>1.1.1</version>
+ </dependency>
+ <!-- Test dependencies -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.cache2k</groupId>
+ <artifactId>cache2k-jcache</artifactId>
+ <version>2.4.1.Final</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-SymbolicName>org.apache.shiro.jcache</Bundle-SymbolicName>
+ <Export-Package>org.apache.shiro.jcache*;version=${project.version}</Export-Package>
+ <Import-Package>
+ org.apache.shiro*;version="${shiro.osgi.importRange}",
+ com.hazelcast*;version="${jcache.osgi.importRange}",
+ *
+ </Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/support/jcache/src/main/java/org/apache/shiro/cache/jcache/JCacheManager.java b/support/jcache/src/main/java/org/apache/shiro/cache/jcache/JCacheManager.java
new file mode 100644
index 00000000..8760652a
--- /dev/null
+++ b/support/jcache/src/main/java/org/apache/shiro/cache/jcache/JCacheManager.java
@@ -0,0 +1,255 @@
+package org.apache.shiro.cache.jcache;
+
+import org.apache.shiro.cache.Cache;
+import org.apache.shiro.cache.CacheException;
+import org.apache.shiro.cache.CacheManager;
+import org.apache.shiro.util.Destroyable;
+import org.apache.shiro.util.Initializable;
+import org.apache.shiro.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.cache.Caching;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.spi.CachingProvider;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+/**
+ * Shiro {@code CacheManager} implementation utilizing JCache for all cache functionality.
+ * <p/>
+ * This class can {@link #setCacheManager(javax.cache.CacheManager) accept} a manually configured
+ * {@link javax.cache.CacheManager javax.cache.CacheManager} instance,
+ * a {@code cacheConfig} URI can be specified, or a call to {@link CachingProvider#getCacheManager()} will be used.
+ * <p/>
+ * This implementation requires a JCache implementation available on the classpath.
+ * <p/>
+ * @since 1.9
+ */
+public class JCacheManager implements CacheManager, Initializable, Destroyable {
+
+ /**
+ * This class's private log instance.
+ */
+ private static final Logger log = LoggerFactory.getLogger(JCacheManager.class);
+
+ private javax.cache.CacheManager jCacheManager;
+
+ private String cacheConfig;
+
+ /**
+ * Indicates if the CacheManager instance was implicitly/automatically created by this instance, indicating that
+ * it should be automatically cleaned up as well on shutdown.
+ */
+ private boolean cacheManagerImplicitlyCreated = false;
+
+ @Override
+ public <K, V> Cache<K, V> getCache(String name) throws CacheException {
+
+ javax.cache.Cache<K, V> cache = ensureCacheManager().getCache(name);
+
+ if (cache == null) {
+ synchronized (this) {
+ cache = ensureCacheManager().getCache(name);
+ if (cache == null) {
+ log.debug("Cache with name '{}' does not yet exist. Creating now.", name);
+ cache = ensureCacheManager().createCache(name, new MutableConfiguration<>());
+ log.debug("Added JCache named [{}]", name);
+ } else {
+ log.debug("Using existing JCache named [{}]", cache.getName());
+ }
+ }
+ }
+
+ return new JCache<>(cache);
+ }
+
+ /**
+ * Initializes this instance.
+ * <p/>
+ * If a CacheManager has been
+ * explicitly set (e.g. via Dependency Injection or programmatically) prior to calling this
+ * method, this method does nothing.
+ * <p/>
+ * Because Shiro cannot use the failsafe defaults (fail-safe expunges cached objects after 2 minutes,
+ * something not desirable for Shiro sessions), this class manages an internal default configuration for
+ * this case.
+ *
+ * @throws org.apache.shiro.cache.CacheException
+ * if there are any CacheExceptions thrown by JCache.
+ */
+ public final void init() throws CacheException {
+ ensureCacheManager();
+ }
+
+ private javax.cache.CacheManager ensureCacheManager() {
+ try {
+ if (this.jCacheManager == null) {
+ log.debug("cacheManager property not set. Constructing CacheManager instance... ");
+ CachingProvider cachingProvider = Caching.getCachingProvider();
+
+ if (StringUtils.hasText(cacheConfig)) {
+
+ URL config = getClass().getResource(cacheConfig);
+ if (config == null) {
+ throw new IllegalArgumentException("Could not load JCache configuration resource: " + cacheConfig);
+ }
+
+ this.jCacheManager = cachingProvider.getCacheManager(config.toURI(), getClass().getClassLoader());
+ } else {
+ this.jCacheManager = cachingProvider.getCacheManager();
+ }
+
+ cacheManagerImplicitlyCreated = true;
+ log.debug("implicit cacheManager created successfully.");
+ }
+ return this.jCacheManager;
+ } catch (Exception e) {
+ throw new CacheException(e);
+ }
+ }
+
+ /**
+ * Shuts-down the wrapped JCache CacheManager <b>only if implicitly created</b>.
+ * <p/>
+ * If another component injected
+ * a non-null CacheManager into this instance before calling {@link #init() init}, this instance expects that same
+ * component to also destroy the CacheManager instance, and it will not attempt to do so.
+ */
+ public void destroy() {
+ if (cacheManagerImplicitlyCreated) {
+ try {
+ jCacheManager.close();
+ } catch (Throwable t) {
+ log.warn("Unable to cleanly shutdown implicitly created CacheManager instance. Ignoring (shutting down)...", t);
+ } finally {
+ this.jCacheManager = null;
+ this.cacheManagerImplicitlyCreated = false;
+ }
+ }
+ }
+
+ public String getCacheConfig() {
+ return cacheConfig;
+ }
+
+ public void setCacheConfig(String jCacheConfig) {
+ this.cacheConfig = jCacheConfig;
+ }
+
+ public javax.cache.CacheManager getCacheManager() {
+ return jCacheManager;
+ }
+
+ public void setCacheManager(javax.cache.CacheManager jCacheManager) {
+ this.jCacheManager = jCacheManager;
+ }
+
+ static class JCache<K,V> implements Cache<K,V> {
+
+ private final javax.cache.Cache<K,V> cache;
+
+ JCache(javax.cache.Cache<K,V> cache) {
+ this.cache = cache;
+ }
+ /**
+ * Gets a value of an element which matches the given key.
+ *
+ * @param key the key of the element to return.
+ * @return The value placed into the cache with an earlier put, or null if not found or expired
+ */
+ @Override
+ public V get(K key) throws CacheException {
+ try {
+ log.trace("Getting object from cache [{}] for key [{}]", cache.getName(), key);
+ if (key == null) {
+ return null;
+ } else {
+ V element = cache.get(key);
+ if (element == null) {
+ log.trace("Element for [{}] is null.", key);
+ return null;
+ } else {
+ return element;
+ }
+ }
+ } catch (Throwable t) {
+ throw new CacheException(t);
+ }
+ }
+
+ /**
+ * Puts an object into the cache.
+ *
+ * @param key the key.
+ * @param value the value.
+ */
+ public V put(K key, V value) throws CacheException {
+ log.trace("Putting object in cache [{}] for key [{}]", cache.getName(), key);
+ try {
+ V previous = get(key);
+ cache.put(key, value);
+ return previous;
+ } catch (Throwable t) {
+ throw new CacheException(t);
+ }
+ }
+
+ /**
+ * Removes the element which matches the key.
+ *
+ * <p>If no element matches, nothing is removed and no Exception is thrown.</p>
+ *
+ * @param key the key of the element to remove
+ */
+ public V remove(K key) throws CacheException {
+ log.trace("Removing object from cache [{}] for key [{}]", cache.getName(), key);
+ try {
+ return cache.getAndRemove(key);
+ } catch (Throwable t) {
+ throw new CacheException(t);
+ }
+ }
+
+ /**
+ * Removes all elements in the cache, but leaves the cache in a useable state.
+ */
+ public void clear() throws CacheException {
+ log.trace("Clearing all objects from cache [{}]", cache.getName());
+ try {
+ cache.removeAll();
+ } catch (Throwable t) {
+ throw new CacheException(t);
+ }
+ }
+
+ public int size() {
+ return (int) toStream(cache.iterator()).count();
+ }
+
+ @Override
+ public Set<K> keys() {
+ return toStream(cache.iterator())
+ .map(javax.cache.Cache.Entry::getKey)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Collection<V> values() {
+ return toStream(cache.iterator())
+ .map(javax.cache.Cache.Entry::getValue)
+ .collect(Collectors.toSet());
+ }
+
+ private Stream<javax.cache.Cache.Entry<K, V>> toStream(Iterator<javax.cache.Cache.Entry<K, V>> iterator) {
+ return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false);
+ }
+ }
+}
diff --git a/support/jcache/src/main/resources/META-INF/NOTICE b/support/jcache/src/main/resources/META-INF/NOTICE
new file mode 100644
index 00000000..9d26a95f
--- /dev/null
+++ b/support/jcache/src/main/resources/META-INF/NOTICE
@@ -0,0 +1,15 @@
+Apache Shiro
+Copyright 2008-2020 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+The implementation for org.apache.shiro.util.SoftHashMap is based
+on initial ideas from Dr. Heinz Kabutz's publicly posted version
+available at http://www.javaspecialists.eu/archive/Issue015.html,
+with continued modifications.
+
+Certain parts (StringUtils, IpAddressMatcher, etc.) of the source
+code for this product was copied for simplicity and to reduce
+dependencies from the source code developed by the Spring Framework
+Project (http://www.springframework.org).
diff --git a/support/jcache/src/test/groovy/org/apache/shiro/cache/jcache/JCacheManagerTest.groovy b/support/jcache/src/test/groovy/org/apache/shiro/cache/jcache/JCacheManagerTest.groovy
new file mode 100644
index 00000000..a4e44a3f
--- /dev/null
+++ b/support/jcache/src/test/groovy/org/apache/shiro/cache/jcache/JCacheManagerTest.groovy
@@ -0,0 +1,146 @@
+/*
+ * 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.shiro.cache.jcache
+
+import org.apache.shiro.cache.Cache
+import org.apache.shiro.cache.CacheException
+import org.junit.Assert
+import org.junit.Test
+
+import static org.hamcrest.MatcherAssert.assertThat
+
+import static org.hamcrest.Matchers.*
+
+/**
+ * Unit tests for {@link JCacheManager}.
+ *
+ * @since 1.9
+ */
+class JCacheManagerTest {
+
+ @Test
+ void invalidConfigFile() {
+ JCacheManager cacheManager = new JCacheManager()
+ cacheManager.setCacheConfig("./invalid/location")
+ def exception = expectThrows CacheException, { cacheManager.init() }
+ assertThat exception.message, containsString("Could not load JCache configuration resource: ./invalid/location")
+ }
+
+ @Test
+ void happyPath() {
+ JCacheManager cacheManager = new JCacheManager()
+ cacheManager.init()
+ Cache cache = cacheManager.getCache("foobar")
+ assertThat cache, notNullValue()
+ cache.put("Foo", "Bar")
+ assertThat cache.get("Foo"), is("Bar")
+ }
+
+ @Test
+ void sizeTest() {
+ JCacheManager cacheManager = new JCacheManager()
+ cacheManager.init()
+ Cache cache = cacheManager.getCache("size-test")
+ assertThat cache, notNullValue()
+ cache.put("one", "value")
+ assertThat cache.size(), is(1)
+ }
+
+ @Test
+ void clear() {
+ JCacheManager cacheManager = new JCacheManager()
+ cacheManager.init()
+ Cache cache = cacheManager.getCache("clear-test")
+ assertThat cache, notNullValue()
+ cache.put("one", "value")
+ cache.clear()
+ assertThat cache.get("one"), nullValue()
+ }
+
+ @Test
+ void remove() {
+ JCacheManager cacheManager = new JCacheManager()
+ cacheManager.init()
+ Cache cache = cacheManager.getCache("remove-test")
+ assertThat cache, notNullValue()
+ cache.put("one", "value1")
+ cache.put("two", "value2")
+ cache.remove("one")
+ assertThat cache.get("one"), nullValue()
+ assertThat cache.get("two"), is("value2")
+ }
+
+ @Test
+ void values() {
+ JCacheManager cacheManager = new JCacheManager()
+ cacheManager.init()
+ Cache cache = cacheManager.getCache("values-test")
+ assertThat cache, notNullValue()
+ cache.put("one", "value1")
+ cache.put("two", "value2")
+ assertThat cache.values(), containsInAnyOrder("value1", "value2")
+ }
+
+ @Test
+ void keys() {
+ JCacheManager cacheManager = new JCacheManager()
+ cacheManager.init()
+ Cache cache = cacheManager.getCache("keys-test")
+ assertThat cache, notNullValue()
+ cache.put("one", "value1")
+ cache.put("two", "value2")
+ assertThat cache.keys(), containsInAnyOrder("one", "two")
+ }
+
+ @Test
+ void putWithPrevious() {
+ JCacheManager cacheManager = new JCacheManager()
+ cacheManager.init()
+ Cache cache = cacheManager.getCache("putWithPrevious-test")
+ assertThat cache, notNullValue()
+ assertThat cache.put("one", "value1"), nullValue()
+ assertThat cache.put("one", "value2"), is("value1")
+ assertThat cache.get("one"), is("value2")
+ }
+
+ @Test
+ void destroy() {
+ JCacheManager cacheManager = new JCacheManager()
+ cacheManager.init()
+ assertThat cacheManager.cacheManagerImplicitlyCreated, is(true)
+ Cache cache = cacheManager.getCache("destroy-test")
+ assertThat cache.put("one", "value1"), nullValue()
+ cacheManager.destroy()
+ assertThat cacheManager.cacheManagerImplicitlyCreated, is(false)
+ assertThat cacheManager.jCacheManager, nullValue()
+ }
+
+ static <T extends Throwable> T expectThrows(Class<T> exceptionClass, Closure closure) {
+ try {
+ closure.run()
+ } catch (Throwable t) {
+ if (exceptionClass.isAssignableFrom(t.getClass())) {
+ return t as T
+ }
+ throw t
+ }
+ Assert.fail("Expected ${exceptionClass.getName()} to be thrown");
+ return null
+ }
+}
diff --git a/support/pom.xml b/support/pom.xml
index a980a7b7..0562bb75 100644
--- a/support/pom.xml
+++ b/support/pom.xml
@@ -32,6 +32,7 @@
<modules>
<module>aspectj</module>
+ <module>jcache</module>
<module>ehcache</module>
<module>hazelcast</module>
<module>quartz</module>