2
* Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements. See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
19
package org.apache.solr;
22
import org.apache.lucene.store.MockDirectoryWrapper;
23
import org.apache.lucene.util.LuceneTestCase;
24
import org.apache.noggit.CharArr;
25
import org.apache.noggit.JSONUtil;
26
import org.apache.noggit.ObjectBuilder;
27
import org.apache.solr.common.SolrException;
28
import org.apache.solr.common.SolrInputDocument;
29
import org.apache.solr.common.SolrInputField;
30
import org.apache.solr.common.params.CommonParams;
31
import org.apache.solr.common.params.ModifiableSolrParams;
32
import org.apache.solr.common.params.SolrParams;
33
import org.apache.solr.common.util.XML;
34
import org.apache.solr.core.SolrConfig;
35
import org.apache.solr.core.SolrCore;
36
import org.apache.solr.handler.JsonUpdateRequestHandler;
37
import org.apache.solr.request.LocalSolrQueryRequest;
38
import org.apache.solr.request.SolrQueryRequest;
39
import org.apache.solr.request.SolrRequestHandler;
40
import org.apache.solr.schema.IndexSchema;
41
import org.apache.solr.schema.SchemaField;
42
import org.apache.solr.search.SolrIndexSearcher;
43
import org.apache.solr.servlet.DirectSolrConnection;
44
import org.apache.solr.util.TestHarness;
45
import org.junit.AfterClass;
46
import org.junit.BeforeClass;
47
import org.slf4j.Logger;
48
import org.slf4j.LoggerFactory;
49
import org.xml.sax.SAXException;
51
import javax.xml.xpath.XPathExpressionException;
53
import java.io.IOException;
54
import java.io.StringWriter;
58
* A junit4 Solr test harness that extends LuceneTestCaseJ4.
59
* Unlike AbstractSolrTestCase, a new core is not created for each test method.
62
public abstract class SolrTestCaseJ4 extends LuceneTestCase {
65
public static void beforeClassSolrTestCase() throws Exception {
66
startTrackingSearchers();
67
ignoreException("ignore_exception");
71
public static void afterClassSolrTestCase() throws Exception {
73
resetExceptionIgnores();
74
endTrackingSearchers();
77
// SOLR-2279: hack to shut these directories down
78
// we still keep the ability to track open index files this way
79
public static void closeDirectories() throws Exception {
80
for (MockDirectoryWrapper d : stores.keySet()) {
88
public void setUp() throws Exception {
90
log.info("###Starting " + getName()); // returns <unknown>???
94
public void tearDown() throws Exception {
95
log.info("###Ending " + getName());
99
/** Call initCore in @BeforeClass to instantiate a solr core in your test class.
100
* deleteCore will be called for you via SolrTestCaseJ4 @AfterClass */
101
public static void initCore(String config, String schema) throws Exception {
102
initCore(config, schema, TEST_HOME());
105
/** Call initCore in @BeforeClass to instantiate a solr core in your test class.
106
* deleteCore will be called for you via SolrTestCaseJ4 @AfterClass */
107
public static void initCore(String config, String schema, String solrHome) throws Exception {
108
configString = config;
109
schemaString = schema;
110
if (solrHome != null) {
111
System.setProperty("solr.solr.home", solrHome);
113
System.setProperty("solr.velocity.enabled", "false");
118
static long numOpens;
119
static long numCloses;
120
public static void startTrackingSearchers() {
121
numOpens = SolrIndexSearcher.numOpens.get();
122
numCloses = SolrIndexSearcher.numCloses.get();
125
public static void endTrackingSearchers() {
126
long endNumOpens = SolrIndexSearcher.numOpens.get();
127
long endNumCloses = SolrIndexSearcher.numCloses.get();
129
SolrIndexSearcher.numOpens.getAndSet(0);
130
SolrIndexSearcher.numCloses.getAndSet(0);
133
if (endNumOpens-numOpens != endNumCloses-numCloses) {
134
String msg = "ERROR: SolrIndexSearcher opens=" + (endNumOpens-numOpens) + " closes=" + (endNumCloses-numCloses);
141
/** Causes an exception matching the regex pattern to not be logged. */
142
public static void ignoreException(String pattern) {
143
if (SolrException.ignorePatterns == null)
144
SolrException.ignorePatterns = new HashSet<String>();
145
SolrException.ignorePatterns.add(pattern);
148
public static void resetExceptionIgnores() {
149
SolrException.ignorePatterns = null;
150
ignoreException("ignore_exception"); // always ignore "ignore_exception"
153
protected static String getClassName() {
154
StackTraceElement[] stack = new RuntimeException("WhoAmI").fillInStackTrace().getStackTrace();
155
for (int i = stack.length-1; i>=0; i--) {
156
StackTraceElement ste = stack[i];
157
String cname = ste.getClassName();
158
if (cname.indexOf(".lucene.")>=0 || cname.indexOf(".solr.")>=0) {
162
return SolrTestCaseJ4.class.getName();
165
protected static String getSimpleClassName() {
166
String cname = getClassName();
167
return cname.substring(cname.lastIndexOf('.')+1);
170
protected static String configString;
171
protected static String schemaString;
173
protected static SolrConfig solrConfig;
175
* Harness initialized by initTestHarness.
178
* For use in test methods as needed.
181
protected static TestHarness h;
183
* LocalRequestFactory initialized by initTestHarness using sensible
187
* For use in test methods as needed.
190
protected static TestHarness.LocalRequestFactory lrf;
194
* Subclasses must define this method to return the name of the
195
* schema.xml they wish to use.
197
public static String getSchemaFile() {
202
* Subclasses must define this method to return the name of the
203
* solrconfig.xml they wish to use.
205
public static String getSolrConfigFile() {
210
* The directory used to story the index managed by the TestHarness h
212
protected static File dataDir;
215
* Initializes things your test might need
218
* <li>Creates a dataDir in the "java.io.tmpdir"</li>
219
* <li>initializes the TestHarness h using this data directory, and getSchemaPath()</li>
220
* <li>initializes the LocalRequestFactory lrf using sensible defaults.</li>
225
public static Logger log = LoggerFactory.getLogger(SolrTestCaseJ4.class);
227
private static String factoryProp;
229
public static void createTempDir() {
230
String cname = getSimpleClassName();
231
dataDir = new File(TEMP_DIR,
232
"solrtest-" + cname + "-" + System.currentTimeMillis());
236
public static void initCore() throws Exception {
237
log.info("####initCore");
239
ignoreException("ignore_exception");
240
factoryProp = System.getProperty("solr.directoryFactory");
241
if (factoryProp == null) {
242
System.setProperty("solr.directoryFactory","solr.RAMDirectoryFactory");
244
if (dataDir == null) {
248
// other methods like starting a jetty instance need these too
249
System.setProperty("solr.test.sys.prop1", "propone");
250
System.setProperty("solr.test.sys.prop2", "proptwo");
252
String configFile = getSolrConfigFile();
253
if (configFile != null) {
255
solrConfig = h.createConfig(getSolrConfigFile());
256
h = new TestHarness( dataDir.getAbsolutePath(),
259
lrf = h.getRequestFactory
260
("standard",0,20,CommonParams.VERSION,"2.2");
262
log.info("####initCore end");
265
/** Subclasses that override setUp can optionally call this method
266
* to log the fact that their setUp process has ended.
268
public void postSetUp() {
269
log.info("####POSTSETUP " + getName());
273
/** Subclasses that override tearDown can optionally call this method
274
* to log the fact that the tearDown process has started. This is necessary
275
* since subclasses will want to call super.tearDown() at the *end* of their
278
public void preTearDown() {
279
log.info("####PRETEARDOWN " + getName());
283
* Shuts down the test harness, and makes the best attempt possible
284
* to delete dataDir, unless the system property "solr.test.leavedatadir"
287
public static void deleteCore() throws Exception {
288
log.info("###deleteCore" );
289
if (h != null) { h.close(); }
291
if (dataDir != null) {
292
String skip = System.getProperty("solr.test.leavedatadir");
293
if (null != skip && 0 != skip.trim().length()) {
294
System.err.println("NOTE: per solr.test.leavedatadir, dataDir will not be removed: " + dataDir.getAbsolutePath());
296
if (!recurseDelete(dataDir)) {
297
System.err.println("!!!! WARNING: best effort to remove " + dataDir.getAbsolutePath() + " FAILED !!!!!");
302
if (factoryProp == null) {
303
System.clearProperty("solr.directoryFactory");
310
configString = schemaString = null;
314
/** Validates an update XML String is successful
316
public static void assertU(String update) {
317
assertU(null, update);
320
/** Validates an update XML String is successful
322
public static void assertU(String message, String update) {
323
checkUpdateU(message, update, true);
326
/** Validates an update XML String failed
328
public static void assertFailedU(String update) {
329
assertFailedU(null, update);
332
/** Validates an update XML String failed
334
public static void assertFailedU(String message, String update) {
335
checkUpdateU(message, update, false);
338
/** Checks the success or failure of an update message
340
private static void checkUpdateU(String message, String update, boolean shouldSucceed) {
342
String m = (null == message) ? "" : message + " ";
344
String res = h.validateUpdate(update);
345
if (res != null) fail(m + "update was not successful: " + res);
347
String res = h.validateErrorUpdate(update);
348
if (res != null) fail(m + "update succeeded, but should have failed: " + res);
350
} catch (SAXException e) {
351
throw new RuntimeException("Invalid XML", e);
355
/** Validates a query matches some XPath test expressions and closes the query */
356
public static void assertQ(SolrQueryRequest req, String... tests) {
357
assertQ(null, req, tests);
360
/** Validates a query matches some XPath test expressions and closes the query */
361
public static void assertQ(String message, SolrQueryRequest req, String... tests) {
363
String m = (null == message) ? "" : message + " ";
364
String response = h.query(req);
366
if (req.getParams().getBool("facet", false)) {
367
// add a test to ensure that faceting did not throw an exception
368
// internally, where it would be added to facet_counts/exception
369
String[] allTests = new String[tests.length+1];
370
System.arraycopy(tests,0,allTests,1,tests.length);
371
allTests[0] = "*[count(//lst[@name='facet_counts']/*[@name='exception'])=0]";
375
String results = h.validateXPath(response, tests);
377
if (null != results) {
378
String msg = "REQUEST FAILED: xpath=" + results
379
+ "\n\txml response was: " + response
380
+ "\n\trequest was:" + req.getParamString();
383
throw new RuntimeException(msg);
386
} catch (XPathExpressionException e1) {
387
throw new RuntimeException("XPath is invalid", e1);
388
} catch (Exception e2) {
389
SolrException.log(log,"REQUEST FAILED: " + req.getParamString(), e2);
390
throw new RuntimeException("Exception during query", e2);
395
* Validates a query matches some JSON test expressions using the default double delta tollerance.
396
* @see JSONTestUtil#DEFAULT_DELTA
397
* @see #assertJQ(SolrQueryRequest,double,String...)
399
public static void assertJQ(SolrQueryRequest req, String... tests) throws Exception {
400
assertJQ(req, JSONTestUtil.DEFAULT_DELTA, tests);
403
* Validates a query matches some JSON test expressions and closes the
404
* query. The text expression is of the form path:JSON. To facilitate
405
* easy embedding in Java strings, the JSON can have double quotes
406
* replaced with single quotes.
408
* Please use this with care: this makes it easy to match complete
409
* structures, but doing so can result in fragile tests if you are
410
* matching more than what you want to test.
412
* @param req Solr request to execute
413
* @param delta tollerance allowed in comparing float/double values
414
* @param tests JSON path expression + '==' + expected value
416
public static void assertJQ(SolrQueryRequest req, double delta, String... tests) throws Exception {
417
SolrParams params = null;
419
params = req.getParams();
420
if (!"json".equals(params.get("wt","xml")) || params.get("indent")==null) {
421
ModifiableSolrParams newParams = new ModifiableSolrParams(params);
422
newParams.set("wt","json");
423
if (params.get("indent")==null) newParams.set("indent","true");
424
req.setParams(newParams);
430
response = h.query(req);
434
log.error("REQUEST FAILED: " + req.getParamString());
438
for (String test : tests) {
439
if (test == null || test.length()==0) continue;
440
String testJSON = test.replace('\'', '"');
444
String err = JSONTestUtil.match(response, testJSON, delta);
447
log.error("query failed JSON validation. error=" + err +
448
"\n expected =" + testJSON +
449
"\n response = " + response +
450
"\n request = " + req.getParamString()
452
throw new RuntimeException(err);
456
log.error("JSON query validation threw an exception." +
457
"\n expected =" + testJSON +
458
"\n response = " + response +
459
"\n request = " + req.getParamString()
465
// restore the params
466
if (params != null && params != req.getParams()) req.setParams(params);
471
/** Makes sure a query throws a SolrException with the listed response code */
472
public static void assertQEx(String message, SolrQueryRequest req, int code ) {
476
} catch (SolrException sex) {
477
assertEquals( code, sex.code() );
478
} catch (Exception e2) {
479
throw new RuntimeException("Exception during query", e2);
483
public static void assertQEx(String message, SolrQueryRequest req, SolrException.ErrorCode code ) {
487
} catch (SolrException e) {
488
assertEquals( code.code, e.code() );
489
} catch (Exception e2) {
490
throw new RuntimeException("Exception during query", e2);
496
* @see TestHarness#optimize
498
public static String optimize(String... args) {
499
return h.optimize(args);
502
* @see TestHarness#commit
504
public static String commit(String... args) {
505
return h.commit(args);
509
* Generates a simple <add><doc>... XML String with no options
511
* @param fieldsAndValues 0th and Even numbered args are fields names odds are field values.
515
public static String adoc(String... fieldsAndValues) {
516
XmlDoc d = doc(fieldsAndValues);
521
* Generates a simple <add><doc>... XML String with no options
523
public static String adoc(SolrInputDocument sdoc) {
524
List<String> fields = new ArrayList<String>();
525
for (SolrInputField sf : sdoc) {
526
for (Object o : sf.getValues()) {
527
fields.add(sf.getName());
528
fields.add(o.toString());
531
return adoc(fields.toArray(new String[fields.size()]));
536
* Generates an <add><doc>... XML String with options
539
* @param doc the Document to add
540
* @param args 0th and Even numbered args are param names, Odds are param values.
544
public static String add(XmlDoc doc, String... args) {
546
StringWriter r = new StringWriter();
549
if (null == args || 0 == args.length) {
554
XML.writeUnescapedXML(r, "add", doc.xml, (Object[])args);
557
return r.getBuffer().toString();
558
} catch (IOException e) {
559
throw new RuntimeException
560
("this should never happen with a StringWriter", e);
565
* Generates a <delete>... XML string for an ID
567
* @see TestHarness#deleteById
569
public static String delI(String id) {
570
return h.deleteById(id);
573
* Generates a <delete>... XML string for an query
575
* @see TestHarness#deleteByQuery
577
public static String delQ(String q) {
578
return h.deleteByQuery(q);
582
* Generates a simple <doc>... XML String with no options
584
* @param fieldsAndValues 0th and Even numbered args are fields names, Odds are field values.
585
* @see TestHarness#makeSimpleDoc
587
public static XmlDoc doc(String... fieldsAndValues) {
588
XmlDoc d = new XmlDoc();
589
d.xml = h.makeSimpleDoc(fieldsAndValues).toString();
593
public static ModifiableSolrParams params(String... params) {
594
ModifiableSolrParams msp = new ModifiableSolrParams();
595
for (int i=0; i<params.length; i+=2) {
596
msp.add(params[i], params[i+1]);
602
* Generates a SolrQueryRequest using the LocalRequestFactory
605
public static SolrQueryRequest req(String... q) {
606
return lrf.makeRequest(q);
610
* Generates a SolrQueryRequest using the LocalRequestFactory
613
public static SolrQueryRequest req(String[] params, String... moreParams) {
614
String[] allParams = moreParams;
615
if (params.length!=0) {
616
int len = params.length + moreParams.length;
617
allParams = new String[len];
618
System.arraycopy(params,0,allParams,0,params.length);
619
System.arraycopy(moreParams,0,allParams,params.length,moreParams.length);
622
return lrf.makeRequest(allParams);
626
* Generates a SolrQueryRequest
628
public static SolrQueryRequest req(SolrParams params, String... moreParams) {
629
ModifiableSolrParams mp = new ModifiableSolrParams(params);
630
for (int i=0; i<moreParams.length; i+=2) {
631
mp.add(moreParams[i], moreParams[i+1]);
633
return new LocalSolrQueryRequest(h.getCore(), mp);
636
/** Neccessary to make method signatures un-ambiguous */
637
public static class XmlDoc {
640
public String toString() { return xml; }
643
public static boolean recurseDelete(File f) {
644
if (f.isDirectory()) {
645
for (File sub : f.listFiles()) {
646
if (!recurseDelete(sub)) {
647
System.err.println("!!!! WARNING: best effort to remove " + sub.getAbsolutePath() + " FAILED !!!!!");
655
public void clearIndex() {
656
assertU(delQ("*:*"));
659
/** Send JSON update commands */
660
public static String updateJ(String json, SolrParams args) throws Exception {
661
SolrCore core = h.getCore();
662
DirectSolrConnection connection = new DirectSolrConnection(core);
663
SolrRequestHandler handler = core.getRequestHandler("/update/json");
664
if (handler == null) {
665
handler = new JsonUpdateRequestHandler();
668
return connection.request(handler, args, json);
672
/////////////////////////////////////////////////////////////////////////////////////
673
//////////////////////////// random document / index creation ///////////////////////
674
/////////////////////////////////////////////////////////////////////////////////////
676
public abstract static class Vals {
677
public abstract Comparable get();
678
public String toJSON(Comparable val) {
679
return JSONUtil.toJSON(val);
682
protected int between(int min, int max) {
683
return min != max ? random.nextInt(max-min+1) + min : min;
687
public abstract static class IVals extends Vals {
688
public abstract int getInt();
691
public static class IRange extends IVals {
694
public IRange(int min, int max) {
700
public int getInt() {
701
return between(min,max);
705
public Comparable get() {
710
public static class FVal extends Vals {
713
public FVal(float min, float max) {
718
public float getFloat() {
719
if (min >= max) return min;
720
return min + random.nextFloat() * (max - min);
724
public Comparable get() {
729
public static class SVal extends Vals {
739
public SVal(char start, char end, int minLength, int maxLength) {
742
this.minLength = minLength;
743
this.maxLength = maxLength;
747
public Comparable get() {
748
char[] arr = new char[between(minLength,maxLength)];
749
for (int i=0; i<arr.length; i++) {
750
arr[i] = (char)between(start, end);
752
return new String(arr);
756
public static final IRange ZERO_ONE = new IRange(0,1);
757
public static final IRange ZERO_TWO = new IRange(0,2);
758
public static final IRange ONE_ONE = new IRange(1,1);
760
public static class Doc implements Comparable{
761
public Comparable id;
762
public List<Fld> fields;
763
public int order; // the order this document was added to the index
767
public String toString() {
768
return "Doc("+order+"):"+fields.toString();
772
public int hashCode() {
773
return id.hashCode();
777
public boolean equals(Object o) {
778
if (!(o instanceof Doc)) return false;
780
return this==other || id != null && id.equals(other.id);
783
public int compareTo(Object o) {
784
if (!(o instanceof Doc)) return this.getClass().hashCode() - o.getClass().hashCode();
786
return this.id.compareTo(other.id);
789
public List<Comparable> getValues(String field) {
790
for (Fld fld : fields) {
791
if (fld.ftype.fname.equals(field)) return fld.vals;
796
public Comparable getFirstValue(String field) {
797
List<Comparable> vals = getValues(field);
798
return vals==null || vals.size()==0 ? null : vals.get(0);
801
public Map<String,Object> toObject(IndexSchema schema) {
802
Map<String,Object> result = new HashMap<String,Object>();
803
for (Fld fld : fields) {
804
SchemaField sf = schema.getField(fld.ftype.fname);
805
if (!sf.multiValued()) {
806
result.put(fld.ftype.fname, fld.vals.get(0));
808
result.put(fld.ftype.fname, fld.vals);
816
public static class Fld {
817
public FldType ftype;
818
public List<Comparable> vals;
820
public String toString() {
821
return ftype.fname + "=" + (vals.size()==1 ? vals.get(0).toString() : vals.toString());
827
public IRange numValues;
830
public FldType(String fname, Vals vals) {
831
this(fname, ZERO_ONE, vals);
834
public FldType(String fname, IRange numValues, Vals vals) {
836
this.numValues = numValues;
840
public Comparable createValue() {
844
public List<Comparable> createValues() {
845
int nVals = numValues.getInt();
846
if (nVals <= 0) return null;
847
List<Comparable> vals = new ArrayList<Comparable>(nVals);
848
for (int i=0; i<nVals; i++)
849
vals.add(createValue());
853
public Fld createField() {
854
List<Comparable> vals = createValues();
855
if (vals == null) return null;
865
public Map<Comparable,Doc> indexDocs(List<FldType> descriptor, Map<Comparable,Doc> model, int nDocs) throws Exception {
867
model = new LinkedHashMap<Comparable,Doc>();
870
// commit an average of 10 times for large sets, or 10% of the time for small sets
871
int commitOneOutOf = Math.max(nDocs/10, 10);
873
for (int i=0; i<nDocs; i++) {
874
Doc doc = createDoc(descriptor);
875
// doc.order = order++;
876
updateJ(toJSON(doc), null);
877
model.put(doc.id, doc);
879
// commit 10% of the time
880
if (random.nextInt(commitOneOutOf)==0) {
884
// duplicate 10% of the docs
885
if (random.nextInt(10)==0) {
886
updateJ(toJSON(doc), null);
887
model.put(doc.id, doc);
891
// optimize 10% of the time
892
if (random.nextInt(10)==0) {
898
// merging segments no longer selects just adjacent segments hence ids (doc.order) can be shuffled.
899
// we need to look at the index to determine the order.
900
String responseStr = h.query(req("q","*:*", "fl","id", "sort","_docid_ asc", "rows",Integer.toString(model.size()*2), "wt","json", "indent","true"));
901
Object response = ObjectBuilder.fromJSON(responseStr);
903
response = ((Map)response).get("response");
904
response = ((Map)response).get("docs");
905
List<Map> docList = (List<Map>)response;
907
for (Map doc : docList) {
908
Object id = doc.get("id");
909
Doc modelDoc = model.get(id);
910
if (modelDoc == null) continue; // may be some docs in the index that aren't modeled
911
modelDoc.order = order++;
914
// make sure we updated the order of all docs in the model
915
assertEquals(order, model.size());
920
public static Doc createDoc(List<FldType> descriptor) {
922
doc.fields = new ArrayList<Fld>();
923
for (FldType ftype : descriptor) {
924
Fld fld = ftype.createField();
927
if ("id".equals(ftype.fname))
928
doc.id = fld.vals.get(0);
934
public static Comparator<Doc> createSort(IndexSchema schema, List<FldType> fieldTypes, String[] out) {
935
StringBuilder sortSpec = new StringBuilder();
936
int nSorts = random.nextInt(4);
937
List<Comparator<Doc>> comparators = new ArrayList<Comparator<Doc>>();
938
for (int i=0; i<nSorts; i++) {
939
if (i>0) sortSpec.append(',');
941
int which = random.nextInt(fieldTypes.size()+2);
942
boolean asc = random.nextBoolean();
943
if (which == fieldTypes.size()) {
945
sortSpec.append("score").append(asc ? " asc" : " desc");
946
comparators.add(createComparator("score", asc, false, false, false));
947
} else if (which == fieldTypes.size() + 1) {
949
sortSpec.append("_docid_").append(asc ? " asc" : " desc");
950
comparators.add(createComparator("_docid_", asc, false, false, false));
952
String field = fieldTypes.get(which).fname;
953
sortSpec.append(field).append(asc ? " asc" : " desc");
954
SchemaField sf = schema.getField(field);
955
comparators.add(createComparator(field, asc, sf.sortMissingLast(), sf.sortMissingFirst(), !(sf.sortMissingLast()||sf.sortMissingFirst()) ));
959
out[0] = sortSpec.length() > 0 ? sortSpec.toString() : null;
961
if (comparators.size() == 0) {
962
// default sort is by score desc
963
comparators.add(createComparator("score", false, false, false, false));
966
return createComparator(comparators);
969
public static Comparator<Doc> createComparator(final String field, final boolean asc, final boolean sortMissingLast, final boolean sortMissingFirst, final boolean sortMissingAsZero) {
970
final int mul = asc ? 1 : -1;
972
if (field.equals("_docid_")) {
973
return new Comparator<Doc>() {
974
public int compare(Doc o1, Doc o2) {
975
return (o1.order - o2.order) * mul;
980
if (field.equals("score")) {
981
return createComparator("score_f", asc, sortMissingLast, sortMissingFirst, sortMissingAsZero);
984
return new Comparator<Doc>() {
985
private Comparable zeroVal(Comparable template) {
986
if (template == null) return null;
987
if (template instanceof String) return null; // fast-path for string
988
if (template instanceof Integer) return 0;
989
if (template instanceof Long) return (long)0;
990
if (template instanceof Float) return (float)0;
991
if (template instanceof Double) return (double)0;
992
if (template instanceof Short) return (short)0;
993
if (template instanceof Byte) return (byte)0;
994
if (template instanceof Character) return (char)0;
998
public int compare(Doc o1, Doc o2) {
999
Comparable v1 = o1.getFirstValue(field);
1000
Comparable v2 = o2.getFirstValue(field);
1002
v1 = v1 == null ? zeroVal(v2) : v1;
1003
v2 = v2 == null ? zeroVal(v1) : v2;
1008
} else if (v1 == null) {
1009
if (sortMissingLast) c = mul;
1010
else if (sortMissingFirst) c = -mul;
1012
} else if (v2 == null) {
1013
if (sortMissingLast) c = -mul;
1014
else if (sortMissingFirst) c = mul;
1017
c = v1.compareTo(v2);
1027
public static Comparator<Doc> createComparator(final List<Comparator<Doc>> comparators) {
1028
return new Comparator<Doc>() {
1029
public int compare(Doc o1, Doc o2) {
1031
for (Comparator<Doc> comparator : comparators) {
1032
c = comparator.compare(o1, o2);
1035
return o1.order - o2.order;
1041
public static String toJSON(Doc doc) {
1042
CharArr out = new CharArr();
1044
out.append("{\"add\":{\"doc\":{");
1045
boolean firstField = true;
1046
for (Fld fld : doc.fields) {
1047
if (firstField) firstField=false;
1048
else out.append(',');
1049
JSONUtil.writeString(fld.ftype.fname, 0, fld.ftype.fname.length(), out);
1051
if (fld.vals.size() > 1) {
1054
boolean firstVal = true;
1055
for (Comparable val : fld.vals) {
1056
if (firstVal) firstVal=false;
1057
else out.append(',');
1058
out.append(JSONUtil.toJSON(val));
1060
if (fld.vals.size() > 1) {
1065
} catch (IOException e) {
1066
// should never happen
1068
return out.toString();
1071
/** Return a Map from field value to a list of document ids */
1072
Map<Comparable, List<Comparable>> invertField(Map<Comparable, Doc> model, String field) {
1073
Map<Comparable, List<Comparable>> value_to_id = new HashMap<Comparable, List<Comparable>>();
1076
for (Comparable key : model.keySet()) {
1077
Doc doc = model.get(key);
1078
List<Comparable> vals = doc.getValues(field);
1079
if (vals == null) continue;
1080
for (Comparable val : vals) {
1081
List<Comparable> ids = value_to_id.get(val);
1083
ids = new ArrayList<Comparable>(2);
1084
value_to_id.put(val, ids);
1094
/** Gets a resource from the context classloader as {@link File}. This method should only be used,
1095
* if a real file is needed. To get a stream, code should prefer
1096
* {@link Class#getResourceAsStream} using {@code this.getClass()}.
1098
public static File getFile(String name) {
1100
File file = new File(name);
1101
if (!file.exists()) {
1102
file = new File(Thread.currentThread().getContextClassLoader().getResource(name).toURI());
1105
} catch (Exception e) {
1106
/* more friendly than NPE */
1107
throw new RuntimeException("Cannot find resource: " + name);
1111
public static String TEST_HOME() {
1112
return getFile("solr/conf").getParent();
1115
public static Throwable getRootCause(Throwable t) {
1116
Throwable result = t;
1117
for (Throwable cause = t; null != cause; cause = cause.getCause()) {