~slub.team/goobi-indexserver/3.x

« back to all changes in this revision

Viewing changes to solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java

  • Committer: Sebastian Meyer
  • Date: 2012-08-03 09:12:40 UTC
  • Revision ID: sebastian.meyer@slub-dresden.de-20120803091240-x6861b0vabq1xror
Remove Lucene and Solr source code and add patches instead
Fix Bug #985487: Auto-suggestion for the search interface

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/**
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
8
 
 *
9
 
 *     http://www.apache.org/licenses/LICENSE-2.0
10
 
 *
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.
16
 
 */
17
 
 
18
 
 
19
 
package org.apache.solr;
20
 
 
21
 
 
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;
50
 
 
51
 
import javax.xml.xpath.XPathExpressionException;
52
 
import java.io.File;
53
 
import java.io.IOException;
54
 
import java.io.StringWriter;
55
 
import java.util.*;
56
 
 
57
 
/**
58
 
 * A junit4 Solr test harness that extends LuceneTestCaseJ4.
59
 
 * Unlike AbstractSolrTestCase, a new core is not created for each test method.
60
 
 *
61
 
 */
62
 
public abstract class SolrTestCaseJ4 extends LuceneTestCase {
63
 
 
64
 
  @BeforeClass
65
 
  public static void beforeClassSolrTestCase() throws Exception {
66
 
    startTrackingSearchers();
67
 
    ignoreException("ignore_exception");
68
 
  }
69
 
 
70
 
  @AfterClass
71
 
  public static void afterClassSolrTestCase() throws Exception {
72
 
    deleteCore();
73
 
    resetExceptionIgnores();
74
 
    endTrackingSearchers();
75
 
  }
76
 
  
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()) {
81
 
      if (d.isOpen()) {
82
 
        d.close();
83
 
      }
84
 
    }
85
 
  }
86
 
 
87
 
  @Override
88
 
  public void setUp() throws Exception {
89
 
    super.setUp();
90
 
    log.info("###Starting " + getName());  // returns <unknown>???
91
 
  }
92
 
 
93
 
  @Override
94
 
  public void tearDown() throws Exception {
95
 
    log.info("###Ending " + getName());
96
 
    super.tearDown();
97
 
  }
98
 
 
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());
103
 
  }
104
 
 
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);
112
 
    }
113
 
    System.setProperty("solr.velocity.enabled", "false");
114
 
    initCore();
115
 
  }
116
 
 
117
 
 
118
 
  static long numOpens;
119
 
  static long numCloses;
120
 
  public static void startTrackingSearchers() {
121
 
    numOpens = SolrIndexSearcher.numOpens.get();
122
 
    numCloses = SolrIndexSearcher.numCloses.get();
123
 
  }
124
 
 
125
 
  public static void endTrackingSearchers() {
126
 
     long endNumOpens = SolrIndexSearcher.numOpens.get();
127
 
     long endNumCloses = SolrIndexSearcher.numCloses.get();
128
 
 
129
 
     SolrIndexSearcher.numOpens.getAndSet(0);
130
 
     SolrIndexSearcher.numCloses.getAndSet(0);
131
 
 
132
 
     
133
 
     if (endNumOpens-numOpens != endNumCloses-numCloses) {
134
 
       String msg = "ERROR: SolrIndexSearcher opens=" + (endNumOpens-numOpens) + " closes=" + (endNumCloses-numCloses);
135
 
       log.error(msg);
136
 
       testsFailed = true;
137
 
       fail(msg);
138
 
     }
139
 
  }
140
 
 
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);
146
 
  }
147
 
 
148
 
  public static void resetExceptionIgnores() {
149
 
    SolrException.ignorePatterns = null;
150
 
    ignoreException("ignore_exception");  // always ignore "ignore_exception"
151
 
  }
152
 
 
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) {
159
 
        return cname;
160
 
      }
161
 
    }
162
 
    return SolrTestCaseJ4.class.getName();
163
 
  }
