package net.openhft.chronicle.engine.queue; import net.openhft.chronicle.core.Jvm; import net.openhft.chronicle.engine.api.column.ChronicleQueueRow; import net.openhft.chronicle.engine.api.column.ClosableIterator; import net.openhft.chronicle.engine.api.column.Column; import net.openhft.chronicle.engine.api.column.QueueColumnView; import net.openhft.chronicle.engine.api.tree.Asset; import net.openhft.chronicle.engine.api.tree.RequestContext; import net.openhft.chronicle.engine.map.ObjectSubscription; import net.openhft.chronicle.engine.map.VanillaMapView; import net.openhft.chronicle.engine.tree.QueueView; import net.openhft.chronicle.queue.ExcerptTailer; import net.openhft.chronicle.queue.impl.single.SingleChronicleQueue; import net.openhft.chronicle.wire.DocumentContext; import net.openhft.chronicle.wire.FieldInfo; import net.openhft.chronicle.wire.Marshallable; import net.openhft.chronicle.wire.Wires; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.function.Predicate; import java.util.stream.StreamSupport; import static java.util.Spliterators.spliteratorUnknownSize; import static net.openhft.chronicle.core.util.ObjectUtils.convertTo; import static net.openhft.chronicle.queue.TailerDirection.BACKWARD; import static net.openhft.chronicle.wire.Wires.fieldInfos; /** * @author Rob Austin. */ public class QueueWrappingColumnView<K, V> implements QueueColumnView { private final Asset asset; @NotNull private final QueueView<String, V> queueView; @Nullable private ArrayList<String> columnNames = null; private final Class<?> messageClass; public QueueWrappingColumnView( RequestContext requestContext, Asset asset, @NotNull QueueView<String, V> queueView) { this.asset = asset; this.queueView = queueView; @Nullable final QueueView.Excerpt<String, V> excerpt = queueView.getExcerpt(0); if (excerpt != null) messageClass = excerpt.message().getClass(); else messageClass = Object.class; } @Override public void registerChangeListener(@NotNull Runnable r) { queueView.registerSubscriber("", o -> r.run()); } private ClosableIterator<ChronicleQueueRow> iteratorWithCountFromEnd( @NotNull final List<MarshableFilter> filters, long countFromEnd) { final long index = toIndexFromEnd(countFromEnd); return toIterator(filters, index); } @NotNull @Override public ClosableIterator<ChronicleQueueRow> iterator(@NotNull final SortedFilter filters) { long countFromEnd = filters.countFromEnd; if (countFromEnd > 0) return iteratorWithCountFromEnd(filters.marshableFilters, countFromEnd); else return iterator(filters.marshableFilters, filters.fromIndex); } @NotNull Map<List<MarshableFilter>, NavigableMap<Long, ChronicleQueueRow>> indexCache = new ConcurrentHashMap<>(); @NotNull private ClosableIterator<ChronicleQueueRow> iterator(@NotNull final List<MarshableFilter> filters, long fromSequenceNumber) { long count = 0; final NavigableMap<Long, ChronicleQueueRow> map = indexCache.computeIfAbsent(filters, k -> new ConcurrentSkipListMap<>()); final Map.Entry<Long, ChronicleQueueRow> longChronicleQueueRowEntry = map.floorEntry(fromSequenceNumber); final long index0; if (longChronicleQueueRowEntry != null) { ChronicleQueueRow value = longChronicleQueueRowEntry.getValue(); count = value.seqNumber(); index0 = longChronicleQueueRowEntry.getValue().index(); } else index0 = 0; @NotNull final ClosableIterator<ChronicleQueueRow> result = toIterator(filters, index0); @Nullable ChronicleQueueRow r = null; while (count < fromSequenceNumber && result.hasNext()) { r = result.next(); count++; r.seqNumber(count); if (r.seqNumber() % 1024 == 0) map.put(count, r); } if (longChronicleQueueRowEntry == null && r != null) map.put(r.seqNumber(), r); return result; } @NotNull private ClosableIterator<ChronicleQueueRow> toIterator(@NotNull List<MarshableFilter> filters, final long index) { @Nullable final Iterator<QueueView.Excerpt<String, V>> i = new Iterator<QueueView.Excerpt<String, V>>() { @Nullable QueueView.Excerpt<String, V> next = queueView.getExcerpt(index); @Override public boolean hasNext() { if (next == null) next = queueView.getExcerpt(""); return next != null; } @Nullable @Override public QueueView.Excerpt<String, V> next() { if (this.next == null) throw new NoSuchElementException(); try { return this.next; } finally { this.next = null; } } }; final Spliterator<QueueView.Excerpt<String, V>> spliterator = spliteratorUnknownSize(i, Spliterator.DISTINCT | Spliterator.SORTED | Spliterator.ORDERED); final Iterator<QueueView.Excerpt<String, V>> core = StreamSupport.stream(spliterator, false) .filter(filter(filters)) .iterator(); return new ClosableIterator<ChronicleQueueRow>() { @Override public void close() { // do nothing } @Override public boolean hasNext() { return core.hasNext(); } @NotNull @Override public ChronicleQueueRow next() { final QueueView.Excerpt<String, V> e = core.next(); @NotNull final ChronicleQueueRow row = new ChronicleQueueRow(columns()); @NotNull final Object value = e.message(); row.set("index", Long.toHexString(e.index())); row.index(e.index()); for (@NotNull final FieldInfo info : fieldInfos(value.getClass())) { if (!columnNames().contains(info.name())) continue; try { final Object newValue = info.get(value); // System.out.println("\t"+newValue); row.set(info.name(), newValue); } catch (Exception e1) { Jvm.warn().on(VanillaMapView.class, e1); } } return row; } }; } private long toIndexFromEnd(long countFromEnd) { long fromSequenceNumber = -1; if (countFromEnd > 0) { @Nullable final SingleChronicleQueue chronicleQueue = (SingleChronicleQueue) queueView.underlying(); @NotNull final ExcerptTailer excerptTailer = chronicleQueue.createTailer().direction(BACKWARD).toEnd(); fromSequenceNumber = excerptTailer.index(); if (fromSequenceNumber == 0) return 0; for (int i = 0; i < countFromEnd; i++) { try (DocumentContext documentContext = excerptTailer.readingDocument()) { if (!documentContext.isPresent()) break; fromSequenceNumber = documentContext.index(); } } } return fromSequenceNumber; } @Override public boolean containsRowWithKey(@NotNull List keys) { if (keys.size() == 1 && keys.get(0) instanceof String) { final long l = Long.parseLong(keys.get(0).toString(), 16); return queueView.getExcerpt(l) != null; } throw new IllegalStateException("unsupported format, keys=" + keys); } @Nullable @Override public ObjectSubscription objectSubscription() { return queueView.asset().getView(ObjectSubscription.class); } @Override public Asset asset() { return asset; } @NotNull @Override public List<Column> columns() { @NotNull List<Column> result = new ArrayList<>(); result.add(new Column("index", true, true, "", String.class, false)); for (@NotNull final FieldInfo fi : fieldInfos(messageClass)) { result.add(new Column(fi.name(), false, false, "", fi.type(), false)); } return result; } @Nullable private List<String> columnNames() { if (columnNames != null) return columnNames; @NotNull LinkedHashSet<String> result = new LinkedHashSet<>(); result.add("index"); // if (Marshallable.class.isAssignableFrom(messageClass)) { for (@NotNull final FieldInfo fi : fieldInfos(messageClass)) { result.add(fi.name()); } // } columnNames = new ArrayList<>(result); return columnNames; } @Override public boolean canDeleteRows() { return false; } @Override public int changedRow(@NotNull Map<String, Object> row, @NotNull Map<String, Object> oldRow) { // chronicle queue is read only return 0; } @Nullable public Predicate<QueueView.Excerpt<String, V>> filter(@Nullable List<MarshableFilter> filters) { @Nullable final Predicate predicate = predicate(filters); return excerpt -> { if (filters == null || filters.isEmpty()) return true; try { for (@NotNull MarshableFilter f : filters) { Object item; final Class messageClass = excerpt.message().getClass(); if (Marshallable.class.isAssignableFrom(messageClass)) { try { // final Field field = messageClass.getDeclaredField(f.columnName); V message = excerpt.message(); FieldInfo info = Wires.fieldInfo(message.getClass(), f.columnName); final Object o = info.get(message); // field.setAccessible(true); // final Object o = field.get(excerpt.message()); if (o == null) return false; if (o instanceof Number) { if (predicate.test(o)) continue; return false; } item = o; } catch (Exception e) { return false; } } else { throw new UnsupportedOperationException(); } if (item instanceof CharSequence) { if (!item.toString().toLowerCase().contains(f.filter.toLowerCase())) return false; } else if (item instanceof Number) { if (!predicate.test(item)) return false; } else { if (!item.equals(convertTo(item.getClass(), f.filter.trim()))) return false; } } return true; } catch (NumberFormatException e) { return false; } }; } /** * @param filters if {@code sortedFilter} == null or empty all the total number of rows is * returned * @return the number of rows the matches this query */ @Override public int rowCount(@NotNull SortedFilter filters) { long countFromEnd = filters.countFromEnd; if (countFromEnd > 0) { int count0 = 0; @NotNull ClosableIterator<ChronicleQueueRow> iterator = iterator(filters); while (iterator.hasNext()) { iterator.next(); count0++; } return count0; } final NavigableMap<Long, ChronicleQueueRow> map = indexCache.computeIfAbsent(filters.marshableFilters, k -> new ConcurrentSkipListMap<>()); Iterator<ChronicleQueueRow> iterator; long count = 0; if (map != null) { final Map.Entry<Long, ChronicleQueueRow> last = map.lastEntry(); if (last == null) { iterator = iterator(filters); } else { final ChronicleQueueRow value = last.getValue(); count = value.seqNumber(); filters.fromIndex = last.getValue().index(); iterator = iterator(filters); } } else iterator = iterator(filters); boolean hasMoreData = false; @Nullable ChronicleQueueRow row = null; while (iterator.hasNext()) { row = iterator.next(); count++; row.seqNumber(count); hasMoreData = true; if (row.seqNumber() % 1024 == 0) map.put(count, row); } if (hasMoreData) map.put(count, row); return (int) count; } }