package org.infinispan.server.hotrod.iteration; import static org.infinispan.filter.CacheFilters.filterAndConvert; import java.io.IOException; import java.util.ArrayList; import java.util.BitSet; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; import org.infinispan.BaseCacheStream; import org.infinispan.CacheStream; import org.infinispan.commons.CacheException; import org.infinispan.commons.logging.LogFactory; import org.infinispan.commons.marshall.Marshaller; import org.infinispan.commons.util.CollectionFactory; import org.infinispan.configuration.cache.CompatibilityModeConfiguration; import org.infinispan.container.entries.CacheEntry; import org.infinispan.filter.KeyValueFilterConverter; import org.infinispan.filter.KeyValueFilterConverterFactory; import org.infinispan.filter.ParamKeyValueFilterConverterFactory; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.server.hotrod.HotRodTypeConverter; import org.infinispan.server.hotrod.OperationStatus; import org.infinispan.server.hotrod.logging.Log; import org.infinispan.util.KeyValuePair; /** * @author gustavonalle * @since 8.0 */ class IterationSegmentsListener implements BaseCacheStream.SegmentCompletionListener { private Set<Integer> finished = new HashSet<>(); private Set<Integer> justFinished = new HashSet<>(); Set<Integer> getFinished(boolean endOfIteration) { synchronized (this) { if (endOfIteration) { Set<Integer> segments = new HashSet<>(finished); segments.addAll(justFinished); return segments; } else { Set<Integer> diff = new HashSet<>(finished); diff.removeAll(justFinished); finished.clear(); finished.addAll(justFinished); return diff; } } } @Override public void segmentCompleted(Set<Integer> segments) { if (!segments.isEmpty()) { synchronized (this) { justFinished = segments; finished.addAll(segments); } } } } class IterationState { final IterationSegmentsListener listener; final Iterator<CacheEntry<Object, Object>> iterator; final CacheStream<CacheEntry<Object, Object>> stream; final int batch; final CompatInfo compatInfo; final boolean metadata; IterationState(IterationSegmentsListener listener, Iterator<CacheEntry<Object, Object>> iterator, CacheStream<CacheEntry<Object, Object>> stream, int batch, CompatInfo compatInfo, boolean metadata) { this.listener = listener; this.iterator = iterator; this.stream = stream; this.batch = batch; this.compatInfo = compatInfo; this.metadata = metadata; } } class CompatInfo { final boolean enabled; final Optional<HotRodTypeConverter> hotRodTypeConverter; CompatInfo(boolean enabled, Optional<HotRodTypeConverter> hotRodTypeConverter) { this.enabled = enabled; this.hotRodTypeConverter = hotRodTypeConverter; } static CompatInfo create(CompatibilityModeConfiguration config) { return new CompatInfo(config.enabled(), config.enabled() ? Optional.of(new HotRodTypeConverter(config.marshaller())) : Optional.empty()); } } public class DefaultIterationManager implements IterationManager { private final EmbeddedCacheManager cacheManager; volatile Optional<Marshaller> marshaller = Optional.empty(); static final Log log = LogFactory.getLog(DefaultIterationManager.class, Log.class); private final Map<String, IterationState> iterationStateMap = CollectionFactory.makeConcurrentMap(); private final Map<String, KeyValueFilterConverterFactory> filterConverterFactoryMap = CollectionFactory.makeConcurrentMap(); public DefaultIterationManager(EmbeddedCacheManager cacheManager) { this.cacheManager = cacheManager; } @Override public String start(String cacheName, Optional<BitSet> segments, Optional<KeyValuePair<String, List<byte[]>>> namedFactory, int batch, boolean metadata) { String iterationId = UUID.randomUUID().toString(); CacheStream<CacheEntry<Object, Object>> stream = cacheManager.getCache(cacheName).getAdvancedCache().cacheEntrySet().stream(); segments.map(bitSet -> stream.filterKeySegments(bitSet.stream().boxed().collect(Collectors.toSet()))); IterationSegmentsListener segmentListener = new IterationSegmentsListener(); CompatInfo compatInfo = CompatInfo.create(cacheManager.getCache(cacheName).getCacheConfiguration().compatibility()); Stream<CacheEntry<Object, Object>> filteredStream; if (namedFactory.isPresent()) { KeyValueFilterConverterFactory factory = getFactory(namedFactory.get().getKey()); List<byte[]> params = namedFactory.get().getValue(); KeyValuePair<KeyValueFilterConverter, Boolean> filter = buildFilter(factory, params.toArray(new byte[params.size()][])); IterationFilter iterationFilter = new IterationFilter(compatInfo.enabled, Optional.of(filter.getKey()), marshaller, filter.getValue()); filteredStream = filterAndConvert(stream.segmentCompletionListener(segmentListener), iterationFilter); } else { filteredStream = stream.segmentCompletionListener(segmentListener); } IterationState iterationState = new IterationState(segmentListener, filteredStream.iterator(), stream, batch, compatInfo, metadata); iterationStateMap.put(iterationId, iterationState); return iterationId; } private KeyValueFilterConverterFactory getFactory(String name) { KeyValueFilterConverterFactory factory = filterConverterFactoryMap.get(name); if (factory == null) { throw log.missingKeyValueFilterConverterFactory(name); } return factory; } private KeyValuePair<KeyValueFilterConverter, Boolean> buildFilter(KeyValueFilterConverterFactory factory, byte[][] params) { if (factory instanceof ParamKeyValueFilterConverterFactory) { ParamKeyValueFilterConverterFactory paramFactory = (ParamKeyValueFilterConverterFactory) factory; Object[] unmarshallParams; if (paramFactory.binaryParam()) { unmarshallParams = params; } else { unmarshallParams = unmarshallParams(params, factory); } return new KeyValuePair<>(paramFactory.getFilterConverter(unmarshallParams), paramFactory.binaryParam()); } else { return new KeyValuePair<>(factory.getFilterConverter(), false); } } private Object[] unmarshallParams(byte[][] params, Object factory) { Marshaller m = marshaller.orElse(MarshallerBuilder.genericFromInstance(Optional.of(factory))); try { Object[] objectParams = new Object[params.length]; int i = 0; for (byte[] param : params) { objectParams[i++] = m.objectFromByteBuffer(param); } return objectParams; } catch (IOException | ClassNotFoundException e) { throw new CacheException(e); } } @Override public IterableIterationResult next(String cacheName, String iterationId) { IterationState iterationState = iterationStateMap.get(iterationId); if (iterationState != null) { int i = 0; List<CacheEntry> entries = new ArrayList<>(iterationState.batch); while (i++ < iterationState.batch && iterationState.iterator.hasNext()) { entries.add(iterationState.iterator.next()); } return new IterableIterationResult(iterationState.listener.getFinished(entries.isEmpty()), OperationStatus.Success, entries, iterationState.compatInfo, iterationState.metadata); } else { return new IterableIterationResult(Collections.emptySet(), OperationStatus.InvalidIteration, Collections.emptyList(), null, false); } } @Override public boolean close(String cacheName, String iterationId) { IterationState iterationState = iterationStateMap.get(iterationId); if (iterationState != null) { iterationState.stream.close(); iterationStateMap.remove(iterationId); return true; } return false; } @Override public void addKeyValueFilterConverterFactory(String name, KeyValueFilterConverterFactory factory) { filterConverterFactoryMap.put(name, factory); } @Override public void removeKeyValueFilterConverterFactory(String name) { filterConverterFactoryMap.remove(name); } @Override public int activeIterations() { return iterationStateMap.size(); } @Override public void setMarshaller(Optional<Marshaller> marshaller) { this.marshaller = marshaller; } }