164
 
 
165
 
  protected static String getSimpleClassName() {
166
 
    String cname = getClassName();
167
 
    return cname.substring(cname.lastIndexOf('.')+1);
168
 
  }
169
 
 
170
 
  protected static String configString;
171
 
  protected static String schemaString;
172
 
 
173
 
  protected static SolrConfig solrConfig;
174
 
  /**
175
 
   * Harness initialized by initTestHarness.
176
 
   *
177
 
   * <p>
178
 
   * For use in test methods as needed.
179
 
   * </p>
180
 
   */
181
 
  protected static TestHarness h;
182
 
  /**
183
 
   * LocalRequestFactory initialized by initTestHarness using sensible
184
 
   * defaults.
185
 
   *
186
 
   * <p>
187
 
   * For use in test methods as needed.
188
 
   * </p>
189
 
   */
190
 
  protected static TestHarness.LocalRequestFactory lrf;
191
 
 
192
 
 
193
 
  /**
194
 
   * Subclasses must define this method to return the name of the
195
 
   * schema.xml they wish to use.
196
 
   */
197
 
  public static  String getSchemaFile() {
198
 
    return schemaString;
199
 
  };
200
 
 
201
 
  /**
202
 
   * Subclasses must define this method to return the name of the
203
 
   * solrconfig.xml they wish to use.
204
 
   */
205
 
  public static  String getSolrConfigFile() {
206
 
    return configString;
207
 
  };
208
 
 
209
 
  /**
210
 
   * The directory used to story the index managed by the TestHarness h
211
 
   */
212
 
  protected static File dataDir;
213
 
 
214
 
  /**
215
 
   * Initializes things your test might need
216
 
   *
217
 
   * <ul>
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>
221
 
   * </ul>
222
 
   *
223
 
   */
224
 
 
225
 
  public static Logger log = LoggerFactory.getLogger(SolrTestCaseJ4.class);
226
 
 
227
 
  private static String factoryProp;
228
 
 
229
 
  public static void createTempDir() {
230
 
    String cname = getSimpleClassName();
231
 
    dataDir = new File(TEMP_DIR,
232
 
            "solrtest-" + cname + "-" + System.currentTimeMillis());
233
 
    dataDir.mkdirs();
234
 
  }
235
 
 
236
 
  public static void initCore() throws Exception {
237
 
    log.info("####initCore");
238
 
 
239
 
    ignoreException("ignore_exception");
240
 
    factoryProp = System.getProperty("solr.directoryFactory");
241
 
    if (factoryProp == null) {
242
 
      System.setProperty("solr.directoryFactory","solr.RAMDirectoryFactory");
243
 
    }
244
 
    if (dataDir == null) {
245
 
      createTempDir();
246
 
    }
247
 
 
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");
251
 
 
252
 
    String configFile = getSolrConfigFile();
253
 
    if (configFile != null) {
254
 
 
255
 
      solrConfig = h.createConfig(getSolrConfigFile());
256
 
      h = new TestHarness( dataDir.getAbsolutePath(),
257
 
              solrConfig,
258
 
              getSchemaFile());
259
 
      lrf = h.getRequestFactory
260
 
              ("standard",0,20,CommonParams.VERSION,"2.2");
261
 
    }
262
 
    log.info("####initCore end");
263
 
  }
264
 
 
265
 
  /** Subclasses that override setUp can optionally call this method
266
 
   * to log the fact that their setUp process has ended.
267
 
   */
268
 
  public void postSetUp() {
269
 
    log.info("####POSTSETUP " + getName());
270
 
  }
271
 
 
272
 
 
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
276
 
   * tearDown method.
277
 
   */
278
 
  public void preTearDown() {
279
 
    log.info("####PRETEARDOWN " + getName());
280
 
  }
281
 
 
282
 
  /**
283
 
   * Shuts down the test harness, and makes the best attempt possible
284
 
   * to delete dataDir, unless the system property "solr.test.leavedatadir"
285
 
   * is set.
286
 
   */
