From 8693df730ce2b735e46ce652abc1a756a51f539f Mon Sep 17 00:00:00 2001 From: Katia Aresti Date: Mon, 12 Jun 2017 12:44:01 +0200 Subject: [PATCH] ISPN-7780 multimap embedded first shot --- core/pom.xml | 1 + .../impl/ClusterExpirationManager.java | 4 + .../distribution/DistributionTestHelper.java | 1 - .../main/asciidoc/user_guide/marshalling.adoc | 2 + .../asciidoc/user_guide/multimapcache.adoc | 144 ++++++++ .../asciidoc/user_guide/user_guide.asciidoc | 1 + multimap/pom.xml | 48 +++ .../EmbeddedMultimapCacheManagerFactory.java | 38 ++ .../multimap/api/MultimapCache.java | 203 +++++++++++ .../multimap/impl/EmbeddedMultimapCache.java | 168 +++++++++ .../EmbeddedMultimapMetadataFileFinder.java | 19 + .../multimap/impl/ExternalizerIds.java | 14 + .../impl/MultimapModuleLifecycle.java | 38 ++ .../multimap/impl/function/BaseFunction.java | 14 + .../impl/function/ContainsFunction.java | 79 ++++ .../multimap/impl/function/PutFunction.java | 66 ++++ .../impl/function/RemoveFunction.java | 115 ++++++ .../OSGI-INF/blueprint/blueprint.xml | 9 + .../impl/DistributedMultimapCacheTest.java | 338 ++++++++++++++++++ .../impl/EmbeddedMultimapCacheTest.java | 277 ++++++++++++++ .../multimap/impl/MultimapTestUtils.java | 61 ++++ .../impl/TxDistributedMultimapCacheTest.java | 115 ++++++ .../impl/TxEmbeddedMultimapCacheTest.java | 192 ++++++++++ .../org/infinispan/multimap/impl/User.java | 70 ++++ .../impl/EntityManagerFactoryRegistry.java | 2 + pom.xml | 1 + 26 files changed, 2019 insertions(+), 1 deletion(-) create mode 100644 documentation/src/main/asciidoc/user_guide/multimapcache.adoc create mode 100644 multimap/pom.xml create mode 100644 multimap/src/main/java/org/infinispan/multimap/api/EmbeddedMultimapCacheManagerFactory.java create mode 100644 multimap/src/main/java/org/infinispan/multimap/api/MultimapCache.java create mode 100644 multimap/src/main/java/org/infinispan/multimap/impl/EmbeddedMultimapCache.java create mode 100644 multimap/src/main/java/org/infinispan/multimap/impl/EmbeddedMultimapMetadataFileFinder.java create mode 100644 multimap/src/main/java/org/infinispan/multimap/impl/ExternalizerIds.java create mode 100644 multimap/src/main/java/org/infinispan/multimap/impl/MultimapModuleLifecycle.java create mode 100644 multimap/src/main/java/org/infinispan/multimap/impl/function/BaseFunction.java create mode 100644 multimap/src/main/java/org/infinispan/multimap/impl/function/ContainsFunction.java create mode 100644 multimap/src/main/java/org/infinispan/multimap/impl/function/PutFunction.java create mode 100644 multimap/src/main/java/org/infinispan/multimap/impl/function/RemoveFunction.java create mode 100644 multimap/src/main/resources/OSGI-INF/blueprint/blueprint.xml create mode 100644 multimap/src/test/java/org/infinispan/multimap/impl/DistributedMultimapCacheTest.java create mode 100644 multimap/src/test/java/org/infinispan/multimap/impl/EmbeddedMultimapCacheTest.java create mode 100644 multimap/src/test/java/org/infinispan/multimap/impl/MultimapTestUtils.java create mode 100644 multimap/src/test/java/org/infinispan/multimap/impl/TxDistributedMultimapCacheTest.java create mode 100644 multimap/src/test/java/org/infinispan/multimap/impl/TxEmbeddedMultimapCacheTest.java create mode 100644 multimap/src/test/java/org/infinispan/multimap/impl/User.java diff --git a/core/pom.xml b/core/pom.xml index 7c9be336aed0..763e1e210dae 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -162,6 +162,7 @@ !${project.groupId}.commons.*, !${project.groupId}.counter.*, + !${project.groupId}.multimap.*, org.infinispan.marshall.core, ${project.groupId}.*;version=${project.version};-split-package:=error diff --git a/core/src/main/java/org/infinispan/expiration/impl/ClusterExpirationManager.java b/core/src/main/java/org/infinispan/expiration/impl/ClusterExpirationManager.java index 2bf97a3fea82..809764e08353 100644 --- a/core/src/main/java/org/infinispan/expiration/impl/ClusterExpirationManager.java +++ b/core/src/main/java/org/infinispan/expiration/impl/ClusterExpirationManager.java @@ -54,6 +54,10 @@ public class ClusterExpirationManager extends ExpirationManagerImpl private AdvancedCache cache; private boolean needTransaction; + public ExecutorService getAsyncExecutor() { + return asyncExecutor; + } + @Inject public void inject(AdvancedCache cache, Configuration configuration, @ComponentName(KnownComponentNames.ASYNC_OPERATIONS_EXECUTOR) ExecutorService asyncExecutor) { diff --git a/core/src/test/java/org/infinispan/distribution/DistributionTestHelper.java b/core/src/test/java/org/infinispan/distribution/DistributionTestHelper.java index c6d4fa242f93..b705342de392 100644 --- a/core/src/test/java/org/infinispan/distribution/DistributionTestHelper.java +++ b/core/src/test/java/org/infinispan/distribution/DistributionTestHelper.java @@ -47,7 +47,6 @@ public static void assertIsInContainerImmortal(Cache cache, Object key) { log.fatal(msg); assert false : msg; } - if (!(ice instanceof ImmortalCacheEntry)) { String msg = "Entry for key [" + key + "] on cache at [" + addressOf(cache) + "] should be immortal but was [" + ice + "]!"; log.fatal(msg); diff --git a/documentation/src/main/asciidoc/user_guide/marshalling.adoc b/documentation/src/main/asciidoc/user_guide/marshalling.adoc index fca75a5c05fa..02c3f515d381 100644 --- a/documentation/src/main/asciidoc/user_guide/marshalling.adoc +++ b/documentation/src/main/asciidoc/user_guide/marshalling.adoc @@ -436,6 +436,8 @@ This is the list of Externalizer identifiers that are used by Infinispan based m |Infinispan Scripting Module:|1800 - 1849 |Infinispan Server Event Logger Module:|1850 - 1899 |Infinispan Remote Store:|1900 - 1999 +|Infinispan Counters:|2000 - 2049 +|Infinispan Multimap:|2050 - 2099 |=============== diff --git a/documentation/src/main/asciidoc/user_guide/multimapcache.adoc b/documentation/src/main/asciidoc/user_guide/multimapcache.adoc new file mode 100644 index 000000000000..40aa6f61ef14 --- /dev/null +++ b/documentation/src/main/asciidoc/user_guide/multimapcache.adoc @@ -0,0 +1,144 @@ +== Multimap Cache + +Infinispan Multimap Cache is a new distributed and clustered collection type introduced in Infinispan 9. +MutimapCache is a type of Infinispan Cache that maps keys to values in which each key can contain multiple values. + +=== Installation and configuration + +.pom.xml +[source,xml] +---- + + org.infinispan + infinispan-multimap + ... + +---- + +=== MultimapCache API + +MultimapCache API exposes several methods to interact with the Multimap Cache. +All these methods are non-blocking in most of the cases. See [limitations] + +[source,java] +---- + +public interface MultimapCache { + + CompletableFuture put(K key, V value); + + CompletableFuture> get(K key); + + CompletableFuture remove(K key); + + CompletableFuture remove(K key, V value); + + CompletableFuture remove(Predicate p); + + CompletableFuture containsKey(K key); + + CompletableFuture containsValue(V value); + + CompletableFuture containsEntry(K key, V value); + + CompletableFuture size(); + + boolean supportsDuplicates(); + +} + +---- + +==== CompletableFuture put(K key, V value) +Puts a key-value pair in the multimap cache. + +[source,java] +---- +MultimapCache multimapCache = ...; + +multimapCache.put("girlNames", "marie") + .thenCompose(r1 -> multimapCache.put("girlNames", "oihana")) + .thenCompose(r3 -> multimapCache.get("girlNames")) + .thenAccept(names -> { + if(names.contains("marie")) + System.out.println("Marie is a girl name"); + + if(names.contains("oihana")) + System.out.println("Oihana is a girl name"); + }); +---- +The output of this code is : + +[source, txt] +---- +Marie is a girl name +Oihana is a girl name +---- + +==== CompletableFuture> get(K key) + +Returns a view collection of the values associated with key in this multimap cache, if any. Any changes to the retrieved collection won't change the values in this multimap cache. +When this method returns an empty collection, it means the key was not found. + + +==== CompletableFuture remove(K key) +Removes the entry associated with the key from the multimap cache, if such exists. + +==== CompletableFuture remove(K key, V value) +Removes a key-value pair from the multimap cache, if such exists. + + +==== CompletableFuture remove(Predicate p) + +==== CompletableFuture containsKey(K key) + +==== CompletableFuture containsValue(V value) + +==== CompletableFuture containsEntry(K key, V value) + +==== CompletableFuture size() +Returns the number of key-value pairs in the multimap cache. It doesn't return the distinct number of keys. + +==== boolean supportsDuplicates() + + +=== Creating a Multimap Cache + +In version 9.2, the MultimapCache is configured as a regular cache. This can be done either by code or XML configuration. +See how to configure a regular Cache in the section link to [configure a cache]. + +==== Embedded mode + +[source,java] +---- +// create or obtain your EmbeddedCacheManager +EmbeddedCacheManager cm = ... ; + +// create or obtain a MultimapCache passing the name, the cache manager and the configuration +MultimapCache multimapCache = EmbeddedMultimapCacheManagerFactory.get("test", cm, c.build()); +---- + +==== Server mode + +TODO + +=== Limitations + +In almost every case the Multimap Cache will behave as a regular Cache, but some limitations exist in the first 9.2 version. + +==== Support for duplicates +Duplicates are not supported yet. This means that the multimap won't contain any duplicate key-value pair. +Whenever put method is called, if the key-value pair already exist, this key-value par won't be added. +Methods used to check if a key-value pair is already present in the Multimap are the `equals` and `hashcode`. + +==== Eviction + +For now, the eviction works per key, and not per key-value pair. +This means that whenever a key is evicted, all the values associated with the key will be evicted too. +Eviction per key-value could be supported in the future. + +==== Transactions + +Implicit transactions are supported through the auto-commit and all the methods are non blocking. +Explicit transactions work without blocking in most of the cases. +Methods that will block are `size`, `containsEntry` and `remove(Predicate p)` diff --git a/documentation/src/main/asciidoc/user_guide/user_guide.asciidoc b/documentation/src/main/asciidoc/user_guide/user_guide.asciidoc index 063c468affbc..6ffe03b24181 100644 --- a/documentation/src/main/asciidoc/user_guide/user_guide.asciidoc +++ b/documentation/src/main/asciidoc/user_guide/user_guide.asciidoc @@ -37,3 +37,4 @@ include::rolling_upgrades.adoc[] include::extending.adoc[] include::architecture.adoc[] include::counters.adoc[] +include::multimapcache.adoc[] \ No newline at end of file diff --git a/multimap/pom.xml b/multimap/pom.xml new file mode 100644 index 000000000000..a52a4e9281fa --- /dev/null +++ b/multimap/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + org.infinispan + infinispan-parent + 9.1.0-SNAPSHOT + ../parent/pom.xml + + + infinispan-multimap + jar + Infinispan Multimap + Infinispan Multimap module + + + false + + + + + ${project.groupId} + infinispan-core + + + org.kohsuke.metainf-services + metainf-services + true + + + + ${project.groupId} + infinispan-core + test-jar + test + + + ${project.groupId} + infinispan-commons-test + test + + + org.testng + testng + test + + + diff --git a/multimap/src/main/java/org/infinispan/multimap/api/EmbeddedMultimapCacheManagerFactory.java b/multimap/src/main/java/org/infinispan/multimap/api/EmbeddedMultimapCacheManagerFactory.java new file mode 100644 index 000000000000..8652c6ce4f42 --- /dev/null +++ b/multimap/src/main/java/org/infinispan/multimap/api/EmbeddedMultimapCacheManagerFactory.java @@ -0,0 +1,38 @@ +package org.infinispan.multimap.api; + +import java.util.Collection; + +import org.infinispan.Cache; +import org.infinispan.configuration.cache.Configuration; +import org.infinispan.manager.EmbeddedCacheManager; +import org.infinispan.multimap.api.MultimapCache; +import org.infinispan.multimap.impl.EmbeddedMultimapCache; + +/** + * A {@link MultimapCache} factory for embedded cached. + * + * @author Katia Aresti, karesti@redhat.com + * @since 9.2 + */ +public final class EmbeddedMultimapCacheManagerFactory { + + private EmbeddedMultimapCacheManagerFactory() { + } + + /** + * Factory used to create an embedded multimap cache. + * + * @param name, name of the multimap cache + * @param cacheManager, the cache manaher + * @param configuration, the configuration + * @return the {@link MultimapCache} created by {@link EmbeddedMultimapCache} + */ + public static MultimapCache get(String name, EmbeddedCacheManager cacheManager, Configuration configuration) { + if (cacheManager.getCacheConfiguration(name) == null) { + cacheManager.defineConfiguration(name, configuration); + } + Cache> cache = cacheManager.getCache(name, true); + EmbeddedMultimapCache multimapCache = new EmbeddedMultimapCache(cache); + return multimapCache; + } +} diff --git a/multimap/src/main/java/org/infinispan/multimap/api/MultimapCache.java b/multimap/src/main/java/org/infinispan/multimap/api/MultimapCache.java new file mode 100644 index 000000000000..025a84839d4d --- /dev/null +++ b/multimap/src/main/java/org/infinispan/multimap/api/MultimapCache.java @@ -0,0 +1,203 @@ +package org.infinispan.multimap.api; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; + +import org.infinispan.commons.util.Experimental; +import org.infinispan.util.function.SerializablePredicate; + +/** + * MultimapCache provides the common building block for the two different types of multimap caches that Infinispan + * provides: embedded and remote.

Please see the Infinispan + * documentation and/or the 5 Minute + * Usage Tutorial for more details on Infinispan. + *

+ *

+ * MutimapCache is a type of Infinispan Cache that maps keys to values, similar to {@link + * org.infinispan.commons.api.AsyncCache} in which each key can contain multiple values. + *

+ *    foo -> 1
+ *    bar -> 3, 4, 5
+ * 
+ *

+ *

Example

+ *
+ *
+ *    multimapCache.put("k", "v1").join();
+ *    multimapCache.put("k", "v2").join();
+ *    multimapCache.put("k", "v3").join();
+ *
+ *    Collection results = multimapCache.get("k").join();
+ *
+ * 
+ *

+ *

Eviction

Eviction works per key. This means all the values associated on a key will be evicted.

+ *

+ *

Views

+ *

+ * The returned collections when calling "get" are views of the values on the key. Any change on these collections won't + * affect the cache values on the key. + *

+ *

Null values

Null values are not supported. The multimap cache won't have a null key or any null value. + *

+ * Example + *

+ *     multimapCache.put(null, "v1").join() -> fails
+ *     multimapCache.put("k", null).join() -> fails
+ *     multimapCache.put("k", "v1").join() -> works and add's v1
+ *     multimapCache.containsKey("k").join() -> true
+ *     multimapCache.remove("k", "v1").join() -> works, removes v1 and as the remaining collection is empty, the key is
+ * removed
+ *     multimapCache.containsKey("k").join() -> false
+ *  
+ *

+ *

+ *

Duplicates

Today's implementation does not support duplicate values on keys. {@link Object#equals(Object)} + * method is used to check if a value is already present in the key. This means that the following code + *
+ *    multimapCache.put("k", "v1").join();
+ *    multimapCache.put("k", "v2").join();
+ *    multimapCache.put("k", "v2").join();
+ *    multimapCache.put("k", "v2").join();
+ *
+ *    multimapCache.get("k").thenAccept(values -> System.out.println(values.size()));
+ * 
+ * prints the value 2. "k" -> ["v1", "v"] + *

+ * Future implementations might evolve to support duplicated values. + *

+ *

Transactions

MultimapCache supports implicit transactions without blocking. The following methods block when + * they are called in a explicit transaction context. This limitation could be improved in the followin versions if + * technically possible.
  • {@link MultimapCache#size()}
  • {@link MultimapCache#containsEntry(Object, + * Object)}
  • {@link MultimapCache#remove(Predicate)}
More about transactions in : the Infinispan Documentation. + * + * @author Katia Aresti, karesti@redhat.com + * @see Infinispan documentation + * @since 9.2 + */ +@Experimental +public interface MultimapCache { + + /** + * Asynchronous method. Puts a key-value pair in this multimap cache. + *
    + *
  • If this multimap cache supports duplicates, the value will be always added.
  • + *
  • If this multimap cache does not support duplicates and the value exists on the key, + * nothing will be done.
  • + *
+ * + * @param key, the key to be put + * @param value, the value to added + * @return {@link CompletableFuture} containing a {@link Void} + */ + CompletableFuture put(K key, V value); + + /*** + * Asynchronous method. Returns a view collection of the values associated with key in this multimap cache, + * if any. Any changes to the retrieved collection won't change the values in this multimap cache. + * When this method returns an empty collection, it means the key was not found. + * + * @param key, to be retrieved + * @return a {@link CompletableFuture} containing {@link Collection } which is a view of the underlying values. + */ + CompletableFuture> get(K key); + + /** + * Asynchronous method. Removes the entry associated with the key from this multimap cache, if such exists. + * + * @param key, to be removed + * @return a {@link CompletableFuture} containing {@link Boolean#TRUE} if the entry was removed, and {@link + * Boolean#FALSE} when the entry was not removed + */ + CompletableFuture remove(K key); + + /** + * Asynchronous method. Removes a key-value pair from this multimap cache, if such exists. + * Returns true when the key-value pair has been removed from the key. + *

+ *

    + *
  • In the case where duplicates are not supported, only one the key-value + * pair will be removed, if such exists.
  • + *
  • In the case where duplicates are supported, all the key-value pairs will be removed.
  • + *
  • If the values remaining after the remove call are empty, the whole entry will be removed.
  • + *
+ * + * @param key, key to be removed + * @param value, value to be removed + * @return {@link CompletableFuture} containing {@link Boolean#TRUE} if the key-value pair was removed, and {@link + * Boolean#FALSE} when the key-value pair was not removed + */ + CompletableFuture remove(K key, V value); + + /** + * Asynchronous method. Removes every value that match the {@link Predicate}. + *

+ * This method is blocking used in a explicit transaction context. + * + * @param p, the predicate to be tested on every value in this multimap cache + * @return {@link CompletableFuture} containing a {@link Void} + */ + CompletableFuture remove(Predicate p); + + /** + * Overloaded method of {@link MultimapCache#remove(Predicate)} with {@link SerializablePredicate}. The compiler + * will pick up this method and make the given predicate {@link java.io.Serializable}. + * + * @param p, the predicate to be tested on every value in this multimap cache + * @return {@link CompletableFuture} containing a {@link Void} + */ + default CompletableFuture remove(SerializablePredicate p) { + return this.remove((Predicate) p); + } + + /** + * Asynchronous method. Returns {@link Boolean#TRUE} if this multimap cache contains the key. + * + * @param key, the key tht might exists in this multimap cache + * @return {@link CompletableFuture} containing a {@link Boolean} + */ + CompletableFuture containsKey(K key); + + /** + * Asynchronous method that returns {@link Boolean#TRUE} if this multimap cache contains the value at any key. + * + * @param value, the value that might exists in any entry + * @return {@link CompletableFuture} containing a {@link Boolean} + */ + CompletableFuture containsValue(V value); + + /** + * Asynchronous method that returns {@link Boolean#TRUE} if this multimap cache contains the key-value pair. + * + * @param key, the key of the key-value pair + * @param value, the value of the key-value pair + * @return {@link CompletableFuture} containing a {@link Boolean} + */ + CompletableFuture containsEntry(K key, V value); + + /** + * Returns the number of key-value pairs in this multimap cache. + * It doesn't return the distinct number of keys. + *

+ * This method is blocking in a explicit transaction context. + *

+ * The {@link CompletableFuture} is a + * + * @return {@link CompletableFuture} containing the size as {@link Long} + */ + CompletableFuture size(); + + /** + * Multimap can support duplicates on the same key k -> ['a', 'a', 'b'] or not k -> ['a', 'b'] + * depending on configuration. + *

+ * Returns duplicates are supported or not in this multimap cache. + * + * @return {@code true} if this multicache supports duplicate values for a given key. + */ + boolean supportsDuplicates(); + +} + diff --git a/multimap/src/main/java/org/infinispan/multimap/impl/EmbeddedMultimapCache.java b/multimap/src/main/java/org/infinispan/multimap/impl/EmbeddedMultimapCache.java new file mode 100644 index 000000000000..2276ee762bd7 --- /dev/null +++ b/multimap/src/main/java/org/infinispan/multimap/impl/EmbeddedMultimapCache.java @@ -0,0 +1,168 @@ +package org.infinispan.multimap.impl; + +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.runAsync; +import static java.util.concurrent.CompletableFuture.supplyAsync; +import static org.infinispan.util.concurrent.CompletableFutures.rethrowException; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; + +import javax.transaction.SystemException; +import javax.transaction.TransactionManager; + +import org.infinispan.Cache; +import org.infinispan.functional.FunctionalMap; +import org.infinispan.functional.impl.FunctionalMapImpl; +import org.infinispan.functional.impl.ReadWriteMapImpl; +import org.infinispan.multimap.api.MultimapCache; +import org.infinispan.multimap.impl.function.ContainsFunction; +import org.infinispan.multimap.impl.function.PutFunction; +import org.infinispan.multimap.impl.function.RemoveFunction; +import org.infinispan.util.concurrent.CompletableFutures; + +/** + * Embedded implementation of {@link MultimapCache} + * + * @author Katia Aresti, karesti@redhat.com + * @since 9.2 + */ +public class EmbeddedMultimapCache implements MultimapCache { + + private FunctionalMap.ReadWriteMap> readWriteMap; + private Cache> cache; + + public EmbeddedMultimapCache(Cache> cache) { + this.cache = cache; + FunctionalMapImpl> functionalMap = FunctionalMapImpl.create(this.cache.getAdvancedCache()); + this.readWriteMap = ReadWriteMapImpl.create(functionalMap); + } + + @Override + public CompletableFuture put(K key, V value) { + requireNonNull(key, "key can't be null"); + requireNonNull(value, "value can't be null"); + return readWriteMap.eval(key, new PutFunction<>(key, value)); + } + + @Override + public CompletableFuture> get(K key) { + requireNonNull(key, "key can't be null"); + return cache.getAsync(key).thenApply(v -> v == null ? Collections.emptySet() : Collections.unmodifiableCollection(v)); + } + + @Override + public CompletableFuture remove(K key) { + requireNonNull(key, "key can't be null"); + return readWriteMap.eval(key, new RemoveFunction<>(key)); + } + + @Override + public CompletableFuture remove(K key, V value) { + requireNonNull(key, "key can't be null"); + requireNonNull(value, "value can't be null"); + return readWriteMap.eval(key, new RemoveFunction<>(key, value)); + } + + @Override + public CompletableFuture remove(Predicate p) { + requireNonNull(p, "predicate can't be null"); + CompletableFuture cf = CompletableFutures.completedNull(); + try { + if (isExplicitTxContext()) { + // block on explicit tx + cf = completedFuture(this.removeInternal(p)); + } else { + cf = runAsync(() -> this.removeInternal(p)); + } + } catch (SystemException e) { + rethrowException(e); + } + return cf; + } + + @Override + public CompletableFuture containsKey(K key) { + requireNonNull(key, "key can't be null"); + return readWriteMap.eval(key, new ContainsFunction<>(key, null)); + } + + @Override + public CompletableFuture containsValue(V value) { + requireNonNull(value, "value can't be null"); + CompletableFuture cf = CompletableFutures.completedNull(); + try { + if (isExplicitTxContext()) { + // block on explicit tx + cf = completedFuture(containsEntryInternal(value)); + } else { + cf = supplyAsync(() -> containsEntryInternal(value)); + } + } catch (SystemException e) { + rethrowException(e); + } + return cf; + } + + @Override + public CompletableFuture containsEntry(K key, V value) { + requireNonNull(key, "key can't be null"); + requireNonNull(value, "value can't be null"); + return readWriteMap.eval(key, new ContainsFunction<>(key, value)); + } + + @Override + public CompletableFuture size() { + CompletableFuture cf = CompletableFutures.completedNull(); + try { + if (isExplicitTxContext()) { + // block on explicit tx + cf = completedFuture(sizeInternal()); + } else { + cf = supplyAsync(() -> sizeInternal()); + } + } catch (SystemException e) { + rethrowException(e); + } + return cf; + } + + private boolean isExplicitTxContext() throws SystemException { + TransactionManager transactionManager = cache.getAdvancedCache().getTransactionManager(); + return transactionManager != null && transactionManager.getTransaction() != null; + } + + private Void removeInternal(Predicate p) { + cache.keySet().stream().forEach((c, key) -> c.computeIfPresent(key, (o, o1) -> { + Collection values = (Collection) o1; + Collection newValues = new HashSet<>(); + for (V v : values) { + if (!p.test(v)) + newValues.add(v); + } + return newValues.isEmpty() ? null : newValues; + })); + return null; + } + + private Boolean containsEntryInternal(V value) { + return cache.entrySet().stream().anyMatch(kCollectionEntry -> kCollectionEntry.getValue().contains(value)); + } + + private Long sizeInternal() { + return cache.values().stream().mapToLong(value -> value.size()).sum(); + } + + @Override + public boolean supportsDuplicates() { + return false; + } + + public Cache> getCache() { + return cache; + } +} diff --git a/multimap/src/main/java/org/infinispan/multimap/impl/EmbeddedMultimapMetadataFileFinder.java b/multimap/src/main/java/org/infinispan/multimap/impl/EmbeddedMultimapMetadataFileFinder.java new file mode 100644 index 000000000000..cd4e8b3a8a2c --- /dev/null +++ b/multimap/src/main/java/org/infinispan/multimap/impl/EmbeddedMultimapMetadataFileFinder.java @@ -0,0 +1,19 @@ +package org.infinispan.multimap.impl; + +import org.infinispan.factories.components.ModuleMetadataFileFinder; +import org.kohsuke.MetaInfServices; + +/** + * Multimap Cache module implementation of {@link ModuleMetadataFileFinder} + * + * @author Katia Aresti - karesti@redhat.com + * @since 9.2 + */ +@MetaInfServices +public class EmbeddedMultimapMetadataFileFinder implements ModuleMetadataFileFinder { + + @Override + public String getMetadataFilename() { + return "infinispan-multimap-component-metadata.dat"; + } +} diff --git a/multimap/src/main/java/org/infinispan/multimap/impl/ExternalizerIds.java b/multimap/src/main/java/org/infinispan/multimap/impl/ExternalizerIds.java new file mode 100644 index 000000000000..438f5d529149 --- /dev/null +++ b/multimap/src/main/java/org/infinispan/multimap/impl/ExternalizerIds.java @@ -0,0 +1,14 @@ +package org.infinispan.multimap.impl; + +/** + * Externalizer Ids that identity the functions used in {@link EmbeddedMultimapCache} + * + * @see Marshalling of Functions + * @author Katia Aresti - karesti@redhat.com + * @since 9.2 + */ +public interface ExternalizerIds { + Integer PUT_KEY_VALUE_FUNCTION = 2050; + Integer REMOVE_KEY_VALUE_FUNCTION = 2051; + Integer CONTAINS_KEY_VALUE_FUNCTION = 2052; +} diff --git a/multimap/src/main/java/org/infinispan/multimap/impl/MultimapModuleLifecycle.java b/multimap/src/main/java/org/infinispan/multimap/impl/MultimapModuleLifecycle.java new file mode 100644 index 000000000000..9e0b5925df6e --- /dev/null +++ b/multimap/src/main/java/org/infinispan/multimap/impl/MultimapModuleLifecycle.java @@ -0,0 +1,38 @@ +package org.infinispan.multimap.impl; + +import java.util.Map; + +import org.infinispan.commons.marshall.AdvancedExternalizer; +import org.infinispan.configuration.global.GlobalConfiguration; +import org.infinispan.factories.GlobalComponentRegistry; +import org.infinispan.lifecycle.AbstractModuleLifecycle; +import org.infinispan.lifecycle.ModuleLifecycle; +import org.infinispan.multimap.impl.function.ContainsFunction; +import org.infinispan.multimap.impl.function.PutFunction; +import org.infinispan.multimap.impl.function.RemoveFunction; +import org.kohsuke.MetaInfServices; + +/** + * MultimapModuleLifecycle is necessary for the Multimap Cache module. + * Registers advanced externalizers. + * + * @author Katia Aresti - karesti@redhat.com + * @since 9.2 + */ +@MetaInfServices(value = ModuleLifecycle.class) +public class MultimapModuleLifecycle extends AbstractModuleLifecycle { + + private static void addAdvancedExternalizer(Map> map, AdvancedExternalizer ext) { + map.put(ext.getId(), ext); + } + + @Override + public void cacheManagerStarting(GlobalComponentRegistry gcr, GlobalConfiguration globalConfiguration) { + final Map> externalizerMap = globalConfiguration.serialization() + .advancedExternalizers(); + + addAdvancedExternalizer(externalizerMap, PutFunction.EXTERNALIZER); + addAdvancedExternalizer(externalizerMap, RemoveFunction.EXTERNALIZER); + addAdvancedExternalizer(externalizerMap, ContainsFunction.EXTERNALIZER); + } +} diff --git a/multimap/src/main/java/org/infinispan/multimap/impl/function/BaseFunction.java b/multimap/src/main/java/org/infinispan/multimap/impl/function/BaseFunction.java new file mode 100644 index 000000000000..6546f3e77fe9 --- /dev/null +++ b/multimap/src/main/java/org/infinispan/multimap/impl/function/BaseFunction.java @@ -0,0 +1,14 @@ +package org.infinispan.multimap.impl.function; + +import java.util.Collection; + +import org.infinispan.functional.EntryView; +import org.infinispan.util.function.SerializableFunction; + +/** + * A base function for the multimap updates + * + * @author Katia Aresti, karesti@redhat.com + * @since 9.2 + */ +abstract class BaseFunction implements SerializableFunction>, R> {} diff --git a/multimap/src/main/java/org/infinispan/multimap/impl/function/ContainsFunction.java b/multimap/src/main/java/org/infinispan/multimap/impl/function/ContainsFunction.java new file mode 100644 index 000000000000..bebaa89f5d6b --- /dev/null +++ b/multimap/src/main/java/org/infinispan/multimap/impl/function/ContainsFunction.java @@ -0,0 +1,79 @@ +package org.infinispan.multimap.impl.function; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +import org.infinispan.commons.marshall.AdvancedExternalizer; +import org.infinispan.functional.EntryView; +import org.infinispan.multimap.impl.ExternalizerIds; + +/** + * Serializable function used by {@link org.infinispan.multimap.impl.EmbeddedMultimapCache#containsKey(Object)} and + * {@link org.infinispan.multimap.impl.EmbeddedMultimapCache#containsEntry(Object, Object)}. + * + * @author Katia Aresti - karesti@redhat.com + * @see Marshalling of Functions + * @since 9.2 + */ +public final class ContainsFunction extends BaseFunction { + + public static final AdvancedExternalizer EXTERNALIZER = new Externalizer(); + private final K key; + private final V value; + + /** + * Call this constructor to create a function that checks if a key/value pair exists + *

    + *
  • if the key is null, the value will be searched in any key
  • + *
  • if the value is null, the key will be searched
  • + *
  • key-value pair are not null, the entry will be searched
  • + *
  • key-value pair are null, a {@link NullPointerException} will be raised
  • + *
+ * @param key key to be checked + * @param value value to be checked on the key + */ + public ContainsFunction(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public Boolean apply(EntryView.ReadWriteEntryView> entryView) { + Boolean contains; + if (value == null) { + contains = entryView.find().isPresent(); + } else { + contains = entryView.find().map(values -> values.contains(value)).orElse(Boolean.FALSE); + } + + return contains; + } + + private static class Externalizer implements AdvancedExternalizer { + + @Override + public Set> getTypeClasses() { + return Collections.singleton(ContainsFunction.class); + } + + @Override + public Integer getId() { + return ExternalizerIds.CONTAINS_KEY_VALUE_FUNCTION; + } + + @Override + public void writeObject(ObjectOutput output, ContainsFunction object) throws IOException { + output.writeObject(object.key); + output.writeObject(object.value); + } + + @Override + public ContainsFunction readObject(ObjectInput input) throws IOException, ClassNotFoundException { + return new ContainsFunction(input.readObject(), input.readObject()); + } + } +} diff --git a/multimap/src/main/java/org/infinispan/multimap/impl/function/PutFunction.java b/multimap/src/main/java/org/infinispan/multimap/impl/function/PutFunction.java new file mode 100644 index 000000000000..0df3d51cba5a --- /dev/null +++ b/multimap/src/main/java/org/infinispan/multimap/impl/function/PutFunction.java @@ -0,0 +1,66 @@ +package org.infinispan.multimap.impl.function; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.infinispan.commons.marshall.AdvancedExternalizer; +import org.infinispan.functional.EntryView; +import org.infinispan.multimap.impl.ExternalizerIds; + +/** + * Serializable function used by {@link org.infinispan.multimap.impl.EmbeddedMultimapCache#put(Object, Object)} + * to add a key/value pair. + * + * @author Katia Aresti - karesti@redhat.com + * @see Marshalling of Functions + * @since 9.2 + */ +public final class PutFunction extends BaseFunction { + + public static final AdvancedExternalizer EXTERNALIZER = new Externalizer(); + private final K key; + private final V value; + + public PutFunction(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public Void apply(EntryView.ReadWriteEntryView> entryView) { + Collection newValues = new HashSet<>(); + newValues.add(value); + entryView.find().map(newValues::addAll); + entryView.set(newValues); + return null; + } + + private static class Externalizer implements AdvancedExternalizer { + + @Override + public Set> getTypeClasses() { + return Collections.singleton(PutFunction.class); + } + + @Override + public Integer getId() { + return ExternalizerIds.PUT_KEY_VALUE_FUNCTION; + } + + @Override + public void writeObject(ObjectOutput output, PutFunction object) throws IOException { + output.writeObject(object.key); + output.writeObject(object.value); + } + + @Override + public PutFunction readObject(ObjectInput input) throws IOException, ClassNotFoundException { + return new PutFunction(input.readObject(), input.readObject()); + } + } +} diff --git a/multimap/src/main/java/org/infinispan/multimap/impl/function/RemoveFunction.java b/multimap/src/main/java/org/infinispan/multimap/impl/function/RemoveFunction.java new file mode 100644 index 000000000000..dc200bea4b3e --- /dev/null +++ b/multimap/src/main/java/org/infinispan/multimap/impl/function/RemoveFunction.java @@ -0,0 +1,115 @@ +package org.infinispan.multimap.impl.function; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.infinispan.commons.marshall.AdvancedExternalizer; +import org.infinispan.functional.EntryView; +import org.infinispan.multimap.impl.ExternalizerIds; + +/** + * Serializable function used by {@link org.infinispan.multimap.impl.EmbeddedMultimapCache#remove(Object)} and + * {@link org.infinispan.multimap.impl.EmbeddedMultimapCache#remove(Object, Object)} to remove a key or a key/value + * pair from the Multimap Cache, if such exists. + *

+ * {@link #apply(EntryView.ReadWriteEntryView)} will return {@link Boolean#TRUE} when the operation removed a key or + * a key/value pair and will return {@link Boolean#FALSE} if the key or key/value pair does not exist + * + * @author Katia Aresti - karesti@redhat.com + * @see Marshalling of Functions + * @since 9.2 + */ +public final class RemoveFunction extends BaseFunction { + + public static final AdvancedExternalizer EXTERNALIZER = new Externalizer(); + private final K key; + private final V value; + + /** + * Call this constructor to create a function that removed a key + * + * @param key key to be removed + */ + public RemoveFunction(K key) { + this.key = key; + this.value = null; + } + + /** + * Call this constructor to create a function that removed a key/value pair + * + * @param key key to be removed + * @param value value to be removed + */ + public RemoveFunction(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public Boolean apply(EntryView.ReadWriteEntryView> entryView) { + Boolean removed; + if (value == null) { + removed = removeKey(entryView); + } else { + removed = removeKeyValue(entryView); + } + return removed; + } + + private Boolean removeKeyValue(EntryView.ReadWriteEntryView> entryView) { + return entryView.find().map(values -> { + if (values.contains(value)) { + Collection newValues = new HashSet<>(); + newValues.addAll(values); + newValues.remove(value); + if (newValues.isEmpty()) { + // If the collection is empty after remove, remove the key + entryView.remove(); + } else { + entryView.set(newValues); + } + return newValues.size() < values.size(); + } else { + return Boolean.FALSE; + } + } + ).orElse(Boolean.FALSE); + } + + private Boolean removeKey(EntryView.ReadWriteEntryView> entryView) { + return entryView.find().map(values -> { + entryView.remove(); + return Boolean.TRUE; + }).orElse(Boolean.FALSE); + } + + private static class Externalizer implements AdvancedExternalizer { + + @Override + public Set> getTypeClasses() { + return Collections.singleton(RemoveFunction.class); + } + + @Override + public Integer getId() { + return ExternalizerIds.REMOVE_KEY_VALUE_FUNCTION; + } + + @Override + public void writeObject(ObjectOutput output, RemoveFunction object) throws IOException { + output.writeObject(object.key); + output.writeObject(object.value); + } + + @Override + public RemoveFunction readObject(ObjectInput input) throws IOException, ClassNotFoundException { + return new RemoveFunction(input.readObject(), input.readObject()); + } + } +} diff --git a/multimap/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/multimap/src/main/resources/OSGI-INF/blueprint/blueprint.xml new file mode 100644 index 000000000000..70a309b4b22d --- /dev/null +++ b/multimap/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -0,0 +1,9 @@ + + + + +${services} + + diff --git a/multimap/src/test/java/org/infinispan/multimap/impl/DistributedMultimapCacheTest.java b/multimap/src/test/java/org/infinispan/multimap/impl/DistributedMultimapCacheTest.java new file mode 100644 index 000000000000..5df30ec6f983 --- /dev/null +++ b/multimap/src/test/java/org/infinispan/multimap/impl/DistributedMultimapCacheTest.java @@ -0,0 +1,338 @@ +package org.infinispan.multimap.impl; + +import static java.lang.String.format; +import static org.infinispan.functional.FunctionalTestUtils.await; +import static org.infinispan.multimap.impl.MultimapTestUtils.JULIEN; +import static org.infinispan.multimap.impl.MultimapTestUtils.NAMES_KEY; +import static org.infinispan.multimap.impl.MultimapTestUtils.OIHANA; +import static org.infinispan.multimap.impl.MultimapTestUtils.RAMON; +import static org.infinispan.multimap.impl.MultimapTestUtils.assertMultimapCacheSize; +import static org.infinispan.multimap.impl.MultimapTestUtils.putValuesOnMultimapCache; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertTrue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.infinispan.Cache; +import org.infinispan.configuration.cache.CacheMode; +import org.infinispan.container.DataContainer; +import org.infinispan.container.entries.ImmortalCacheEntry; +import org.infinispan.container.entries.InternalCacheEntry; +import org.infinispan.distribution.BaseDistFunctionalTest; +import org.infinispan.manager.EmbeddedCacheManager; +import org.infinispan.multimap.api.EmbeddedMultimapCacheManagerFactory; +import org.infinispan.multimap.api.MultimapCache; +import org.infinispan.remoting.transport.Address; +import org.testng.annotations.Test; + +@Test(groups = "functional", testName = "distribution.DistributedMultimapCacheTest") +public class DistributedMultimapCacheTest extends BaseDistFunctionalTest> { + + protected Map> multimapCacheCluster = new HashMap<>(); + + protected boolean fromOwner; + + public DistributedMultimapCacheTest fromOwner(boolean fromOwner) { + this.fromOwner = fromOwner; + return this; + } + + @Override + protected String[] parameterNames() { + return concat(super.parameterNames(), "fromOwner"); + } + + @Override + protected Object[] parameterValues() { + return concat(super.parameterValues(), fromOwner ? Boolean.TRUE : Boolean.FALSE); + } + + @Override + public Object[] factory() { + List testsToRun = new ArrayList<>(); + testsToRun.add(new DistributedMultimapCacheTest().fromOwner(false).cacheMode(CacheMode.DIST_SYNC).transactional(false)); + testsToRun.add(new DistributedMultimapCacheTest().fromOwner(true).cacheMode(CacheMode.DIST_SYNC).transactional(false)); + return testsToRun.toArray(); + } + + @Override + protected void createCacheManagers() throws Throwable { + super.createCacheManagers(); + + for (EmbeddedCacheManager cacheManager : cacheManagers) { + multimapCacheCluster.put(cacheManager.getAddress(), EmbeddedMultimapCacheManagerFactory.get(cacheName, cacheManager, configuration.build())); + } + } + + @Override + protected void initAndTest() { + assertMultimapCacheSize(multimapCacheCluster, 0); + + putValuesOnMultimapCache(multimapCacheCluster, NAMES_KEY, OIHANA); + assertValuesAndOwnership(NAMES_KEY, OIHANA); + } + + public void testPut() { + initAndTest(); + MultimapCache multimapCache = getMultimapCacheMember(NAMES_KEY); + + putValuesOnMultimapCache(multimapCache, NAMES_KEY, JULIEN); + + assertValuesAndOwnership(NAMES_KEY, JULIEN); + } + + public void testRemoveKey() { + initAndTest(); + MultimapCache multimapCache = getMultimapCacheMember(NAMES_KEY); + + await( + multimapCache.remove(NAMES_KEY, OIHANA).thenCompose(r1 -> { + assertTrue(r1); + return multimapCache.get(NAMES_KEY).thenAccept(v -> assertTrue(v.isEmpty())); + }) + ); + + assertRemovedOnAllCaches(NAMES_KEY); + } + + public void testRemoveKeyValue() { + initAndTest(); + MultimapCache multimapCache = getMultimapCacheMember(NAMES_KEY); + + await(multimapCache.remove("unexistingKey", OIHANA).thenAccept(r -> assertFalse(r))); + assertValuesAndOwnership(NAMES_KEY, OIHANA); + + await( + multimapCache.remove(NAMES_KEY, OIHANA).thenCompose(r1 -> { + assertTrue(r1); + return multimapCache.get(NAMES_KEY).thenAccept(v -> assertTrue(v.isEmpty())); + } + ) + ); + + assertRemovedOnAllCaches(NAMES_KEY); + } + + public void testRemoveWithPredicate() { + MultimapCache multimapCache = getMultimapCacheMember(); + + await( + multimapCache.put(NAMES_KEY, OIHANA) + .thenCompose(r1 -> multimapCache.put(NAMES_KEY, JULIEN)) + .thenCompose(r2 -> multimapCache.get(NAMES_KEY)) + .thenAccept(v -> assertEquals(2, v.size())) + ); + + assertValuesAndOwnership(NAMES_KEY, OIHANA); + assertValuesAndOwnership(NAMES_KEY, JULIEN); + + MultimapCache multimapCache2 = getMultimapCacheMember(NAMES_KEY); + + await( + multimapCache2.remove(o -> o.getName().contains("Ka")) + .thenCompose(r1 -> multimapCache2.get(NAMES_KEY)) + .thenAccept(v -> + assertEquals(2, v.size()) + + ) + ); + assertValuesAndOwnership(NAMES_KEY, OIHANA); + assertValuesAndOwnership(NAMES_KEY, JULIEN); + + await( + multimapCache.remove(o -> o.getName().contains("Ju")) + .thenCompose(r1 -> multimapCache.get(NAMES_KEY)) + .thenAccept(v -> + assertEquals(1, v.size()) + ) + ); + assertValuesAndOwnership(NAMES_KEY, OIHANA); + assertKeyValueNotFoundInAllCaches(NAMES_KEY, JULIEN); + + + await( + multimapCache.remove(o -> o.getName().contains("Oi")) + .thenCompose(r1 -> multimapCache.get(NAMES_KEY)) + .thenAccept(v -> + assertTrue(v.isEmpty()) + ) + ); + + assertRemovedOnAllCaches(NAMES_KEY); + } + + public void testGetAndModifyResults() { + initAndTest(); + MultimapCache multimapCache = getMultimapCacheMember(NAMES_KEY); + + User pepe = new User("Pepe", 17); + + await( + multimapCache.get(NAMES_KEY) + .thenAccept(v -> { + List modifiedList = new ArrayList<>(v); + modifiedList.add(pepe); + } + ) + + ); + + assertKeyValueNotFoundInAllCaches(NAMES_KEY, pepe); + } + + public void testContainsKey() { + initAndTest(); + + multimapCacheCluster.values().forEach(mc -> { + await( + mc.containsKey("other") + .thenAccept(containsKey -> assertFalse(containsKey)) + ); + await( + mc.containsKey(NAMES_KEY) + .thenAccept(containsKey -> assertTrue(containsKey)) + ); + }); + } + + public void testContainsValue() { + initAndTest(); + + multimapCacheCluster.values().forEach(mc -> { + await( + mc.containsValue(RAMON) + .thenAccept(containsValue -> assertFalse(containsValue)) + ); + await( + mc.containsValue(OIHANA) + .thenAccept(containsValue -> assertTrue(containsValue)) + ); + }); + } + + public void testContainEntry() { + initAndTest(); + + multimapCacheCluster.values().forEach(mc -> { + await( + mc.containsEntry(NAMES_KEY, RAMON) + .thenAccept(containsValue -> assertFalse(containsValue)) + ); + await( + mc.containsEntry(NAMES_KEY, OIHANA) + .thenAccept(containsValue -> assertTrue(containsValue)) + ); + }); + } + + public void testSize() { + String anotherKey = "firstNames"; + MultimapCache multimapCache = getMultimapCacheMember(NAMES_KEY); + + await( + multimapCache.put(NAMES_KEY, OIHANA) + .thenCompose(r1 -> multimapCache.put(NAMES_KEY, JULIEN)) + .thenCompose(r2 -> multimapCache.put(anotherKey, OIHANA)) + .thenCompose(r3 -> multimapCache.put(anotherKey, JULIEN)) + .thenCompose(r4 -> multimapCache.size()) + .thenAccept(s -> { + assertEquals(4, s.intValue()); + assertValuesAndOwnership(NAMES_KEY, JULIEN); + assertValuesAndOwnership(NAMES_KEY, OIHANA); + assertValuesAndOwnership(anotherKey, JULIEN); + assertValuesAndOwnership(anotherKey, OIHANA); + }) + ); + } + + protected MultimapCache getMultimapCacheMember() { + return multimapCacheCluster.values().stream().findFirst().orElseThrow(() -> new IllegalStateException("Cluster is empty")); + } + + protected MultimapCache getMultimapCacheMember(String key) { + Cache> cache = fromOwner ? getFirstOwner(key) : getFirstNonOwner(key); + return multimapCacheCluster.get(cache.getCacheManager().getAddress()); + } + + protected MultimapCache getMultimapCacheFirstOwner(String key) { + Cache> cache = getFirstOwner(key); + return multimapCacheCluster.get(cache.getCacheManager().getAddress()); + } + + protected MultimapCache getMultimapCacheSecondOwner(String key) { + Cache> cache = getSecondNonOwner(key); + return multimapCacheCluster.get(cache.getCacheManager().getAddress()); + } + + protected void assertValuesAndOwnership(String key, User value) { + assertOwnershipAndNonOwnership(key, l1CacheEnabled); + assertOnAllCaches(key, value); + } + + protected void assertKeyValueNotFoundInAllCaches(String key, User value) { + for (Map.Entry> entry : multimapCacheCluster.entrySet()) { + await(entry.getValue().get(key).thenAccept(v -> { + assertNotNull(format("values on the key %s must be not null", key), v); + assertFalse(format("values on the key '%s' must not contain '%s' on node '%s'", key, value, entry.getKey()), v.contains(value)); + }) + + ); + } + } + + protected void assertKeyValueFoundInOwners(String key, User value) { + Cache> firstOwner = getFirstOwner(key); + Cache> secondNonOwner = getSecondNonOwner(key); + + MultimapCache mcFirstOwner = multimapCacheCluster.get(firstOwner.getCacheManager().getAddress()); + MultimapCache mcSecondOwner = multimapCacheCluster.get(secondNonOwner.getCacheManager().getAddress()); + + + await(mcFirstOwner.get(key).thenAccept(v -> { + assertTrue(format("firstOwner '%s' must contain key '%s' value '%s' pair", firstOwner.getCacheManager().getAddress(), key, value), v.contains(value)); + }) + ); + + await(mcSecondOwner.get(key).thenAccept(v -> { + assertTrue(format("secondOwner '%s' must contain key '%s' value '%s' pair", secondNonOwner.getCacheManager().getAddress(), key, value), v.contains(value)); + }) + ); + } + + @Override + protected void assertOwnershipAndNonOwnership(Object key, boolean allowL1) { + for (Cache cache : caches) { + DataContainer dc = cache.getAdvancedCache().getDataContainer(); + InternalCacheEntry ice = dc.get(key); + if (isOwner(cache, key)) { + assertNotNull(ice); + assertTrue(ice instanceof ImmortalCacheEntry); + } else { + if (allowL1) { + assertTrue("ice is null or L1Entry", ice == null || ice.isL1Entry()); + } else { + // Segments no longer owned are invalidated asynchronously + eventuallyEquals("Fail on non-owner cache " + addressOf(cache) + ": dc.get(" + key + ")", + null, () -> dc.get(key)); + } + } + } + } + + protected void assertOnAllCaches(Object key, User value) { + for (Map.Entry> entry : multimapCacheCluster.entrySet()) { + await(entry.getValue().get((String) key).thenAccept(v -> { + assertNotNull(format("values on the key %s must be not null", key), v); + assertTrue(format("values on the key '%s' must contain '%s' on node '%s'", key, value, entry.getKey()), v.contains(value)); + }) + + ); + } + } +} diff --git a/multimap/src/test/java/org/infinispan/multimap/impl/EmbeddedMultimapCacheTest.java b/multimap/src/test/java/org/infinispan/multimap/impl/EmbeddedMultimapCacheTest.java new file mode 100644 index 000000000000..02121630e180 --- /dev/null +++ b/multimap/src/test/java/org/infinispan/multimap/impl/EmbeddedMultimapCacheTest.java @@ -0,0 +1,277 @@ +package org.infinispan.multimap.impl; + +import static org.infinispan.functional.FunctionalTestUtils.await; +import static org.infinispan.multimap.impl.MultimapTestUtils.JULIEN; +import static org.infinispan.multimap.impl.MultimapTestUtils.KOLDO; +import static org.infinispan.multimap.impl.MultimapTestUtils.NAMES_KEY; +import static org.infinispan.multimap.impl.MultimapTestUtils.OIHANA; +import static org.infinispan.multimap.impl.MultimapTestUtils.RAMON; +import static org.infinispan.multimap.impl.MultimapTestUtils.putValuesOnMultimapCache; +import static org.infinispan.test.Exceptions.expectException; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import org.infinispan.configuration.cache.ConfigurationBuilder; +import org.infinispan.manager.EmbeddedCacheManager; +import org.infinispan.multimap.api.EmbeddedMultimapCacheManagerFactory; +import org.infinispan.multimap.api.MultimapCache; +import org.infinispan.test.SingleCacheManagerTest; +import org.infinispan.test.fwk.TestCacheManagerFactory; +import org.testng.annotations.Test; + +/** + * Single Multimap Cache Test + * + * @author Katia Aresti, karesti@redhat.com + * @since 9.2 + */ +@Test(groups = "functional", testName = "multimap.EmbeddedMultimapCacheTest") +public class EmbeddedMultimapCacheTest extends SingleCacheManagerTest { + protected MultimapCache multimapCache; + + @Override + protected EmbeddedCacheManager createCacheManager() throws Exception { + // start a single cache instance + ConfigurationBuilder c = getDefaultStandaloneCacheConfig(false); + EmbeddedCacheManager cm = TestCacheManagerFactory.createCacheManager(true); + multimapCache = EmbeddedMultimapCacheManagerFactory.get("test", cm, c.build()); + return cm; + } + + public void testSupportsDuplicates() { + assertFalse(multimapCache.supportsDuplicates()); + } + + public void testPut() { + await( + multimapCache.put(NAMES_KEY, JULIEN) + .thenCompose(r1 -> multimapCache.put(NAMES_KEY, OIHANA)) + .thenCompose(r2 -> multimapCache.put(NAMES_KEY, JULIEN)) + .thenCompose(r3 -> multimapCache.get(NAMES_KEY)) + .thenAccept(v -> { + assertTrue(v.contains(JULIEN)); + assertEquals(1, v.stream().filter(n -> n.equals(JULIEN)).count()); + } + ) + + ); + + await( + multimapCache.get(NAMES_KEY).thenAccept(v -> { + assertFalse(v.contains(KOLDO)); + assertEquals(2, v.size()); + } + ) + ); + } + + public void testRemoveKey() { + String key = NAMES_KEY; + + await( + multimapCache.put(key, OIHANA) + .thenCompose(r1 -> multimapCache.size()) + .thenAccept(s -> assertEquals(1, s.intValue())) + ); + + await( + multimapCache.remove(key, OIHANA).thenCompose(r1 -> { + assertTrue(r1); + return multimapCache.get(key).thenAccept(v -> assertTrue(v.isEmpty())); + }) + ); + + await(multimapCache.remove(key).thenAccept(r -> assertFalse(r))); + } + + public void testRemoveKeyValue() { + + await( + multimapCache.put(NAMES_KEY, OIHANA) + .thenCompose(r1 -> multimapCache.size()) + .thenAccept(s -> assertEquals(1, s.intValue())) + ); + + await(multimapCache.remove("unexistingKey", OIHANA).thenAccept(r -> assertFalse(r))); + + await( + multimapCache.remove(NAMES_KEY, JULIEN).thenCompose(r1 -> { + assertFalse(r1); + return multimapCache.get(NAMES_KEY).thenAccept(v -> assertEquals(1, v.size())); + } + ) + ); + + await( + multimapCache.remove(NAMES_KEY, OIHANA).thenCompose(r1 -> { + assertTrue(r1); + return multimapCache.get(NAMES_KEY).thenAccept(v -> assertTrue(v.isEmpty())); + } + ) + ); + + await(multimapCache.size().thenAccept(s -> assertEquals(0, s.intValue()))); + } + + public void testRemoveWithPredicate() { + + await( + multimapCache.put(NAMES_KEY, OIHANA) + .thenCompose(r1 -> multimapCache.put(NAMES_KEY, JULIEN)) + .thenCompose(r2 -> multimapCache.get(NAMES_KEY)) + .thenAccept(v -> assertEquals(2, v.size())) + ); + + await( + multimapCache.remove(o -> o.getName().contains("Ka")) + .thenCompose(r1 -> multimapCache.get(NAMES_KEY)) + .thenAccept(v -> + assertEquals(2, v.size()) + + ) + ); + + await( + multimapCache.remove(o -> o.getName().contains("Ju")) + .thenCompose(r1 -> multimapCache.get(NAMES_KEY)) + .thenAccept(v -> + assertEquals(1, v.size()) + ) + ); + + await( + multimapCache.remove(o -> o.getName().contains("Oi")) + .thenCompose(r1 -> multimapCache.get(NAMES_KEY)) + .thenAccept(v -> + assertTrue(v.isEmpty()) + ) + ); + } + + public void testGetAndModifyResults() { + User pepe = new User("Pepe", 17); + + await( + multimapCache.put(NAMES_KEY, JULIEN) + .thenCompose(r1 -> multimapCache.put(NAMES_KEY, OIHANA)) + .thenCompose(r2 -> multimapCache.put(NAMES_KEY, RAMON)) + .thenCompose(r3 -> multimapCache.get(NAMES_KEY)) + .thenAccept(v -> { + assertEquals(3, v.size()); + List modifiedList = new ArrayList<>(v); + modifiedList.add(pepe); + assertEquals(3, v.size()); + assertEquals(4, modifiedList.size()); + } + ) + + ); + + await( + multimapCache.get(NAMES_KEY).thenAccept(v -> { + assertFalse(v.contains(pepe)); + assertEquals(3, v.size()); + } + ) + ); + } + + public void testSize() { + String anotherKey = "firstNames"; + + await( + multimapCache.put(NAMES_KEY, OIHANA) + .thenCompose(r1 -> multimapCache.put(NAMES_KEY, JULIEN)) + .thenCompose(r2 -> multimapCache.put(anotherKey, OIHANA)) + .thenCompose(r3 -> multimapCache.put(anotherKey, JULIEN)) + .thenCompose(r4 -> multimapCache.size()) + .thenAccept(s -> assertEquals(4, s.intValue())) + ); + } + + public void testContainsKey() { + await( + multimapCache.containsKey(NAMES_KEY) + .thenAccept(containsKey -> assertFalse(containsKey)) + ); + + await( + multimapCache.put(NAMES_KEY, OIHANA) + .thenCompose(r -> multimapCache.containsKey(NAMES_KEY)) + .thenAccept(containsKey -> assertTrue(containsKey)) + ); + } + + public void testContainsValue() { + await( + multimapCache.containsValue(OIHANA) + .thenAccept(containsValue -> assertFalse(containsValue)) + ); + + putValuesOnMultimapCache(multimapCache, NAMES_KEY, OIHANA, JULIEN, RAMON, KOLDO); + + await( + multimapCache.containsValue(RAMON) + .thenAccept(containsValue -> assertTrue(containsValue)) + ); + } + + public void testContainEntry() { + await( + multimapCache.containsEntry(NAMES_KEY, OIHANA) + .thenAccept(containsEntry -> assertFalse(containsEntry)) + ); + + await( + multimapCache.put(NAMES_KEY, OIHANA) + .thenCompose(r -> multimapCache.containsEntry(NAMES_KEY, OIHANA)) + .thenAccept(containsEntry -> assertTrue(containsEntry)) + ); + + await( + multimapCache.put(NAMES_KEY, OIHANA) + .thenCompose(r -> multimapCache.containsEntry(NAMES_KEY, JULIEN)) + .thenAccept(containsEntry -> assertFalse(containsEntry)) + ); + } + + public void testPutWithNull() { + expectException(NullPointerException.class, "key can't be null", () -> multimapCache.put(null, OIHANA)); + expectException(NullPointerException.class, "value can't be null", () -> multimapCache.put(NAMES_KEY, null)); + } + + public void testGetWithNull() { + expectException(NullPointerException.class, "key can't be null", () -> multimapCache.get(null)); + } + + public void testRemoveKeyValueWithNull() { + expectException(NullPointerException.class, "key can't be null", () -> multimapCache.remove(null, RAMON)); + expectException(NullPointerException.class, "value can't be null", () -> multimapCache.remove(NAMES_KEY, null)); + } + + public void testRemoveKeyWithNulll() { + expectException(NullPointerException.class, "key can't be null", () -> multimapCache.remove((String) null)); + } + + public void testRemoveWithNullPredicate() { + expectException(NullPointerException.class, "predicate can't be null", () -> multimapCache.remove((Predicate) null)); + } + + public void testContainsKeyWithNull() { + expectException(NullPointerException.class, "key can't be null", () -> multimapCache.containsKey(null)); + } + + public void testContainsValueWithNull() { + expectException(NullPointerException.class, "value can't be null", () -> multimapCache.containsValue(null)); + } + + public void testContainsEntryWithNull() { + expectException(NullPointerException.class, "key can't be null", () -> multimapCache.containsEntry(null, OIHANA)); + expectException(NullPointerException.class, "value can't be null", () -> multimapCache.containsEntry(NAMES_KEY, null)); + } +} diff --git a/multimap/src/test/java/org/infinispan/multimap/impl/MultimapTestUtils.java b/multimap/src/test/java/org/infinispan/multimap/impl/MultimapTestUtils.java new file mode 100644 index 000000000000..e214fa920654 --- /dev/null +++ b/multimap/src/test/java/org/infinispan/multimap/impl/MultimapTestUtils.java @@ -0,0 +1,61 @@ +package org.infinispan.multimap.impl; + +import static java.lang.String.format; +import static org.infinispan.functional.FunctionalTestUtils.await; +import static org.infinispan.test.TestingUtil.extractComponent; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertTrue; + +import java.util.Map; + +import javax.transaction.TransactionManager; + +import org.infinispan.multimap.api.MultimapCache; +import org.infinispan.remoting.transport.Address; + +public class MultimapTestUtils { + public static final String NAMES_KEY = "names"; + public static final User JULIEN = new User("Julien", 33); + public static final User OIHANA = new User("Oihana", 1); + public static final User RAMON = new User("Ramon", 38); + public static final User KOLDO = new User("Koldo", 0); + + + public static TransactionManager getTransactionManager(MultimapCache multimapCache) { + EmbeddedMultimapCache embeddedMultimapCache = (EmbeddedMultimapCache) multimapCache; + return embeddedMultimapCache == null ? null : extractComponent(embeddedMultimapCache.getCache(), TransactionManager.class); + } + + public static void putValuesOnMultimapCache(MultimapCache multimapCache, String key, User... values) { + for (int i = 0; i < values.length; i++) { + await(multimapCache.put(key, values[i])); + } + } + + public static void putValuesOnMultimapCache(Map> cluster, String key, User... values) { + for (MultimapCache mc : cluster.values()) { + putValuesOnMultimapCache(mc, key, values); + } + } + + public static void assertMultimapCacheSize(MultimapCache multimapCache, int expectedSize) { + assertEquals(expectedSize, await(multimapCache.size()).intValue()); + } + + public static void assertMultimapCacheSize(Map> cluster, int expectedSize) { + for (MultimapCache mc : cluster.values()) { + assertMultimapCacheSize(mc, expectedSize); + } + } + + public static void assertContaisKeyValue(MultimapCache multimapCache, String key, User value) { + Address address = ((EmbeddedMultimapCache) multimapCache).getCache().getCacheManager().getAddress(); + await(multimapCache.get(key).thenAccept(v -> { + assertTrue(format("get method call : multimap '%s' must contain key '%s' value '%s' pair", address, key, value), v.contains(value)); + })); + await(multimapCache.containsEntry(key, value).thenAccept(v -> { + assertTrue(format("containsEntry method call : multimap '%s' must contain key '%s' value '%s' pair", address, key, value), v); + })); + } + +} diff --git a/multimap/src/test/java/org/infinispan/multimap/impl/TxDistributedMultimapCacheTest.java b/multimap/src/test/java/org/infinispan/multimap/impl/TxDistributedMultimapCacheTest.java new file mode 100644 index 000000000000..294585ad675e --- /dev/null +++ b/multimap/src/test/java/org/infinispan/multimap/impl/TxDistributedMultimapCacheTest.java @@ -0,0 +1,115 @@ +package org.infinispan.multimap.impl; + +import static java.util.Arrays.asList; +import static org.infinispan.multimap.impl.MultimapTestUtils.JULIEN; +import static org.infinispan.multimap.impl.MultimapTestUtils.NAMES_KEY; +import static org.infinispan.multimap.impl.MultimapTestUtils.assertContaisKeyValue; +import static org.infinispan.multimap.impl.MultimapTestUtils.assertMultimapCacheSize; +import static org.infinispan.multimap.impl.MultimapTestUtils.putValuesOnMultimapCache; +import static org.infinispan.transaction.LockingMode.OPTIMISTIC; +import static org.infinispan.transaction.LockingMode.PESSIMISTIC; +import static org.testng.AssertJUnit.fail; + +import java.util.ArrayList; +import java.util.List; + +import javax.transaction.NotSupportedException; +import javax.transaction.SystemException; +import javax.transaction.TransactionManager; + +import org.infinispan.configuration.cache.CacheMode; +import org.infinispan.multimap.api.MultimapCache; +import org.infinispan.transaction.LockingMode; +import org.infinispan.util.concurrent.IsolationLevel; +import org.testng.annotations.Test; + +@Test(groups = "functional", testName = "distribution.DistributedMultimapCacheTest") +public class TxDistributedMultimapCacheTest extends DistributedMultimapCacheTest { + + public TxDistributedMultimapCacheTest() { + transactional = true; + cleanup = CleanupPhase.AFTER_TEST; + cacheMode = CacheMode.DIST_SYNC; + } + + @Override + protected String[] parameterNames() { + return concat(super.parameterNames(), "fromOwner"); + } + + @Override + protected Object[] parameterValues() { + return concat(super.parameterValues(), fromOwner ? Boolean.TRUE : Boolean.FALSE); + } + + @Override + public Object[] factory() { + List testsToRun = new ArrayList(); + testsToRun.addAll(txTests(OPTIMISTIC)); + testsToRun.addAll(txTests(PESSIMISTIC)); + return testsToRun.toArray(); + } + + public void testExplicitTx() throws SystemException, NotSupportedException { + initAndTest(); + MultimapCache multimapCache = getMultimapCacheMember(NAMES_KEY); + + TransactionManager tm1 = MultimapTestUtils.getTransactionManager(multimapCache); + assertMultimapCacheSize(multimapCache, 1); + + tm1.begin(); + + try { + putValuesOnMultimapCache(multimapCache, NAMES_KEY, JULIEN); + if (fromOwner) { + assertContaisKeyValue(multimapCache, NAMES_KEY, JULIEN); + } + tm1.commit(); + } catch (Exception e) { + fail(e.getMessage()); + } + assertValuesAndOwnership(NAMES_KEY, JULIEN); + assertMultimapCacheSize(multimapCache, 2); + } + + public void testExplicitTxWithRollback() throws SystemException, NotSupportedException { + initAndTest(); + MultimapCache multimapCache = getMultimapCacheMember(NAMES_KEY); + + TransactionManager tm1 = MultimapTestUtils.getTransactionManager(multimapCache); + assertMultimapCacheSize(multimapCache, 1); + + tm1.begin(); + try { + putValuesOnMultimapCache(multimapCache, NAMES_KEY, JULIEN); + if (fromOwner) { + assertContaisKeyValue(multimapCache, NAMES_KEY, JULIEN); + } + } finally { + tm1.rollback(); + } + assertKeyValueNotFoundInAllCaches(NAMES_KEY, JULIEN); + assertMultimapCacheSize(multimapCache, 1); + } + + private List txTests(LockingMode lockingMode) { + return asList( + new TxDistributedMultimapCacheTest() + .fromOwner(false) + .lockingMode(lockingMode) + .isolationLevel(IsolationLevel.READ_COMMITTED), + new TxDistributedMultimapCacheTest() + .fromOwner(true) + .lockingMode(lockingMode) + .isolationLevel(IsolationLevel.READ_COMMITTED), + new TxDistributedMultimapCacheTest() + .fromOwner(false) + .lockingMode(lockingMode). + isolationLevel(IsolationLevel.REPEATABLE_READ), + new TxDistributedMultimapCacheTest() + .fromOwner(true) + .lockingMode(lockingMode). + isolationLevel(IsolationLevel.REPEATABLE_READ) + ); + } +} diff --git a/multimap/src/test/java/org/infinispan/multimap/impl/TxEmbeddedMultimapCacheTest.java b/multimap/src/test/java/org/infinispan/multimap/impl/TxEmbeddedMultimapCacheTest.java new file mode 100644 index 000000000000..e1586c4132bf --- /dev/null +++ b/multimap/src/test/java/org/infinispan/multimap/impl/TxEmbeddedMultimapCacheTest.java @@ -0,0 +1,192 @@ +package org.infinispan.multimap.impl; + +import static org.infinispan.functional.FunctionalTestUtils.await; +import static org.infinispan.multimap.impl.MultimapTestUtils.assertMultimapCacheSize; +import static org.infinispan.multimap.impl.MultimapTestUtils.getTransactionManager; +import static org.infinispan.multimap.impl.MultimapTestUtils.putValuesOnMultimapCache; +import static org.infinispan.multimap.impl.MultimapTestUtils.NAMES_KEY; +import static org.infinispan.multimap.impl.MultimapTestUtils.OIHANA; +import static org.infinispan.multimap.impl.MultimapTestUtils.KOLDO; +import static org.infinispan.multimap.impl.MultimapTestUtils.JULIEN; +import static org.infinispan.multimap.impl.MultimapTestUtils.RAMON; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertTrue; + +import javax.transaction.NotSupportedException; +import javax.transaction.SystemException; +import javax.transaction.TransactionManager; + +import org.infinispan.configuration.cache.ConfigurationBuilder; +import org.infinispan.manager.EmbeddedCacheManager; +import org.infinispan.multimap.api.EmbeddedMultimapCacheManagerFactory; +import org.infinispan.test.fwk.TestCacheManagerFactory; +import org.infinispan.util.concurrent.IsolationLevel; +import org.infinispan.util.function.SerializablePredicate; +import org.testng.annotations.Test; + +/** + * Multimap Cache with transactions in single cache + * + * @author Katia Aresti, karesti@redhat.com + * @since 9.2 + */ +@Test(groups = "functional", testName = "multimap.TxEmbeddedMultimapCacheTest") +public class TxEmbeddedMultimapCacheTest extends EmbeddedMultimapCacheTest { + + @Override + protected EmbeddedCacheManager createCacheManager() throws Exception { + // start a single multimapCache instance + ConfigurationBuilder c = getDefaultStandaloneCacheConfig(true); + c.locking().isolationLevel(IsolationLevel.READ_COMMITTED); + EmbeddedCacheManager cm = TestCacheManagerFactory.createCacheManager(false); + multimapCache = EmbeddedMultimapCacheManagerFactory.get("test", cm, c.build()); + return cm; + } + + public void testSizeInExplicitTx() throws SystemException, NotSupportedException { + assertMultimapCacheSize(multimapCache, 0); + + TransactionManager tm1 = getTransactionManager(multimapCache); + tm1.begin(); + try { + putValuesOnMultimapCache(multimapCache, NAMES_KEY, JULIEN, OIHANA); + assertMultimapCacheSize(multimapCache, 2); + } finally { + tm1.rollback(); + } + + assertMultimapCacheSize(multimapCache, 0); + } + + public void testSizeInExplicitTxWithRemoveNonExistentAndPut() throws SystemException, NotSupportedException { + assertMultimapCacheSize(multimapCache, 0); + putValuesOnMultimapCache(multimapCache, NAMES_KEY, JULIEN); + assertMultimapCacheSize(multimapCache, 1); + + TransactionManager tm1 = getTransactionManager(multimapCache); + tm1.begin(); + try { + await(multimapCache.remove("firstnames")); + assertMultimapCacheSize(multimapCache, 1); + putValuesOnMultimapCache(multimapCache, "firstnames", JULIEN, OIHANA, RAMON); + assertMultimapCacheSize(multimapCache, 4); + } finally { + tm1.rollback(); + } + assertMultimapCacheSize(multimapCache, 1); + } + + public void testSizeInExplicitTxWithRemoveKeyValue() throws SystemException, NotSupportedException { + assertMultimapCacheSize(multimapCache, 0); + putValuesOnMultimapCache(multimapCache, NAMES_KEY, JULIEN, OIHANA); + assertMultimapCacheSize(multimapCache, 2); + + TransactionManager tm1 = getTransactionManager(multimapCache); + tm1.begin(); + try { + await(multimapCache.remove(MultimapTestUtils.NAMES_KEY, JULIEN)); + assertMultimapCacheSize(multimapCache, 1); + } finally { + tm1.rollback(); + } + assertMultimapCacheSize(multimapCache, 2); + } + + public void testSizeInExplicitTxWithRemoveExistent() throws SystemException, NotSupportedException { + assertMultimapCacheSize(multimapCache, 0); + putValuesOnMultimapCache(multimapCache, NAMES_KEY, JULIEN); + assertMultimapCacheSize(multimapCache, 1); + + TransactionManager tm1 = getTransactionManager(multimapCache); + tm1.begin(); + try { + putValuesOnMultimapCache(multimapCache, NAMES_KEY, OIHANA); + assertMultimapCacheSize(multimapCache, 2); + await(multimapCache.remove(MultimapTestUtils.NAMES_KEY)); + assertTrue(await(multimapCache.get(MultimapTestUtils.NAMES_KEY)).isEmpty()); + } finally { + tm1.rollback(); + } + assertMultimapCacheSize(multimapCache, 1); + } + + public void testSizeInExplicitTxWithRemoveWithPredicate() throws SystemException, NotSupportedException { + assertMultimapCacheSize(multimapCache, 0); + putValuesOnMultimapCache(multimapCache, NAMES_KEY, JULIEN, OIHANA); + assertMultimapCacheSize(multimapCache, 2); + + TransactionManager tm1 = getTransactionManager(multimapCache); + tm1.begin(); + try { + await(multimapCache.remove(v -> v.getName().contains("Ju")) + .thenCompose(r1 -> multimapCache.get(NAMES_KEY)) + .thenAccept(values -> { + assertTrue(values.contains(OIHANA)); + assertFalse(values.contains(JULIEN)); + } + )); + assertMultimapCacheSize(multimapCache, 1); + } finally { + tm1.rollback(); + } + + assertMultimapCacheSize(multimapCache, 2); + } + + public void testSizeInExplicitTxWithRemoveAllWithPredicate() throws SystemException, NotSupportedException { + assertMultimapCacheSize(multimapCache, 0); + putValuesOnMultimapCache(multimapCache, NAMES_KEY, JULIEN, OIHANA, KOLDO); + assertMultimapCacheSize(multimapCache, 3); + TransactionManager tm1 = getTransactionManager(multimapCache); + tm1.begin(); + try { + SerializablePredicate removePredicate = v -> v.getName().contains("ih") || v.getName().contains("ol"); + multimapCache.remove(removePredicate).thenAccept(r -> { + assertMultimapCacheSize(multimapCache, 1); + }).join(); + } finally { + tm1.rollback(); + } + assertMultimapCacheSize(multimapCache, 3); + } + + public void testSizeInExplicitTxWithModification() throws SystemException, NotSupportedException { + assertMultimapCacheSize(multimapCache, 0); + putValuesOnMultimapCache(multimapCache, NAMES_KEY, OIHANA); + assertMultimapCacheSize(multimapCache, 1); + + TransactionManager tm1 = getTransactionManager(multimapCache); + tm1.begin(); + try { + putValuesOnMultimapCache(multimapCache, NAMES_KEY, JULIEN); + putValuesOnMultimapCache(multimapCache, "morenames", RAMON); + assertMultimapCacheSize(multimapCache, 3); + } finally { + tm1.rollback(); + } + + assertMultimapCacheSize(multimapCache, 1); + } + + public void testContainsMethodsInExplicitTxWithModification() throws SystemException, NotSupportedException { + TransactionManager tm1 = getTransactionManager(multimapCache); + tm1.begin(); + + await(multimapCache.containsKey(NAMES_KEY).thenAccept(c -> assertFalse(c))); + await(multimapCache.containsValue(JULIEN).thenAccept(c -> assertFalse(c))); + await(multimapCache.containsEntry(NAMES_KEY, JULIEN).thenAccept(c -> assertFalse(c))); + + try { + putValuesOnMultimapCache(multimapCache, NAMES_KEY, JULIEN); + await(multimapCache.containsKey(NAMES_KEY).thenAccept(c -> assertTrue(c))); + await(multimapCache.containsValue(JULIEN).thenAccept(c -> assertTrue(c))); + await(multimapCache.containsEntry(NAMES_KEY, JULIEN).thenAccept(c -> assertTrue(c))); + } finally { + tm1.rollback(); + } + + await(multimapCache.containsKey(NAMES_KEY).thenAccept(c -> assertFalse(c))); + await(multimapCache.containsValue(JULIEN).thenAccept(c -> assertFalse(c))); + await(multimapCache.containsEntry(NAMES_KEY, JULIEN).thenAccept(c -> assertFalse(c))); + } +} diff --git a/multimap/src/test/java/org/infinispan/multimap/impl/User.java b/multimap/src/test/java/org/infinispan/multimap/impl/User.java new file mode 100644 index 000000000000..c7cb63e9a7ed --- /dev/null +++ b/multimap/src/test/java/org/infinispan/multimap/impl/User.java @@ -0,0 +1,70 @@ +package org.infinispan.multimap.impl; + +import static java.util.Objects.hash; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.Serializable; + +import org.infinispan.commons.marshall.Externalizer; +import org.infinispan.commons.marshall.SerializeWith; + +@SerializeWith(User.UserExternalizer.class) +public class User implements Serializable { + private final String name; + private final int age; + + public User(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + User user = (User) o; + + if (age != user.age) return false; + return name != null ? name.equals(user.name) : user.name == null; + } + + @Override + public int hashCode() { + return hash(name, age); + } + + @Override + public String toString() { + return "User{" + + "name='" + name + '\'' + + ", age=" + age + + '}'; + } + + public static class UserExternalizer implements Externalizer { + + @Override + public void writeObject(ObjectOutput output, User object) throws IOException { + output.writeObject(object.name); + output.writeInt(object.age); + } + + @Override + public User readObject(ObjectInput input) throws IOException, ClassNotFoundException { + String name = (String) input.readObject(); + int age = input.readInt(); + return new User(name, age); + } + } +} diff --git a/persistence/jpa/src/main/java/org/infinispan/persistence/jpa/impl/EntityManagerFactoryRegistry.java b/persistence/jpa/src/main/java/org/infinispan/persistence/jpa/impl/EntityManagerFactoryRegistry.java index b12340011d7d..1252a28efbaa 100644 --- a/persistence/jpa/src/main/java/org/infinispan/persistence/jpa/impl/EntityManagerFactoryRegistry.java +++ b/persistence/jpa/src/main/java/org/infinispan/persistence/jpa/impl/EntityManagerFactoryRegistry.java @@ -10,6 +10,8 @@ import javax.persistence.spi.PersistenceProvider; import org.infinispan.commons.util.Util; +import org.infinispan.factories.scopes.Scope; +import org.infinispan.factories.scopes.Scopes; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.osgi.framework.Bundle; diff --git a/pom.xml b/pom.xml index 0863a7a2772e..5437d0b6e1a3 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,7 @@ cloud extended-statistics counter + multimap tools query query-dsl