package org.infinispan.filter; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; import org.infinispan.commons.marshall.AdvancedExternalizer; import org.infinispan.commons.util.Util; import org.infinispan.container.InternalEntryFactory; import org.infinispan.container.entries.CacheEntry; import org.infinispan.factories.ComponentRegistry; import org.infinispan.factories.annotations.Inject; import org.infinispan.marshall.core.Ids; import org.infinispan.metadata.Metadata; import org.infinispan.stream.StreamMarshalling; import org.jboss.marshalling.util.IdentityIntMap; /** * Static factory class that contains utility methods that can be used for performing proper transformations from * {@link KeyValueFilter}, {@link Converter} & {@link KeyValueFilterConverter} to appropriate distributed stream * instances. */ public final class CacheFilters { private CacheFilters() { } /** * Creates a new {@link Predicate} using the provided key value filter as a basis for the operation. This is useful * for when using {@link Stream#filter(Predicate)} method on distributed streams. The key, * value and metadata are all used to determine if the predicate returns true or not. * @param filter the filter to utilize * @param <K> key type * @param <V> value type * @return predicate based on the filter */ public static <K, V> Predicate<CacheEntry<K, V>> predicate(KeyValueFilter<? super K, ? super V> filter) { return new KeyValueFilterAsPredicate<>(filter); } /** * Creates a new {@link Function} using the provided converter as a basis for the operation. This is useful * for when using {@link Stream#map(Function)} method on distributed streams. The key, * value and metadata are all used to determine the converted value. * @param converter the converter to utilize * @param <K> key type * @param <V> value type * @param <C> convertered value type * @return function based on the converter */ public static <K, V, C> Function<CacheEntry<K, V>, CacheEntry<K, C>> function( Converter<? super K, ? super V, C> converter) { return new ConverterAsCacheEntryFunction<>(converter); } /** * Adds needed intermediate operations to the provided stream, returning a possibly new stream as a result of the * operations. This method keeps the contract of filter and conversion being performed in only 1 call as the * {@link KeyValueFilterConverter} was designed to do. The key, * value and metadata are all used to determine whether the value is returned and the converted value. * @param stream stream to perform the operations on * @param filterConverter converter to apply * @param <K> * @param <V> * @param <C> * @return */ public static <K, V, C> Stream<CacheEntry<K, C>> filterAndConvert(Stream<CacheEntry<K, V>> stream, KeyValueFilterConverter<? super K, ? super V, C> filterConverter) { return stream.map(new FilterConverterAsCacheEntryFunction(filterConverter)).filter( StreamMarshalling.nonNullPredicate()); } private static class KeyValueFilterAsPredicate<K, V> implements Predicate<CacheEntry<K, V>> { private final KeyValueFilter<? super K, ? super V> filter; public KeyValueFilterAsPredicate(KeyValueFilter<? super K, ? super V> filter) { Objects.nonNull(filter); this.filter = filter; } @Override public boolean test(CacheEntry<K, V> kvCacheEntry) { return filter.accept(kvCacheEntry.getKey(), kvCacheEntry.getValue(), kvCacheEntry.getMetadata()); } @Inject public void inject(ComponentRegistry registry) { registry.wireDependencies(filter); } } private static class ConverterAsCacheEntryFunction<K, V, C> implements Function<CacheEntry<K, V>, CacheEntry<K, C>> { protected final Converter<? super K, ? super V, C> converter; protected InternalEntryFactory factory; public ConverterAsCacheEntryFunction(Converter<? super K, ? super V, C> converter) { Objects.nonNull(converter); this.converter = converter; } @Inject public void inject(InternalEntryFactory factory, ComponentRegistry registry) { this.factory = factory; registry.wireDependencies(converter); } @Override public CacheEntry<K, C> apply(CacheEntry<K, V> kvCacheEntry) { K key = kvCacheEntry.getKey(); V value = kvCacheEntry.getValue(); Metadata metadata = kvCacheEntry.getMetadata(); C converted = converter.convert(key, value, metadata); if (value == converted) { return (CacheEntry<K, C>) kvCacheEntry; } return factory.create(key, converted, metadata); } } private static class FilterConverterAsCacheEntryFunction<K, V, C> implements Function<CacheEntry<K, V>, CacheEntry<K, C>> { protected final KeyValueFilterConverter<? super K, ? super V, C> converter; protected InternalEntryFactory factory; public FilterConverterAsCacheEntryFunction(KeyValueFilterConverter<? super K, ? super V, C> converter) { Objects.nonNull(converter); this.converter = converter; } @Inject public void inject(InternalEntryFactory factory, ComponentRegistry registry) { this.factory = factory; registry.wireDependencies(converter); } @Override public CacheEntry<K, C> apply(CacheEntry<K, V> kvCacheEntry) { K key = kvCacheEntry.getKey(); V value = kvCacheEntry.getValue(); Metadata metadata = kvCacheEntry.getMetadata(); C converted = converter.filterAndConvert(key, value, metadata); if (converted == null) { return null; } return factory.create(key, converted, metadata); } } public static final class CacheFiltersExternalizer implements AdvancedExternalizer<Object> { private static final int KEY_VALUE_FILTER_PREDICATE = 0; private static final int CONVERTER_FUNCTION = 1; private static final int FILTER_CONVERTER_FUNCTION = 2; private final IdentityIntMap<Class<?>> objects = new IdentityIntMap<>(); public CacheFiltersExternalizer() { objects.put(KeyValueFilterAsPredicate.class, KEY_VALUE_FILTER_PREDICATE); objects.put(ConverterAsCacheEntryFunction.class, CONVERTER_FUNCTION); objects.put(FilterConverterAsCacheEntryFunction.class, FILTER_CONVERTER_FUNCTION); } @Override public Set<Class<?>> getTypeClasses() { return Util.asSet(KeyValueFilterAsPredicate.class, ConverterAsCacheEntryFunction.class, FilterConverterAsCacheEntryFunction.class); } @Override public Integer getId() { return Ids.CACHE_FILTERS; } @Override public void writeObject(ObjectOutput output, Object object) throws IOException { int number = objects.get(object.getClass(), -1); output.writeByte(number); switch (number) { case KEY_VALUE_FILTER_PREDICATE: output.writeObject(((KeyValueFilterAsPredicate) object).filter); break; case CONVERTER_FUNCTION: output.writeObject(((ConverterAsCacheEntryFunction) object).converter); break; case FILTER_CONVERTER_FUNCTION: output.writeObject(((FilterConverterAsCacheEntryFunction) object).converter); break; } } @Override public Object readObject(ObjectInput input) throws IOException, ClassNotFoundException { int number = input.readUnsignedByte(); switch (number) { case KEY_VALUE_FILTER_PREDICATE: return new KeyValueFilterAsPredicate((KeyValueFilter) input.readObject()); case CONVERTER_FUNCTION: return new ConverterAsCacheEntryFunction((Converter) input.readObject()); case FILTER_CONVERTER_FUNCTION: return new FilterConverterAsCacheEntryFunction((KeyValueFilterConverter) input.readObject()); default: throw new IllegalArgumentException("Found invalid number " + number); } } } }