287
 
  public static void deleteCore() throws Exception {
288
 
    log.info("###deleteCore" );
289
 
    if (h != null) { h.close(); }
290
 
    closeDirectories();
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());
295
 
      } else {
296
 
        if (!recurseDelete(dataDir)) {
297
 
          System.err.println("!!!! WARNING: best effort to remove " + dataDir.getAbsolutePath() + " FAILED !!!!!");
298
 
        }
299
 
      }
300
 
    }
301
 
 
302
 
    if (factoryProp == null) {
303
 
      System.clearProperty("solr.directoryFactory");
304
 
    }
305
 
 
306
 
    dataDir = null;
307
 
    solrConfig = null;
308
 
    h = null;
309
 
    lrf = null;
310
 
    configString = schemaString = null;
311
 
  }
312
 
 
313
 
 
314
 
  /** Validates an update XML String is successful
315
 
   */
316
 
  public static void assertU(String update) {
317
 
    assertU(null, update);
318
 
  }
319
 
 
320
 
  /** Validates an update XML String is successful
321
 
   */
322
 
  public static void assertU(String message, String update) {
323
 
    checkUpdateU(message, update, true);
324
 
  }
325
 
 
326
 
  /** Validates an update XML String failed
327
 
   */
328
 
  public static void assertFailedU(String update) {
329
 
    assertFailedU(null, update);
330
 
  }
331
 
 
332
 
  /** Validates an update XML String failed
333
 
   */
334
 
  public static void assertFailedU(String message, String update) {
335
 
    checkUpdateU(message, update, false);
336
 
  }
337
 
 
338
 
  /** Checks the success or failure of an update message
339
 
   */
340
 
  private static void checkUpdateU(String message, String update, boolean shouldSucceed) {
341
 
    try {
342
 
      String m = (null == message) ? "" : message + " ";
343
 
      if (shouldSucceed) {
344
 
           String res = h.validateUpdate(update);
345
 
         if (res != null) fail(m + "update was not successful: " + res);
346
 
      } else {
347
 
           String res = h.validateErrorUpdate(update);
348
 
         if (res != null) fail(m + "update succeeded, but should have failed: " + res);
349
 
      }
350
 
    } catch (SAXException e) {
351
 
      throw new RuntimeException("Invalid XML", e);
352
 
    }
353
 
  }
354
 
 
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);
358
 
  }
359
 
 
360
 
  /** Validates a query matches some XPath test expressions and closes the query */
361
 
  public static void assertQ(String message, SolrQueryRequest req, String... tests) {
362
 
    try {
363
 
      String m = (null == message) ? "" : message + " ";
364
 
      String response = h.query(req);
365
 
 
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]";
372
 
        tests = allTests;
373
 
      }
374
 
 
375
 
      String results = h.validateXPath(response, tests);
376
 
 
377
 
      if (null != results) {
378
 
        String msg = "REQUEST FAILED: xpath=" + results
379
 
            + "\n\txml response was: " + response
380
 
            + "\n\trequest was:" + req.getParamString();
381
 
 
382
 
        log.error(msg);
383
 
        throw new RuntimeException(msg);
384
 
      }
385
 
 
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);
391
 
    }
392
 
  }
393
 
 
394
 
  /**
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...)
398
 
   */
399
 
  public static void assertJQ(SolrQueryRequest req, String... tests) throws Exception {
400
 
    assertJQ(req, JSONTestUtil.DEFAULT_DELTA, tests);
401
 
  }
402
 
  /**
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.
407
 
   * <p>
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.
411
 
   * </p>
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
415
 
   */
416
 
  public static void assertJQ(SolrQueryRequest req, double delta, String... tests) throws Exception {
417
 
    SolrParams params =  null;
418
 
    try {
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);
425
 
      }
426
 
 
427
 
      String response;
428
 
      boolean failed=true;
