childs =
+ ((IndexReader) obj).getContext().children();
+ if (childs != null) { // it is composite reader
+ for (final IndexReaderContext ctx : childs) {
+ all.add(ctx.reader().getCoreCacheKey());
+ }
+ }
+ } catch (AlreadyClosedException ace) {
+ // ignore this reader
+ }
+ }
+ }
+ // need to skip the first, because it was the seed
+ return all.subList(1, all.size());
+ }
+
+ /**
+ * Simple pair object for using "readerKey + fieldName" a Map key
+ */
+ private final static class ReaderField {
+ public final Object readerKey;
+ public final String fieldName;
+ public ReaderField(Object readerKey, String fieldName) {
+ this.readerKey = readerKey;
+ this.fieldName = fieldName;
+ }
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(readerKey) * fieldName.hashCode();
+ }
+ @Override
+ public boolean equals(Object that) {
+ if (! (that instanceof ReaderField)) return false;
+
+ ReaderField other = (ReaderField) that;
+ return (this.readerKey == other.readerKey &&
+ this.fieldName.equals(other.fieldName));
+ }
+ @Override
+ public String toString() {
+ return readerKey.toString() + "+" + fieldName;
+ }
+ }
+
+ /**
+ * Simple container for a collection of related CacheEntry objects that
+ * in conjunction with each other represent some "insane" usage of the
+ * FieldCache.
+ */
+ public final static class Insanity {
+ private final InsanityType type;
+ private final String msg;
+ private final CacheEntry[] entries;
+ public Insanity(InsanityType type, String msg, CacheEntry... entries) {
+ if (null == type) {
+ throw new IllegalArgumentException
+ ("Insanity requires non-null InsanityType");
+ }
+ if (null == entries || 0 == entries.length) {
+ throw new IllegalArgumentException
+ ("Insanity requires non-null/non-empty CacheEntry[]");
+ }
+ this.type = type;
+ this.msg = msg;
+ this.entries = entries;
+
+ }
+ /**
+ * Type of insane behavior this object represents
+ */
+ public InsanityType getType() { return type; }
+ /**
+ * Description of hte insane behavior
+ */
+ public String getMsg() { return msg; }
+ /**
+ * CacheEntry objects which suggest a problem
+ */
+ public CacheEntry[] getCacheEntries() { return entries; }
+ /**
+ * Multi-Line representation of this Insanity object, starting with
+ * the Type and Msg, followed by each CacheEntry.toString() on its
+ * own line prefaced by a tab character
+ */
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append(getType()).append(": ");
+
+ String m = getMsg();
+ if (null != m) buf.append(m);
+
+ buf.append('\n');
+
+ CacheEntry[] ce = getCacheEntries();
+ for (int i = 0; i < ce.length; i++) {
+ buf.append('\t').append(ce[i].toString()).append('\n');
+ }
+
+ return buf.toString();
+ }
+ }
+
+ /**
+ * An Enumeration of the different types of "insane" behavior that
+ * may be detected in a FieldCache.
+ *
+ * @see InsanityType#SUBREADER
+ * @see InsanityType#VALUEMISMATCH
+ * @see InsanityType#EXPECTED
+ */
+ public final static class InsanityType {
+ private final String label;
+ private InsanityType(final String label) {
+ this.label = label;
+ }
+ @Override
+ public String toString() { return label; }
+
+ /**
+ * Indicates an overlap in cache usage on a given field
+ * in sub/super readers.
+ */
+ public final static InsanityType SUBREADER
+ = new InsanityType("SUBREADER");
+
+ /**
+ *
+ * Indicates entries have the same reader+fieldname but
+ * different cached values. This can happen if different datatypes,
+ * or parsers are used -- and while it's not necessarily a bug
+ * it's typically an indication of a possible problem.
+ *
+ *
+ * NOTE: Only the reader, fieldname, and cached value are actually
+ * tested -- if two cache entries have different parsers or datatypes but
+ * the cached values are the same Object (== not just equal()) this method
+ * does not consider that a red flag. This allows for subtle variations
+ * in the way a Parser is specified (null vs DEFAULT_LONG_PARSER, etc...)
+ *
+ */
+ public final static InsanityType VALUEMISMATCH
+ = new InsanityType("VALUEMISMATCH");
+
+ /**
+ * Indicates an expected bit of "insanity". This may be useful for
+ * clients that wish to preserve/log information about insane usage
+ * but indicate that it was expected.
+ */
+ public final static InsanityType EXPECTED
+ = new InsanityType("EXPECTED");
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5525f429/solr/core/src/java/org/apache/solr/uninverting/UninvertingReader.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/uninverting/UninvertingReader.java b/solr/core/src/java/org/apache/solr/uninverting/UninvertingReader.java
new file mode 100644
index 0000000..4450cbb
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/uninverting/UninvertingReader.java
@@ -0,0 +1,391 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.uninverting;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Map;
+
+import org.apache.lucene.document.BinaryDocValuesField; // javadocs
+import org.apache.lucene.document.NumericDocValuesField; // javadocs
+import org.apache.lucene.document.SortedDocValuesField; // javadocs
+import org.apache.lucene.document.SortedSetDocValuesField; // javadocs
+import org.apache.lucene.document.StringField; // javadocs
+import org.apache.lucene.index.BinaryDocValues;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.DocValuesType;
+import org.apache.lucene.index.FieldInfo;
+import org.apache.lucene.index.FieldInfos;
+import org.apache.lucene.index.FilterDirectoryReader;
+import org.apache.lucene.index.FilterLeafReader;
+import org.apache.lucene.index.IndexOptions;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.NumericDocValues;
+import org.apache.lucene.index.SortedDocValues;
+import org.apache.lucene.index.SortedSetDocValues;
+import org.apache.lucene.util.Bits;
+import org.apache.solr.uninverting.FieldCache.CacheEntry;
+
+/**
+ * A FilterReader that exposes indexed values as if they also had
+ * docvalues.
+ *
+ * This is accomplished by "inverting the inverted index" or "uninversion".
+ *
+ * The uninversion process happens lazily: upon the first request for the
+ * field's docvalues (e.g. via {@link org.apache.lucene.index.LeafReader#getNumericDocValues(String)}
+ * or similar), it will create the docvalues on-the-fly if needed and cache it,
+ * based on the core cache key of the wrapped LeafReader.
+ */
+public class UninvertingReader extends FilterLeafReader {
+
+ /**
+ * Specifies the type of uninversion to apply for the field.
+ */
+ public static enum Type {
+ /**
+ * Single-valued Integer, (e.g. indexed with {@link org.apache.lucene.document.IntPoint})
+ *
+ * Fields with this type act as if they were indexed with
+ * {@link NumericDocValuesField}.
+ */
+ INTEGER_POINT,
+ /**
+ * Single-valued Integer, (e.g. indexed with {@link org.apache.lucene.document.LongPoint})
+ *
+ * Fields with this type act as if they were indexed with
+ * {@link NumericDocValuesField}.
+ */
+ LONG_POINT,
+ /**
+ * Single-valued Integer, (e.g. indexed with {@link org.apache.lucene.document.FloatPoint})
+ *
+ * Fields with this type act as if they were indexed with
+ * {@link NumericDocValuesField}.
+ */
+ FLOAT_POINT,
+ /**
+ * Single-valued Integer, (e.g. indexed with {@link org.apache.lucene.document.DoublePoint})
+ *
+ * Fields with this type act as if they were indexed with
+ * {@link NumericDocValuesField}.
+ */
+ DOUBLE_POINT,
+ /**
+ * Single-valued Integer, (e.g. indexed with {@link org.apache.lucene.document.LegacyIntField})
+ *
+ * Fields with this type act as if they were indexed with
+ * {@link NumericDocValuesField}.
+ * @deprecated Index with points and use {@link #INTEGER_POINT} instead.
+ */
+ @Deprecated
+ LEGACY_INTEGER,
+ /**
+ * Single-valued Long, (e.g. indexed with {@link org.apache.lucene.document.LegacyLongField})
+ *
+ * Fields with this type act as if they were indexed with
+ * {@link NumericDocValuesField}.
+ * @deprecated Index with points and use {@link #LONG_POINT} instead.
+ */
+ @Deprecated
+ LEGACY_LONG,
+ /**
+ * Single-valued Float, (e.g. indexed with {@link org.apache.lucene.document.LegacyFloatField})
+ *
+ * Fields with this type act as if they were indexed with
+ * {@link NumericDocValuesField}.
+ * @deprecated Index with points and use {@link #FLOAT_POINT} instead.
+ */
+ @Deprecated
+ LEGACY_FLOAT,
+ /**
+ * Single-valued Double, (e.g. indexed with {@link org.apache.lucene.document.LegacyDoubleField})
+ *
+ * Fields with this type act as if they were indexed with
+ * {@link NumericDocValuesField}.
+ * @deprecated Index with points and use {@link #DOUBLE_POINT} instead.
+ */
+ @Deprecated
+ LEGACY_DOUBLE,
+ /**
+ * Single-valued Binary, (e.g. indexed with {@link StringField})
+ *
+ * Fields with this type act as if they were indexed with
+ * {@link BinaryDocValuesField}.
+ */
+ BINARY,
+ /**
+ * Single-valued Binary, (e.g. indexed with {@link StringField})
+ *
+ * Fields with this type act as if they were indexed with
+ * {@link SortedDocValuesField}.
+ */
+ SORTED,
+ /**
+ * Multi-valued Binary, (e.g. indexed with {@link StringField})
+ *
+ * Fields with this type act as if they were indexed with
+ * {@link SortedSetDocValuesField}.
+ */
+ SORTED_SET_BINARY,
+ /**
+ * Multi-valued Integer, (e.g. indexed with {@link org.apache.lucene.document.LegacyIntField})
+ *
+ * Fields with this type act as if they were indexed with
+ * {@link SortedSetDocValuesField}.
+ */
+ SORTED_SET_INTEGER,
+ /**
+ * Multi-valued Float, (e.g. indexed with {@link org.apache.lucene.document.LegacyFloatField})
+ *
+ * Fields with this type act as if they were indexed with
+ * {@link SortedSetDocValuesField}.
+ */
+ SORTED_SET_FLOAT,
+ /**
+ * Multi-valued Long, (e.g. indexed with {@link org.apache.lucene.document.LegacyLongField})
+ *
+ * Fields with this type act as if they were indexed with
+ * {@link SortedSetDocValuesField}.
+ */
+ SORTED_SET_LONG,
+ /**
+ * Multi-valued Double, (e.g. indexed with {@link org.apache.lucene.document.LegacyDoubleField})
+ *
+ * Fields with this type act as if they were indexed with
+ * {@link SortedSetDocValuesField}.
+ */
+ SORTED_SET_DOUBLE
+ }
+
+ /**
+ * Wraps a provided DirectoryReader. Note that for convenience, the returned reader
+ * can be used normally (e.g. passed to {@link DirectoryReader#openIfChanged(DirectoryReader)})
+ * and so on.
+ */
+ public static DirectoryReader wrap(DirectoryReader in, final Map mapping) throws IOException {
+ return new UninvertingDirectoryReader(in, mapping);
+ }
+
+ static class UninvertingDirectoryReader extends FilterDirectoryReader {
+ final Map mapping;
+
+ public UninvertingDirectoryReader(DirectoryReader in, final Map mapping) throws IOException {
+ super(in, new FilterDirectoryReader.SubReaderWrapper() {
+ @Override
+ public LeafReader wrap(LeafReader reader) {
+ return new UninvertingReader(reader, mapping);
+ }
+ });
+ this.mapping = mapping;
+ }
+
+ @Override
+ protected DirectoryReader doWrapDirectoryReader(DirectoryReader in) throws IOException {
+ return new UninvertingDirectoryReader(in, mapping);
+ }
+ }
+
+ final Map mapping;
+ final FieldInfos fieldInfos;
+
+ /**
+ * Create a new UninvertingReader with the specified mapping
+ *
+ * Expert: This should almost never be used. Use {@link #wrap(DirectoryReader, Map)}
+ * instead.
+ *
+ * @lucene.internal
+ */
+ public UninvertingReader(LeafReader in, Map mapping) {
+ super(in);
+ this.mapping = mapping;
+ ArrayList filteredInfos = new ArrayList<>();
+ for (FieldInfo fi : in.getFieldInfos()) {
+ DocValuesType type = fi.getDocValuesType();
+ if (type == DocValuesType.NONE) {
+ Type t = mapping.get(fi.name);
+ if (t != null) {
+ if (t == Type.INTEGER_POINT || t == Type.LONG_POINT || t == Type.FLOAT_POINT || t == Type.DOUBLE_POINT) {
+ // type uses points
+ if (fi.getPointDimensionCount() == 0) {
+ continue;
+ }
+ } else {
+ // type uses inverted index
+ if (fi.getIndexOptions() == IndexOptions.NONE) {
+ continue;
+ }
+ }
+ switch(t) {
+ case INTEGER_POINT:
+ case LONG_POINT:
+ case FLOAT_POINT:
+ case DOUBLE_POINT:
+ case LEGACY_INTEGER:
+ case LEGACY_LONG:
+ case LEGACY_FLOAT:
+ case LEGACY_DOUBLE:
+ type = DocValuesType.NUMERIC;
+ break;
+ case BINARY:
+ type = DocValuesType.BINARY;
+ break;
+ case SORTED:
+ type = DocValuesType.SORTED;
+ break;
+ case SORTED_SET_BINARY:
+ case SORTED_SET_INTEGER:
+ case SORTED_SET_FLOAT:
+ case SORTED_SET_LONG:
+ case SORTED_SET_DOUBLE:
+ type = DocValuesType.SORTED_SET;
+ break;
+ default:
+ throw new AssertionError();
+ }
+ }
+ }
+ filteredInfos.add(new FieldInfo(fi.name, fi.number, fi.hasVectors(), fi.omitsNorms(),
+ fi.hasPayloads(), fi.getIndexOptions(), type, fi.getDocValuesGen(), fi.attributes(),
+ fi.getPointDimensionCount(), fi.getPointNumBytes()));
+ }
+ fieldInfos = new FieldInfos(filteredInfos.toArray(new FieldInfo[filteredInfos.size()]));
+ }
+
+ @Override
+ public FieldInfos getFieldInfos() {
+ return fieldInfos;
+ }
+
+ @Override
+ public NumericDocValues getNumericDocValues(String field) throws IOException {
+ Type v = getType(field);
+ if (v != null) {
+ switch (v) {
+ case INTEGER_POINT: return FieldCache.DEFAULT.getNumerics(in, field, FieldCache.INT_POINT_PARSER, true);
+ case FLOAT_POINT: return FieldCache.DEFAULT.getNumerics(in, field, FieldCache.FLOAT_POINT_PARSER, true);
+ case LONG_POINT: return FieldCache.DEFAULT.getNumerics(in, field, FieldCache.LONG_POINT_PARSER, true);
+ case DOUBLE_POINT: return FieldCache.DEFAULT.getNumerics(in, field, FieldCache.DOUBLE_POINT_PARSER, true);
+ case LEGACY_INTEGER: return FieldCache.DEFAULT.getNumerics(in, field, FieldCache.LEGACY_INT_PARSER, true);
+ case LEGACY_FLOAT: return FieldCache.DEFAULT.getNumerics(in, field, FieldCache.LEGACY_FLOAT_PARSER, true);
+ case LEGACY_LONG: return FieldCache.DEFAULT.getNumerics(in, field, FieldCache.LEGACY_LONG_PARSER, true);
+ case LEGACY_DOUBLE: return FieldCache.DEFAULT.getNumerics(in, field, FieldCache.LEGACY_DOUBLE_PARSER, true);
+ }
+ }
+ return super.getNumericDocValues(field);
+ }
+
+ @Override
+ public BinaryDocValues getBinaryDocValues(String field) throws IOException {
+ Type v = getType(field);
+ if (v == Type.BINARY) {
+ return FieldCache.DEFAULT.getTerms(in, field, true);
+ } else {
+ return in.getBinaryDocValues(field);
+ }
+ }
+
+ @Override
+ public SortedDocValues getSortedDocValues(String field) throws IOException {
+ Type v = getType(field);
+ if (v == Type.SORTED) {
+ return FieldCache.DEFAULT.getTermsIndex(in, field);
+ } else {
+ return in.getSortedDocValues(field);
+ }
+ }
+
+ @Override
+ public SortedSetDocValues getSortedSetDocValues(String field) throws IOException {
+ Type v = getType(field);
+ if (v != null) {
+ switch (v) {
+ case SORTED_SET_INTEGER:
+ case SORTED_SET_FLOAT:
+ return FieldCache.DEFAULT.getDocTermOrds(in, field, FieldCache.INT32_TERM_PREFIX);
+ case SORTED_SET_LONG:
+ case SORTED_SET_DOUBLE:
+ return FieldCache.DEFAULT.getDocTermOrds(in, field, FieldCache.INT64_TERM_PREFIX);
+ case SORTED_SET_BINARY:
+ return FieldCache.DEFAULT.getDocTermOrds(in, field, null);
+ }
+ }
+ return in.getSortedSetDocValues(field);
+ }
+
+ @Override
+ public Bits getDocsWithField(String field) throws IOException {
+ Type v = getType(field);
+ if (v != null) {
+ switch (v) {
+ case INTEGER_POINT: return FieldCache.DEFAULT.getDocsWithField(in, field, FieldCache.INT_POINT_PARSER);
+ case FLOAT_POINT: return FieldCache.DEFAULT.getDocsWithField(in, field, FieldCache.FLOAT_POINT_PARSER);
+ case LONG_POINT: return FieldCache.DEFAULT.getDocsWithField(in, field, FieldCache.LONG_POINT_PARSER);
+ case DOUBLE_POINT: return FieldCache.DEFAULT.getDocsWithField(in, field, FieldCache.DOUBLE_POINT_PARSER);
+ case LEGACY_INTEGER: return FieldCache.DEFAULT.getDocsWithField(in, field, FieldCache.LEGACY_INT_PARSER);
+ case LEGACY_FLOAT: return FieldCache.DEFAULT.getDocsWithField(in, field, FieldCache.LEGACY_FLOAT_PARSER);
+ case LEGACY_LONG: return FieldCache.DEFAULT.getDocsWithField(in, field, FieldCache.LEGACY_LONG_PARSER);
+ case LEGACY_DOUBLE: return FieldCache.DEFAULT.getDocsWithField(in, field, FieldCache.LEGACY_DOUBLE_PARSER);
+ default:
+ return FieldCache.DEFAULT.getDocsWithField(in, field, null);
+ }
+ } else {
+ return in.getDocsWithField(field);
+ }
+ }
+
+ /**
+ * Returns the field's uninversion type, or null
+ * if the field doesn't exist or doesn't have a mapping.
+ */
+ private Type getType(String field) {
+ FieldInfo info = fieldInfos.fieldInfo(field);
+ if (info == null || info.getDocValuesType() == DocValuesType.NONE) {
+ return null;
+ }
+ return mapping.get(field);
+ }
+
+ @Override
+ public Object getCoreCacheKey() {
+ return in.getCoreCacheKey();
+ }
+
+ @Override
+ public Object getCombinedCoreAndDeletesKey() {
+ return in.getCombinedCoreAndDeletesKey();
+ }
+
+ @Override
+ public String toString() {
+ return "Uninverting(" + in.toString() + ")";
+ }
+
+ /**
+ * Return information about the backing cache
+ * @lucene.internal
+ */
+ public static String[] getUninvertedStats() {
+ CacheEntry[] entries = FieldCache.DEFAULT.getCacheEntries();
+ String[] info = new String[entries.length];
+ for (int i = 0; i < entries.length; i++) {
+ info[i] = entries[i].toString();
+ }
+ return info;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5525f429/solr/core/src/java/org/apache/solr/uninverting/package-info.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/uninverting/package-info.java b/solr/core/src/java/org/apache/solr/uninverting/package-info.java
new file mode 100644
index 0000000..d95e08f
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/uninverting/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Support for creating docvalues on-the-fly from the inverted index at runtime.
+ */
+package org.apache.solr.uninverting;
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5525f429/solr/core/src/java/org/apache/solr/update/DeleteByQueryWrapper.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/update/DeleteByQueryWrapper.java b/solr/core/src/java/org/apache/solr/update/DeleteByQueryWrapper.java
index 3d87161..778e4c6 100644
--- a/solr/core/src/java/org/apache/solr/update/DeleteByQueryWrapper.java
+++ b/solr/core/src/java/org/apache/solr/update/DeleteByQueryWrapper.java
@@ -29,8 +29,9 @@ import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Weight;
-import org.apache.lucene.uninverting.UninvertingReader;
import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.uninverting.UninvertingReader;
+import org.apache.solr.uninverting.UninvertingReader;
/**
* Allows access to uninverted docvalues by delete-by-queries.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5525f429/solr/core/src/java/org/apache/solr/update/VersionInfo.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/update/VersionInfo.java b/solr/core/src/java/org/apache/solr/update/VersionInfo.java
index 5fe415c..bee30f5 100644
--- a/solr/core/src/java/org/apache/solr/update/VersionInfo.java
+++ b/solr/core/src/java/org/apache/solr/update/VersionInfo.java
@@ -24,7 +24,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.SlowCompositeReaderWrapper;
import org.apache.lucene.index.Terms;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
@@ -34,6 +33,7 @@ import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.LegacyNumericUtils;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.SuppressForbidden;
+import org.apache.solr.index.SlowCompositeReaderWrapper;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.SolrIndexSearcher;
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5525f429/solr/core/src/test/org/apache/solr/index/TestSlowCompositeReaderWrapper.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/index/TestSlowCompositeReaderWrapper.java b/solr/core/src/test/org/apache/solr/index/TestSlowCompositeReaderWrapper.java
new file mode 100644
index 0000000..0685e55
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/index/TestSlowCompositeReaderWrapper.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.index;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.TestUtil;
+
+public class TestSlowCompositeReaderWrapper extends LuceneTestCase {
+
+ public void testCoreListenerOnSlowCompositeReaderWrapper() throws IOException {
+ RandomIndexWriter w = new RandomIndexWriter(random(), newDirectory());
+ final int numDocs = TestUtil.nextInt(random(), 1, 5);
+ for (int i = 0; i < numDocs; ++i) {
+ w.addDocument(new Document());
+ if (random().nextBoolean()) {
+ w.commit();
+ }
+ }
+ w.commit();
+ w.close();
+
+ final IndexReader reader = DirectoryReader.open(w.w.getDirectory());
+ final LeafReader leafReader = SlowCompositeReaderWrapper.wrap(reader);
+
+ final int numListeners = TestUtil.nextInt(random(), 1, 10);
+ final List listeners = new ArrayList<>();
+ AtomicInteger counter = new AtomicInteger(numListeners);
+
+ for (int i = 0; i < numListeners; ++i) {
+ CountCoreListener listener = new CountCoreListener(counter, leafReader.getCoreCacheKey());
+ listeners.add(listener);
+ leafReader.addCoreClosedListener(listener);
+ }
+ for (int i = 0; i < 100; ++i) {
+ leafReader.addCoreClosedListener(listeners.get(random().nextInt(listeners.size())));
+ }
+ final int removed = random().nextInt(numListeners);
+ Collections.shuffle(listeners, random());
+ for (int i = 0; i < removed; ++i) {
+ leafReader.removeCoreClosedListener(listeners.get(i));
+ }
+ assertEquals(numListeners, counter.get());
+ // make sure listeners are registered on the wrapped reader and that closing any of them has the same effect
+ if (random().nextBoolean()) {
+ reader.close();
+ } else {
+ leafReader.close();
+ }
+ assertEquals(removed, counter.get());
+ w.w.getDirectory().close();
+ }
+
+ private static final class CountCoreListener implements LeafReader.CoreClosedListener {
+
+ private final AtomicInteger count;
+ private final Object coreCacheKey;
+
+ public CountCoreListener(AtomicInteger count, Object coreCacheKey) {
+ this.count = count;
+ this.coreCacheKey = coreCacheKey;
+ }
+
+ @Override
+ public void onClose(Object coreCacheKey) {
+ assertSame(this.coreCacheKey, coreCacheKey);
+ count.decrementAndGet();
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5525f429/solr/core/src/test/org/apache/solr/request/TestFaceting.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/request/TestFaceting.java b/solr/core/src/test/org/apache/solr/request/TestFaceting.java
index 97dcedf..4dd49e1 100644
--- a/solr/core/src/test/org/apache/solr/request/TestFaceting.java
+++ b/solr/core/src/test/org/apache/solr/request/TestFaceting.java
@@ -25,12 +25,12 @@ import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.SortedSetDocValues;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermsEnum;
-import org.apache.lucene.uninverting.DocTermOrds;
import org.apache.lucene.util.BytesRef;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.params.FacetParams;
import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.uninverting.DocTermOrds;
import org.apache.solr.util.RefCounted;
import org.junit.After;
import org.junit.BeforeClass;
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5525f429/solr/core/src/test/org/apache/solr/search/TestSort.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/TestSort.java b/solr/core/src/test/org/apache/solr/search/TestSort.java
index e874c37..8590b18 100644
--- a/solr/core/src/test/org/apache/solr/search/TestSort.java
+++ b/solr/core/src/test/org/apache/solr/search/TestSort.java
@@ -42,13 +42,12 @@ import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.LeafCollector;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
-import org.apache.lucene.search.SortField;
import org.apache.lucene.search.SortField.Type;
+import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopFieldCollector;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
-import org.apache.lucene.uninverting.UninvertingReader;
import org.apache.lucene.util.BitDocIdSet;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.FixedBitSet;
@@ -56,6 +55,7 @@ import org.apache.lucene.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.schema.SchemaField;
+import org.apache.solr.uninverting.UninvertingReader;
import org.junit.BeforeClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;