429
 
      try {
430
 
        response = h.query(req);
431
 
        failed = false;
432
 
      } finally {
433
 
        if (failed) {
434
 
          log.error("REQUEST FAILED: " + req.getParamString());
435
 
        }
436
 
      }
437
 
 
438
 
      for (String test : tests) {
439
 
        if (test == null || test.length()==0) continue;
440
 
        String testJSON = test.replace('\'', '"');
441
 
 
442
 
        try {
443
 
          failed = true;
444
 
          String err = JSONTestUtil.match(response, testJSON, delta);
445
 
          failed = false;
446
 
          if (err != null) {
447
 
            log.error("query failed JSON validation. error=" + err +
448
 
                "\n expected =" + testJSON +
449
 
                "\n response = " + response +
450
 
                "\n request = " + req.getParamString()
451
 
            );
452
 
            throw new RuntimeException(err);
453
 
          }
454
 
        } finally {
455
 
          if (failed) {
456
 
            log.error("JSON query validation threw an exception." +
457
 
                "\n expected =" + testJSON +
458
 
                "\n response = " + response +
459
 
                "\n request = " + req.getParamString()
460
 
            );
461
 
          }
462
 
        }
463
 
      }
464
 
    } finally {
465
 
      // restore the params
466
 
      if (params != null && params != req.getParams()) req.setParams(params);
467
 
    }
468
 
  }
469
 
 
470
 
 
471
 
  /** Makes sure a query throws a SolrException with the listed response code */
472
 
  public static void assertQEx(String message, SolrQueryRequest req, int code ) {
473
 
    try {
474
 
      h.query(req);
475
 
      fail( message );
476
 
    } catch (SolrException sex) {
477
 
      assertEquals( code, sex.code() );
478
 
    } catch (Exception e2) {
479
 
      throw new RuntimeException("Exception during query", e2);
480
 
    }
481
 
  }
482
 
 
483
 
  public static void assertQEx(String message, SolrQueryRequest req, SolrException.ErrorCode code ) {
484
 
    try {
485
 
      h.query(req);
486
 
      fail( message );
487
 
    } catch (SolrException e) {
488
 
      assertEquals( code.code, e.code() );
489
 
    } catch (Exception e2) {
490
 
      throw new RuntimeException("Exception during query", e2);
491
 
    }
492
 
  }
493
 
 
494
 
 
495
 
  /**
496
 
   * @see TestHarness#optimize
497
 
   */
498
 
  public static String optimize(String... args) {
499
 
    return h.optimize(args);
500
 
  }
501
 
  /**
502
 
   * @see TestHarness#commit
503
 
   */
504
 
  public static String commit(String... args) {
505
 
    return h.commit(args);
506
 
  }
507
 
 
508
 
  /**
509
 
   * Generates a simple &lt;add&gt;&lt;doc&gt;... XML String with no options
510
 
   *
511
 
   * @param fieldsAndValues 0th and Even numbered args are fields names odds are field values.
512
 
   * @see #add
513
 
   * @see #doc
514
 
   */
515
 
  public static String adoc(String... fieldsAndValues) {
516
 
    XmlDoc d = doc(fieldsAndValues);
517
 
    return add(d);
518
 
  }
519
 
 
520
 
  /**
521
 
   * Generates a simple &lt;add&gt;&lt;doc&gt;... XML String with no options
522
 
   */
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());
529
 
      }
530
 
    }
531
 
    return adoc(fields.toArray(new String[fields.size()]));
532
 
  }
533
 
 
534
 
 
535
 
  /**
536
 
   * Generates an &lt;add&gt;&lt;doc&gt;... XML String with options
537
 
   * on the add.
538
 
   *
539
 
   * @param doc the Document to add
540
 
   * @param args 0th and Even numbered args are param names, Odds are param values.
541
 
   * @see #add
542
 
   * @see #doc
543
 
   */
544
 
  public static String add(XmlDoc doc, String... args) {
545
 
    try {
546
 
      StringWriter r = new StringWriter();
547
 
 
548
 
      // this is anoying
549
 
      if (null == args || 0 == args.length) {
550
 
        r.write("<add>");
551
 
        r.write(doc.xml);
552
 
        r.write("</add>");
553
 
      } else {
554
 
        XML.writeUnescapedXML(r, "add", doc.xml, (Object[])args);
555
 
      }
556
 
 
557
 
      return r.getBuffer().toString();
558
 
    } catch (IOException e) {
559
 
      throw new RuntimeException
560
 
        ("this should never happen with a StringWriter", e);
561
 
    }
562
 
  }
563
 
 
564
 
  /**
565
 
   * Generates a &lt;delete&gt;... XML string for an ID
566
 
   *
567
 
   * @see TestHarness#deleteById
568
 
   */
569
 
  public static String delI(String id) {
570
 
    return h.deleteById(id);
571
 
  }
572
 
  /**
573
 
   * Generates a &lt;delete&gt;... XML string for an query
574
 
   *
575
 
   * @see TestHarness#deleteByQuery
576
 
   */
577
 
  public static String delQ(String q) {
578
 
    return h.deleteByQuery(q);
579
 
  }
580
 
 
581
 
  /**
582
 
   * Generates a simple &lt;doc&gt;... XML String with no options
583
 
   *
584
 
   * @param fieldsAndValues 0th and Even numbered args are fields names, Odds are field values.
585
 
   * @see TestHarness#makeSimpleDoc
586
 
   */
587
 
  public static XmlDoc doc(String... fieldsAndValues) {
588
 
    XmlDoc d = new XmlDoc();
589
 
    d.xml = h.makeSimpleDoc(fieldsAndValues).toString();
590
 
    return d;
591
 
  }
592
 
 
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]);
597
 
    }
598
 
    return msp;
599
 
  }
600
 
 
601
 
  /**
602
 
   * Generates a SolrQueryRequest using the LocalRequestFactory
603
 
   * @see #lrf
604
 
   */
605
 
  public static SolrQueryRequest req(String... q) {
606
 
    return lrf.makeRequest(q);
607
 
  }
608
 
 
609
 
  /**
610
 
   * Generates a SolrQueryRequest using the LocalRequestFactory
611
 
   * @see #lrf
612
 
   */
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);
620
 
    }
621
 
 
622
 
    return lrf.makeRequest(allParams);
623
 
  }
624
 
 
625
 
  /**
626
 
   * Generates a SolrQueryRequest
627
 
   */
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]);
632
 
    }
633
 
    return new LocalSolrQueryRequest(h.getCore(), mp);
634
 
  }
635
 
 
636
 
  /** Neccessary to make method signatures un-ambiguous */
637
 
  public static class XmlDoc {
638
 
    public String xml;
639
 
    @Override
640
 
    public String toString() { return xml; }
641
 
  }
642
 
 
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 !!!!!");
648
 
          return false;
649
 
        }
650
 
      }
651
 
    }
652
 
    return f.delete();
653
 
  }
654
 
 
655
 
  public void clearIndex() {
656
 
    assertU(delQ("*:*"));
657
 
  }
658
 
 
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();
666
 
      handler.init(null);
667
 
    }
668
 
    return connection.request(handler, args, json);
669
 
  }
670
 
 
671
 
 
672
 
  /////////////////////////////////////////////////////////////////////////////////////
673
 
  //////////////////////////// random document / index creation ///////////////////////
674
 
  /////////////////////////////////////////////////////////////////////////////////////
675
 
 
676
 
  public abstract static class Vals {
677
 
    public abstract Comparable get();
678
 
    public String toJSON(Comparable val) {
679
 
      return JSONUtil.toJSON(val);
680
 
    }
681
 
 
682
 
    protected int between(int min, int max) {
683
 
      return min != max ? random.nextInt(max-min+1) + min : min;
684
 
    }
685
 
  }
686
 
 
687
 
  public abstract static class IVals extends Vals {
688
 
    public abstract int getInt();
689
 
  }
690
 
 
691
 
  public static class IRange extends IVals {
692
 
    final int min;
693
 
    final int max;
694
 
    public IRange(int min, int max) {
695
 
      this.min = min;
696
 
      this.max = max;
697
 
    }
698
 
 
699
 
    @Override
700
 
    public int getInt() {
701
 
      return between(min,max);
702
 
    }
703
 
 
704
 
    @Override
705
 
    public Comparable get() {
706
 
      return getInt();
707
 
    }
708
 
  }
709
 
 
710
 
  public static class FVal extends Vals {
711
 
    final float min;
712
 
    final float max;
713
 
    public FVal(float min, float max) {
714
 
      this.min = min;
715
 
      this.max = max;
716
 
    }
717
 
 
718
 
    public float getFloat() {
719
 
      if (min >= max) return min;
720
 
      return min + random.nextFloat() *  (max - min);
721
 
    }
722
 
 
723
 
    @Override
724
 
    public Comparable get() {
725
 
      return getFloat();
726
 
    }
727
 
  }
728
 
 
729
 
  public static class SVal extends Vals {
730
 
    char start;
731
 
    char end;
732
 
    int minLength;
733
 
    int maxLength;
734
 
 
735
 
    public SVal() {
736
 
      this('a','z',1,10);
737
 
    }
738
 
 
739
 
    public SVal(char start, char end, int minLength, int maxLength) {
740
 
      this.start = start;
741
 
      this.end = end;
742
 
      this.minLength = minLength;
743
 
      this.maxLength = maxLength;
744
 
    }
745
 
 
746
 
    @Override
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);
751
 
      }
752
 
      return new String(arr);
753
 
    }
754
 
  }
755
 
 
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);
759
 
 
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
764
 
 
765
 
 
766
 
    @Override
767
 
    public String toString() {
768
 
      return "Doc("+order+"):"+fields.toString();
769
 
    }
770
 
 
771
 
    @Override
772
 
    public int hashCode() {
773
 
      return id.hashCode();
774
 
    }
775
 
 
776
 
    @Override
777
 
    public boolean equals(Object o) {
778
 
      if (!(o instanceof Doc)) return false;
779
 
      Doc other = (Doc)o;
780
 
      return this==other || id != null && id.equals(other.id);
781
 
    }
782
 
 
783
 
    public int compareTo(Object o) {
784
 
      if (!(o instanceof Doc)) return this.getClass().hashCode() - o.getClass().hashCode();
785
 
      Doc other = (Doc)o;
786
 
      return this.id.compareTo(other.id);
787
 
    }
788
 
 
789
 
    public List<Comparable> getValues(String field) {
790
 
      for (Fld fld : fields) {
791
 
        if (fld.ftype.fname.equals(field)) return fld.vals;
792
 
      }
793
 
      return null;
794
 
    }
795
 
 
796
 
    public Comparable getFirstValue(String field) {
797
 
      List<Comparable> vals = getValues(field);
798
 
      return vals==null || vals.size()==0 ? null : vals.get(0);
799
 
    }
800
 
 
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));
807
 
        } else {
808
 
          result.put(fld.ftype.fname, fld.vals);
809
 
        }
810
 
      }
811
 
      return result;
812
 
    }
813
 
 
814
 
  }
815
 
 
816
 
  public static class Fld {
817
 
    public FldType ftype;
818
 
    public List<Comparable> vals;
819
 
    @Override
820
 
    public String toString() {
821
 
      return ftype.fname + "=" + (vals.size()==1 ? vals.get(0).toString() : vals.toString());
822
 
    }
823
 
  }
824
 
 
825
 
  class FldType {
826
 
    public String fname;
827
 
    public IRange numValues;
828
 
    public Vals vals;
829
 
 
830
 
    public FldType(String fname, Vals vals) {
831
 
      this(fname, ZERO_ONE, vals);
832
 
    }
833
 
 
834
 
    public FldType(String fname, IRange numValues, Vals vals) {
835
 
      this.fname = fname;
836
 
      this.numValues = numValues;
837
 
      this.vals = vals;
838
 
    }
839
 
 
840
 
    public Comparable createValue() {
841
 
      return vals.get();
842
 
    }
843
 
 
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());
850
 
      return vals;
851
 
    }
852
 
 
853
 
    public Fld createField() {
854
 
      List<Comparable> vals = createValues();
855
 
      if (vals == null) return null;
856
 
 
857
 
      Fld fld = new Fld();
858
 
      fld.ftype = this;
859
 
      fld.vals = vals;
860
 
      return fld;
861
 
    }
862
 
 
863
 
  }
864
 
 
865
 
  public Map<Comparable,Doc> indexDocs(List<FldType> descriptor, Map<Comparable,Doc> model, int nDocs) throws Exception {
866
 
    if (model == null) {
867
 
      model = new LinkedHashMap<Comparable,Doc>();
868
 
    }
869
 
 
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);
872
 
 
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);
878
 
 
879
 
      // commit 10% of the time
880
 
      if (random.nextInt(commitOneOutOf)==0) {
881
 
        assertU(commit());
882
 
      }
883
 
 
884
 
      // duplicate 10% of the docs
885
 
      if (random.nextInt(10)==0) {
886
 
        updateJ(toJSON(doc), null);
887
 
        model.put(doc.id, doc);
888
 
      }
889
 
    }
890
 
 
891
 
    // optimize 10% of the time
892
 
    if (random.nextInt(10)==0) {
893
 
      assertU(optimize());
894
 
    } else {
895
 
      assertU(commit());
896
 
    }
897
 
 
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);
902
 
 
903
 
    response = ((Map)response).get("response");
904
 
    response = ((Map)response).get("docs");
905
 
    List<Map> docList = (List<Map>)response;
906
 
    int order = 0;
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++;
912
 
    }
913
 
 
914
 
    // make sure we updated the order of all docs in the model
915
 
    assertEquals(order, model.size());
916
 
 
917
 
    return model;
918
 
  }
919
 
 
920
 
  public static Doc createDoc(List<FldType> descriptor) {
921
 
    Doc doc = new Doc();
922
 
    doc.fields = new ArrayList<Fld>();
923
 
    for (FldType ftype : descriptor) {
924
 
      Fld fld = ftype.createField();
925
 
      if (fld != null) {
926
 
        doc.fields.add(fld);
927
 
        if ("id".equals(ftype.fname))
928
 
          doc.id = fld.vals.get(0);
929
 
      }
930
 
    }
931
 
    return doc;
932
 
  }
933
 
 
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(',');
940
 
 
941
 
      int which = random.nextInt(fieldTypes.size()+2);
942
 
      boolean asc = random.nextBoolean();
943
 
      if (which == fieldTypes.size()) {
944
 
        // sort by score
945
 
        sortSpec.append("score").append(asc ? " asc" : " desc");
946
 
        comparators.add(createComparator("score", asc, false, false, false));
947
 
      } else if (which == fieldTypes.size() + 1) {
948
 
        // sort by docid
949
 
        sortSpec.append("_docid_").append(asc ? " asc" : " desc");
950
 
        comparators.add(createComparator("_docid_", asc, false, false, false));
951
 
      } else {
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()) ));
956
 
      }
957
 
    }
958
 
 
959
 
    out[0] = sortSpec.length() > 0 ? sortSpec.toString() : null;
960
 
 
961
 
    if (comparators.size() == 0) {
962
 
      // default sort is by score desc
963
 
      comparators.add(createComparator("score", false, false, false, false));
964
 
    }
965
 
 
966
 
    return createComparator(comparators);
967
 
  }
968
 
 
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;
971
 
 
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;
976
 
      }
977
 
     };
978
 
    }
979
 
 
980
 
    if (field.equals("score")) {
981
 
      return createComparator("score_f", asc, sortMissingLast, sortMissingFirst, sortMissingAsZero);
982
 
    }
983
 
 
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;
995
 
        return null;
996
 
      }
997
 
 
998
 
      public int compare(Doc o1, Doc o2) {
999
 
        Comparable v1 = o1.getFirstValue(field);
1000
 
        Comparable v2 = o2.getFirstValue(field);
1001
 
 
1002
 
        v1 = v1 == null ? zeroVal(v2) : v1;
1003
 
        v2 = v2 == null ? zeroVal(v1) : v2;
1004
 
 
1005
 
        int c = 0;
1006
 
        if (v1 == v2) {
1007
 
          c = 0;
1008
 
        } else if (v1 == null) {
1009
 
          if (sortMissingLast) c = mul;
1010
 
          else if (sortMissingFirst) c = -mul;
1011
 
          else c = -1;
1012
 
        } else if (v2 == null) {
1013
 
          if (sortMissingLast) c = -mul;
1014
 
          else if (sortMissingFirst) c = mul;
1015
 
          else c = 1;
1016
 
        } else {
1017
 
          c = v1.compareTo(v2);
1018
 
        }
1019
 
 
1020
 
        c = c * mul;
1021
 
 
1022
 
        return c;
1023
 
      }
1024
 
    };
1025
 
  }
1026
 
 
1027
 
  public static Comparator<Doc> createComparator(final List<Comparator<Doc>> comparators) {
1028
 
    return new Comparator<Doc>() {
1029
 
      public int compare(Doc o1, Doc o2) {
1030
 
        int c = 0;
1031
 
        for (Comparator<Doc> comparator : comparators) {
1032
 
          c = comparator.compare(o1, o2);
1033
 
          if (c!=0) return c;
1034
 
        }
1035
 
        return o1.order - o2.order;
1036
 
      }
1037
 
    };
1038
 
  }
1039
 
 
1040
 
 
1041
 
  public static String toJSON(Doc doc) {
1042
 
    CharArr out = new CharArr();
1043
 
    try {
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);
1050
 
        out.append(':');
1051
 
        if (fld.vals.size() > 1) {
1052
 
          out.append('[');
1053
 
        }
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));
1059
 
        }
1060
 
        if (fld.vals.size() > 1) {
1061
 
          out.append(']');
1062
 
        }
1063
 
      }
1064
 
      out.append("}}}");
1065
 
    } catch (IOException e) {
1066
 
      // should never happen
1067
 
    }
1068
 
    return out.toString();
1069
 
  }
1070
 
 
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>>();
1074
 
 
1075
 
    // invert field
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);
1082
 
        if (ids == null) {
1083
 
          ids = new ArrayList<Comparable>(2);
1084
 
          value_to_id.put(val, ids);
1085
 
        }
1086
 
        ids.add(key);
1087
 
      }
1088
 
    }
1089
 
 
1090
 
    return value_to_id;
1091
 
  }
1092
 
 
1093
 
 
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()}.
1097
 
   */
1098
 
  public static File getFile(String name) {
1099
 
    try {
1100
 
      File file = new File(name);
1101
 
      if (!file.exists()) {
1102
 
        file = new File(Thread.currentThread().getContextClassLoader().getResource(name).toURI());
1103
 
      }
1104
 
      return file;
1105
 
    } catch (Exception e) {
1106
 
      /* more friendly than NPE */
1107
 
      throw new RuntimeException("Cannot find resource: " + name);
1108
 
    }
1109
 
  }
1110
 
 
1111
 
  public static String TEST_HOME() {
1112
 
    return getFile("solr/conf").getParent();
1113
 
  }
1114
 
 
1115
 
  public static Throwable getRootCause(Throwable t) {
1116
 
    Throwable result = t;
1117
 
    for (Throwable cause = t; null != cause; cause = cause.getCause()) {
1118
 
      result = cause;
1119
 
    }
1120
 
    return result;
1121
 
  }
1122
 
}