1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package org.modeshape.search.lucene;
25
26 import java.io.IOException;
27 import java.io.StringReader;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.HashSet;
32 import java.util.LinkedList;
33 import java.util.List;
34 import java.util.Set;
35 import java.util.UUID;
36 import net.jcip.annotations.NotThreadSafe;
37 import org.apache.lucene.analysis.Analyzer;
38 import org.apache.lucene.analysis.TokenStream;
39 import org.apache.lucene.analysis.tokenattributes.TermAttribute;
40 import org.apache.lucene.document.Document;
41 import org.apache.lucene.document.Field;
42 import org.apache.lucene.document.FieldSelector;
43 import org.apache.lucene.document.FieldSelectorResult;
44 import org.apache.lucene.document.NumericField;
45 import org.apache.lucene.document.Field.Index;
46 import org.apache.lucene.document.Field.Store;
47 import org.apache.lucene.index.IndexReader;
48 import org.apache.lucene.index.IndexWriter;
49 import org.apache.lucene.index.Term;
50 import org.apache.lucene.index.IndexWriter.MaxFieldLength;
51 import org.apache.lucene.queryParser.ParseException;
52 import org.apache.lucene.queryParser.QueryParser;
53 import org.apache.lucene.search.BooleanQuery;
54 import org.apache.lucene.search.IndexSearcher;
55 import org.apache.lucene.search.MatchAllDocsQuery;
56 import org.apache.lucene.search.NumericRangeQuery;
57 import org.apache.lucene.search.PrefixQuery;
58 import org.apache.lucene.search.Query;
59 import org.apache.lucene.search.ScoreDoc;
60 import org.apache.lucene.search.Scorer;
61 import org.apache.lucene.search.TermQuery;
62 import org.apache.lucene.search.TopDocs;
63 import org.apache.lucene.search.BooleanClause.Occur;
64 import org.apache.lucene.store.Directory;
65 import org.apache.lucene.util.Version;
66 import org.modeshape.common.util.Logger;
67 import org.modeshape.graph.JcrLexicon;
68 import org.modeshape.graph.Location;
69 import org.modeshape.graph.ModeShapeIntLexicon;
70 import org.modeshape.graph.ModeShapeLexicon;
71 import org.modeshape.graph.ModeShapeIntLexicon.Namespace;
72 import org.modeshape.graph.property.DateTime;
73 import org.modeshape.graph.property.Name;
74 import org.modeshape.graph.property.Path;
75 import org.modeshape.graph.property.Property;
76 import org.modeshape.graph.property.ValueFactories;
77 import org.modeshape.graph.property.ValueFactory;
78 import org.modeshape.graph.property.basic.BasicName;
79 import org.modeshape.graph.query.QueryResults.Columns;
80 import org.modeshape.graph.query.QueryResults.Statistics;
81 import org.modeshape.graph.query.model.Length;
82 import org.modeshape.graph.query.model.NodeDepth;
83 import org.modeshape.graph.query.model.NodeLocalName;
84 import org.modeshape.graph.query.model.NodeName;
85 import org.modeshape.graph.query.model.NodePath;
86 import org.modeshape.graph.query.model.Operator;
87 import org.modeshape.graph.query.model.PropertyValue;
88 import org.modeshape.graph.query.model.ReferenceValue;
89 import org.modeshape.search.lucene.AbstractLuceneSearchEngine.TupleCollector;
90 import org.modeshape.search.lucene.AbstractLuceneSearchEngine.WorkspaceSession;
91 import org.modeshape.search.lucene.IndexRules.FieldType;
92 import org.modeshape.search.lucene.IndexRules.NumericRule;
93 import org.modeshape.search.lucene.IndexRules.Rule;
94 import org.modeshape.search.lucene.LuceneSearchWorkspace.ContentIndex;
95 import org.modeshape.search.lucene.query.CompareLengthQuery;
96 import org.modeshape.search.lucene.query.CompareNameQuery;
97 import org.modeshape.search.lucene.query.ComparePathQuery;
98 import org.modeshape.search.lucene.query.CompareStringQuery;
99 import org.modeshape.search.lucene.query.MatchNoneQuery;
100 import org.modeshape.search.lucene.query.NotQuery;
101
102
103
104
105 @NotThreadSafe
106 public class LuceneSearchSession implements WorkspaceSession {
107
108 protected static final Set<Name> NON_SEARCHABLE_NAMES = Collections.unmodifiableSet(new HashSet<Name>(
109 Arrays.asList(JcrLexicon.UUID,
110 ModeShapeLexicon.UUID,
111 JcrLexicon.PRIMARY_TYPE,
112 JcrLexicon.MIXIN_TYPES,
113 ModeShapeIntLexicon.NODE_DEFINITON,
114 new BasicName(
115 Namespace.URI,
116 "multiValuedProperties"))));
117
118
119
120
121 protected static final FieldSelector LOCATION_FIELDS_SELECTOR = new FieldSelector() {
122 private static final long serialVersionUID = 1L;
123
124 public FieldSelectorResult accept( String fieldName ) {
125 if (ContentIndex.PATH.equals(fieldName) || ContentIndex.LOCATION_ID_PROPERTIES.equals(fieldName)) {
126 return FieldSelectorResult.LOAD;
127 }
128 return FieldSelectorResult.NO_LOAD;
129 }
130 };
131
132 protected static final int MIN_DEPTH = 0;
133 protected static final int MAX_DEPTH = 100;
134 protected static final int MIN_SNS_INDEX = 1;
135 protected static final int MAX_SNS_INDEX = 1000;
136
137 private final LuceneSearchWorkspace workspace;
138 protected final LuceneSearchProcessor processor;
139 private final Directory contentIndexDirectory;
140 private IndexReader contentReader;
141 private IndexWriter contentWriter;
142 private IndexSearcher contentSearcher;
143 private int numChanges;
144 private final Logger logger = Logger.getLogger(getClass());
145
146 protected LuceneSearchSession( LuceneSearchWorkspace workspace,
147 LuceneSearchProcessor processor ) {
148 assert workspace != null;
149 assert processor != null;
150 this.workspace = workspace;
151 this.contentIndexDirectory = workspace.contentDirectory;
152 this.processor = processor;
153 }
154
155
156
157
158
159
160 public String getWorkspaceName() {
161 return workspace.getWorkspaceName();
162 }
163
164
165
166
167 public LuceneSearchWorkspace getWorkspace() {
168 return workspace;
169 }
170
171 protected IndexReader getContentReader() throws IOException {
172 if (contentReader == null) {
173 try {
174 contentReader = IndexReader.open(contentIndexDirectory, processor.readOnly);
175 } catch (IOException e) {
176
177 IndexWriter writer = new IndexWriter(contentIndexDirectory, workspace.analyzer, MaxFieldLength.UNLIMITED);
178 writer.close();
179
180 contentReader = IndexReader.open(contentIndexDirectory, processor.readOnly);
181 }
182 }
183 return contentReader;
184 }
185
186 protected IndexWriter getContentWriter() throws IOException {
187 assert !processor.readOnly;
188 if (contentWriter == null) {
189
190 contentWriter = new IndexWriter(contentIndexDirectory, workspace.analyzer, MaxFieldLength.UNLIMITED);
191 }
192 return contentWriter;
193 }
194
195 public IndexSearcher getContentSearcher() throws IOException {
196 if (contentSearcher == null) {
197 contentSearcher = new IndexSearcher(getContentReader());
198 }
199 return contentSearcher;
200 }
201
202
203
204
205
206
207 public Analyzer getAnalyzer() {
208 return workspace.analyzer;
209 }
210
211 public boolean hasWriters() {
212 return contentWriter != null;
213 }
214
215 protected final void recordChange() {
216 ++numChanges;
217 }
218
219 protected final void recordChanges( int numberOfChanges ) {
220 assert numberOfChanges >= 0;
221 numChanges += numberOfChanges;
222 }
223
224
225
226
227
228
229 public final int getChangeCount() {
230 return numChanges;
231 }
232
233
234
235
236
237
238 public void commit() {
239 if (logger.isTraceEnabled() && numChanges > 0) {
240 logger.trace("index for \"{0}\" workspace: COMMIT", workspace.getWorkspaceName());
241 }
242
243
244 final boolean optimize = workspace.isOptimizationRequired(numChanges);
245 numChanges = 0;
246
247 IOException ioError = null;
248 RuntimeException runtimeError = null;
249 if (contentReader != null) {
250 try {
251 contentReader.close();
252 } catch (IOException e) {
253 ioError = e;
254 } catch (RuntimeException e) {
255 runtimeError = e;
256 } finally {
257 contentReader = null;
258 }
259 }
260 if (contentWriter != null) {
261 try {
262 if (optimize) contentWriter.optimize();
263 } catch (IOException e) {
264 if (ioError == null) ioError = e;
265 } catch (RuntimeException e) {
266 if (runtimeError == null) runtimeError = e;
267 } finally {
268 try {
269 contentWriter.close();
270 } catch (IOException e) {
271 if (ioError == null) ioError = e;
272 } catch (RuntimeException e) {
273 if (runtimeError == null) runtimeError = e;
274 } finally {
275 contentWriter = null;
276 }
277 }
278 }
279 if (ioError != null) {
280 String msg = LuceneI18n.errorWhileCommittingIndexChanges.text(workspace.getWorkspaceName(),
281 processor.getSourceName(),
282 ioError.getMessage());
283 throw new LuceneException(msg, ioError);
284 }
285 if (runtimeError != null) throw runtimeError;
286 }
287
288
289
290
291
292
293 public void rollback() {
294 if (logger.isTraceEnabled() && numChanges > 0) {
295 logger.trace("index for \"{0}\" workspace: ROLLBACK", workspace.getWorkspaceName());
296 }
297 numChanges = 0;
298 IOException ioError = null;
299 RuntimeException runtimeError = null;
300 if (contentReader != null) {
301 try {
302 contentReader.close();
303 } catch (IOException e) {
304 ioError = e;
305 } catch (RuntimeException e) {
306 runtimeError = e;
307 } finally {
308 contentReader = null;
309 }
310 }
311 if (contentWriter != null) {
312 try {
313 contentWriter.rollback();
314 } catch (IOException e) {
315 if (ioError == null) ioError = e;
316 } catch (RuntimeException e) {
317 if (runtimeError == null) runtimeError = e;
318 } finally {
319 try {
320 contentWriter.close();
321 } catch (IOException e) {
322 ioError = e;
323 } catch (RuntimeException e) {
324 runtimeError = e;
325 } finally {
326 contentWriter = null;
327 }
328 }
329 }
330 if (ioError != null) {
331 String msg = LuceneI18n.errorWhileRollingBackIndexChanges.text(workspace.getWorkspaceName(),
332 processor.getSourceName(),
333 ioError.getMessage());
334 throw new LuceneException(msg, ioError);
335 }
336 if (runtimeError != null) throw runtimeError;
337 }
338
339 protected Statistics search( String fullTextSearchExpression,
340 List<Object[]> results,
341 int maxRows,
342 int offset ) throws ParseException, IOException {
343
344 long planningNanos = System.nanoTime();
345 QueryParser parser = new QueryParser(Version.LUCENE_29, ContentIndex.FULL_TEXT, workspace.analyzer);
346 Query query = parser.parse(fullTextSearchExpression);
347 planningNanos = System.nanoTime() - planningNanos;
348
349
350 TopDocs docs = getContentSearcher().search(query, maxRows + offset);
351 IndexReader contentReader = getContentReader();
352 ScoreDoc[] scoreDocs = docs.scoreDocs;
353 int numberOfResults = scoreDocs.length;
354 if (numberOfResults > offset) {
355
356 for (int i = offset, num = scoreDocs.length; i != num; ++i) {
357 ScoreDoc result = scoreDocs[i];
358 int docId = result.doc;
359
360 Document doc = contentReader.document(docId, LOCATION_FIELDS_SELECTOR);
361 Location location = readLocation(doc);
362
363 results.add(new Object[] {location, result.score});
364 }
365 }
366 long executionNanos = System.nanoTime() - planningNanos;
367 return new Statistics(planningNanos, 0L, 0L, executionNanos);
368 }
369
370 protected Location readLocation( Document doc ) {
371
372 String pathString = doc.get(ContentIndex.PATH);
373 Path path = processor.pathFactory.create(pathString);
374
375 String[] idProps = doc.getValues(ContentIndex.LOCATION_ID_PROPERTIES);
376 if (idProps.length == 0) {
377 return Location.create(path);
378 }
379 if (idProps.length == 1) {
380 Property idProp = processor.deserializeProperty(idProps[0]);
381 if (idProp == null) return Location.create(path);
382 if (idProp.isSingle() && (idProp.getName().equals(JcrLexicon.UUID) || idProp.getName().equals(ModeShapeLexicon.UUID))) {
383 return Location.create(path, (UUID)idProp.getFirstValue());
384 }
385 return Location.create(path, idProp);
386 }
387 List<Property> properties = new LinkedList<Property>();
388 for (String idProp : idProps) {
389 Property prop = processor.deserializeProperty(idProp);
390 if (prop != null) properties.add(prop);
391 }
392 return properties.isEmpty() ? Location.create(path) : Location.create(path, properties);
393 }
394
395 protected void setOrReplaceProperties( Location location,
396 Iterable<Property> properties ) throws IOException {
397
398 Document doc = new Document();
399
400
401 Path path = location.getPath();
402 String pathStr = processor.pathAsString(path);
403 String nameStr = path.isRoot() ? "" : processor.stringFactory.create(path.getLastSegment().getName());
404 String localNameStr = path.isRoot() ? "" : path.getLastSegment().getName().getLocalName();
405 int sns = path.isRoot() ? 1 : path.getLastSegment().getIndex();
406
407
408
409 doc.add(new Field(ContentIndex.PATH, pathStr, Field.Store.YES, Field.Index.NOT_ANALYZED));
410 doc.add(new Field(ContentIndex.NODE_NAME, nameStr, Field.Store.YES, Field.Index.NOT_ANALYZED));
411 doc.add(new Field(ContentIndex.LOCAL_NAME, localNameStr, Field.Store.YES, Field.Index.NOT_ANALYZED));
412 doc.add(new NumericField(ContentIndex.SNS_INDEX, Field.Store.YES, true).setIntValue(sns));
413 doc.add(new NumericField(ContentIndex.DEPTH, Field.Store.YES, true).setIntValue(path.size()));
414 if (location.hasIdProperties()) {
415 for (Property idProp : location.getIdProperties()) {
416 String fieldValue = processor.serializeProperty(idProp);
417 doc.add(new Field(ContentIndex.LOCATION_ID_PROPERTIES, fieldValue, Field.Store.YES, Field.Index.NOT_ANALYZED));
418 }
419 }
420
421
422 StringBuilder fullTextSearchValue = new StringBuilder();
423 fullTextSearchValue.append(localNameStr);
424
425
426 String stringValue = null;
427 for (Property property : properties) {
428 Name name = property.getName();
429 Rule rule = workspace.rules.getRule(name);
430 if (rule.isSkipped()) continue;
431 String nameString = processor.stringFactory.create(name);
432 FieldType type = rule.getType();
433 if (type == FieldType.DATE) {
434 boolean index = rule.getIndexOption() != Field.Index.NO;
435 for (Object value : property) {
436 if (value == null) continue;
437
438 DateTime dateValue = processor.dateFactory.create(value);
439 long longValue = dateValue.getMillisecondsInUtc();
440 doc.add(new NumericField(nameString, rule.getStoreOption(), index).setLongValue(longValue));
441 }
442 continue;
443 }
444 if (type == FieldType.INT) {
445 ValueFactory<Long> longFactory = processor.valueFactories.getLongFactory();
446 boolean index = rule.getIndexOption() != Field.Index.NO;
447 for (Object value : property) {
448 if (value == null) continue;
449
450 int intValue = longFactory.create(value).intValue();
451 doc.add(new NumericField(nameString, rule.getStoreOption(), index).setIntValue(intValue));
452 }
453 continue;
454 }
455 if (type == FieldType.DOUBLE) {
456 ValueFactory<Double> doubleFactory = processor.valueFactories.getDoubleFactory();
457 boolean index = rule.getIndexOption() != Field.Index.NO;
458 for (Object value : property) {
459 if (value == null) continue;
460
461 double dValue = doubleFactory.create(value);
462 doc.add(new NumericField(nameString, rule.getStoreOption(), index).setDoubleValue(dValue));
463 }
464 continue;
465 }
466 if (type == FieldType.FLOAT) {
467 ValueFactory<Double> doubleFactory = processor.valueFactories.getDoubleFactory();
468 boolean index = rule.getIndexOption() != Field.Index.NO;
469 for (Object value : property) {
470 if (value == null) continue;
471
472 float fValue = doubleFactory.create(value).floatValue();
473 doc.add(new NumericField(nameString, rule.getStoreOption(), index).setFloatValue(fValue));
474 }
475 continue;
476 }
477 if (type == FieldType.BINARY) {
478
479 continue;
480 }
481 if (type == FieldType.WEAK_REFERENCE) {
482 ValueFactory<Path> pathFactory = processor.valueFactories.getPathFactory();
483 for (Object value : property) {
484 if (value == null) continue;
485
486 String valueStr = processor.stringFactory.create(pathFactory.create(value));
487 doc.add(new Field(nameString, valueStr, rule.getStoreOption(), Field.Index.NOT_ANALYZED));
488 }
489 continue;
490 }
491 if (type == FieldType.REFERENCE) {
492 for (Object value : property) {
493 if (value == null) continue;
494
495 stringValue = processor.stringFactory.create(value);
496
497 doc.add(new Field(nameString, stringValue, rule.getStoreOption(), Field.Index.NOT_ANALYZED));
498
499 doc.add(new Field(ContentIndex.REFERENCES, stringValue, Field.Store.NO, Field.Index.NOT_ANALYZED));
500 }
501 continue;
502 }
503 assert type == FieldType.STRING;
504 for (Object value : property) {
505 if (value == null) continue;
506 stringValue = processor.stringFactory.create(value);
507
508 doc.add(new Field(nameString, stringValue, rule.getStoreOption(), Field.Index.NOT_ANALYZED));
509
510 boolean treatedAsReference = false;
511 if (rule.canBeReference()) {
512 if (stringValue.length() == 36 && stringValue.charAt(8) == '-') {
513
514 try {
515 UUID.fromString(stringValue);
516
517 treatedAsReference = true;
518 doc.add(new Field(ContentIndex.REFERENCES, stringValue, Field.Store.YES, Field.Index.NOT_ANALYZED));
519 } catch (IllegalArgumentException e) {
520
521 }
522 }
523 }
524 if (!treatedAsReference && rule.getIndexOption() != Field.Index.NO && rule.isFullTextSearchable()
525 && !NON_SEARCHABLE_NAMES.contains(name)) {
526
527 fullTextSearchValue.append(' ').append(stringValue);
528
529
530 String fullTextNameString = processor.fullTextFieldName(nameString);
531 doc.add(new Field(fullTextNameString, stringValue, Store.NO, Index.ANALYZED));
532 }
533 }
534 }
535
536 if (fullTextSearchValue.length() != 0) {
537 doc.add(new Field(ContentIndex.FULL_TEXT, fullTextSearchValue.toString(), Field.Store.NO, Field.Index.ANALYZED));
538 }
539 if (logger.isTraceEnabled()) {
540 logger.trace("index for \"{0}\" workspace: ADD {1} {2}", workspace.getWorkspaceName(), pathStr, doc);
541 if (fullTextSearchValue.length() != 0) {
542
543
544 String fullTextContent = fullTextSearchValue.toString();
545 TokenStream stream = getAnalyzer().tokenStream(ContentIndex.FULL_TEXT, new StringReader(fullTextContent));
546 TermAttribute term = stream.addAttribute(TermAttribute.class);
547
548
549
550
551 StringBuilder output = new StringBuilder();
552 while (stream.incrementToken()) {
553 output.append(term.term()).append(' ');
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569 }
570 logger.trace("index for \"{0}\" workspace: {1} fts terms: {2}", workspace.getWorkspaceName(), pathStr, output);
571 }
572 }
573 getContentWriter().updateDocument(new Term(ContentIndex.PATH, pathStr), doc);
574 }
575
576
577
578
579
580
581 public TupleCollector createTupleCollector( Columns columns ) {
582 return new DualIndexTupleCollector(this, columns);
583 }
584
585 public Location getLocationForRoot() throws IOException {
586
587 Query query = NumericRangeQuery.newIntRange(ContentIndex.DEPTH, 0, 0, true, true);
588
589
590 List<Object[]> tuples = new ArrayList<Object[]>(1);
591 FullTextSearchTupleCollector collector = new FullTextSearchTupleCollector(this, tuples);
592 getContentSearcher().search(query, collector);
593
594
595 return tuples.isEmpty() ? Location.create(processor.pathFactory.createRootPath()) : (Location)tuples.get(0)[0];
596 }
597
598 public Query findAllNodesBelow( Path parentPath ) {
599
600 String stringifiedPath = processor.pathAsString(parentPath);
601
602 stringifiedPath = stringifiedPath + '/';
603
604
605 return new PrefixQuery(new Term(ContentIndex.PATH, stringifiedPath));
606 }
607
608 public Query findAllNodesAtOrBelow( Path parentPath ) {
609 if (parentPath.isRoot()) {
610 return new MatchAllDocsQuery();
611 }
612
613 String stringifiedPath = processor.pathAsString(parentPath);
614
615
616 return new PrefixQuery(new Term(ContentIndex.PATH, stringifiedPath));
617 }
618
619
620
621
622
623
624
625
626 public Query findChildNodes( Path parentPath ) {
627
628 String stringifiedPath = processor.pathAsString(parentPath);
629
630 stringifiedPath = stringifiedPath + '/';
631
632
633 Query query = new PrefixQuery(new Term(ContentIndex.PATH, stringifiedPath));
634
635 int childrenDepth = parentPath.size() + 1;
636 Query depthQuery = NumericRangeQuery.newIntRange(ContentIndex.DEPTH, childrenDepth, childrenDepth, true, true);
637
638 BooleanQuery combinedQuery = new BooleanQuery();
639 combinedQuery.add(query, Occur.MUST);
640 combinedQuery.add(depthQuery, Occur.MUST);
641 return combinedQuery;
642 }
643
644
645
646
647
648
649
650 public Query findNodeAt( Path path ) {
651 if (path.isRoot()) {
652
653 return NumericRangeQuery.newIntRange(ContentIndex.DEPTH, 0, 0, true, true);
654 }
655 String stringifiedPath = processor.pathAsString(path);
656 return new TermQuery(new Term(ContentIndex.PATH, stringifiedPath));
657 }
658
659 public Query findNodesLike( String fieldName,
660 String likeExpression,
661 boolean caseSensitive ) {
662 ValueFactories factories = processor.valueFactories;
663 return CompareStringQuery.createQueryForNodesWithFieldLike(likeExpression, fieldName, factories, caseSensitive);
664 }
665
666 public Query findNodesWith( Length propertyLength,
667 Operator operator,
668 Object value ) {
669 assert propertyLength != null;
670 assert value != null;
671 PropertyValue propertyValue = propertyLength.getPropertyValue();
672 String field = processor.stringFactory.create(propertyValue.getPropertyName());
673 ValueFactories factories = processor.valueFactories;
674 int length = factories.getLongFactory().create(value).intValue();
675 switch (operator) {
676 case EQUAL_TO:
677 return CompareLengthQuery.createQueryForNodesWithFieldEqualTo(length, field, factories);
678 case NOT_EQUAL_TO:
679 return CompareLengthQuery.createQueryForNodesWithFieldNotEqualTo(length, field, factories);
680 case GREATER_THAN:
681 return CompareLengthQuery.createQueryForNodesWithFieldGreaterThan(length, field, factories);
682 case GREATER_THAN_OR_EQUAL_TO:
683 return CompareLengthQuery.createQueryForNodesWithFieldGreaterThanOrEqualTo(length, field, factories);
684 case LESS_THAN:
685 return CompareLengthQuery.createQueryForNodesWithFieldLessThan(length, field, factories);
686 case LESS_THAN_OR_EQUAL_TO:
687 return CompareLengthQuery.createQueryForNodesWithFieldLessThanOrEqualTo(length, field, factories);
688 case LIKE:
689
690 assert false;
691 break;
692 }
693 return null;
694 }
695
696 @SuppressWarnings( "unchecked" )
697 public Query findNodesWith( PropertyValue propertyValue,
698 Operator operator,
699 Object value,
700 boolean caseSensitive ) {
701 ValueFactory<String> stringFactory = processor.stringFactory;
702 String field = stringFactory.create(propertyValue.getPropertyName());
703 Name fieldName = processor.nameFactory.create(field);
704 ValueFactories factories = processor.valueFactories;
705 IndexRules.Rule rule = workspace.rules.getRule(fieldName);
706 if (rule == null || rule.isSkipped()) return new MatchNoneQuery();
707 FieldType type = rule.getType();
708 switch (type) {
709 case REFERENCE:
710 case WEAK_REFERENCE:
711 case STRING:
712 String stringValue = stringFactory.create(value);
713 if (value instanceof Path) {
714 stringValue = processor.pathAsString((Path)value);
715 }
716 if (!caseSensitive) stringValue = stringValue.toLowerCase();
717 switch (operator) {
718 case EQUAL_TO:
719 return CompareStringQuery.createQueryForNodesWithFieldEqualTo(stringValue,
720 field,
721 factories,
722 caseSensitive);
723 case NOT_EQUAL_TO:
724 Query query = CompareStringQuery.createQueryForNodesWithFieldEqualTo(stringValue,
725 field,
726 factories,
727 caseSensitive);
728 return new NotQuery(query);
729 case GREATER_THAN:
730 return CompareStringQuery.createQueryForNodesWithFieldGreaterThan(stringValue,
731 field,
732 factories,
733 caseSensitive);
734 case GREATER_THAN_OR_EQUAL_TO:
735 return CompareStringQuery.createQueryForNodesWithFieldGreaterThanOrEqualTo(stringValue,
736 field,
737 factories,
738 caseSensitive);
739 case LESS_THAN:
740 return CompareStringQuery.createQueryForNodesWithFieldLessThan(stringValue,
741 field,
742 factories,
743 caseSensitive);
744 case LESS_THAN_OR_EQUAL_TO:
745 return CompareStringQuery.createQueryForNodesWithFieldLessThanOrEqualTo(stringValue,
746 field,
747 factories,
748 caseSensitive);
749 case LIKE:
750 return findNodesLike(field, stringValue, caseSensitive);
751 }
752 break;
753 case DATE:
754 NumericRule<Long> longRule = (NumericRule<Long>)rule;
755 long date = factories.getLongFactory().create(value);
756 switch (operator) {
757 case EQUAL_TO:
758 if (date < longRule.getMinimum() || date > longRule.getMaximum()) return new MatchNoneQuery();
759 return NumericRangeQuery.newLongRange(field, date, date, true, true);
760 case NOT_EQUAL_TO:
761 if (date < longRule.getMinimum() || date > longRule.getMaximum()) return new MatchAllDocsQuery();
762 Query query = NumericRangeQuery.newLongRange(field, date, date, true, true);
763 return new NotQuery(query);
764 case GREATER_THAN:
765 if (date > longRule.getMaximum()) return new MatchNoneQuery();
766 return NumericRangeQuery.newLongRange(field, date, longRule.getMaximum(), false, true);
767 case GREATER_THAN_OR_EQUAL_TO:
768 if (date > longRule.getMaximum()) return new MatchNoneQuery();
769 return NumericRangeQuery.newLongRange(field, date, longRule.getMaximum(), true, true);
770 case LESS_THAN:
771 if (date < longRule.getMinimum()) return new MatchNoneQuery();
772 return NumericRangeQuery.newLongRange(field, longRule.getMinimum(), date, true, false);
773 case LESS_THAN_OR_EQUAL_TO:
774 if (date < longRule.getMinimum()) return new MatchNoneQuery();
775 return NumericRangeQuery.newLongRange(field, longRule.getMinimum(), date, true, true);
776 case LIKE:
777
778 assert false;
779 return null;
780 }
781 break;
782 case LONG:
783 longRule = (NumericRule<Long>)rule;
784 long longValue = factories.getLongFactory().create(value);
785 switch (operator) {
786 case EQUAL_TO:
787 if (longValue < longRule.getMinimum() || longValue > longRule.getMaximum()) return new MatchNoneQuery();
788 return NumericRangeQuery.newLongRange(field, longValue, longValue, true, true);
789 case NOT_EQUAL_TO:
790 if (longValue < longRule.getMinimum() || longValue > longRule.getMaximum()) return new MatchAllDocsQuery();
791 Query query = NumericRangeQuery.newLongRange(field, longValue, longValue, true, true);
792 return new NotQuery(query);
793 case GREATER_THAN:
794 if (longValue > longRule.getMaximum()) return new MatchNoneQuery();
795 return NumericRangeQuery.newLongRange(field, longValue, longRule.getMaximum(), false, true);
796 case GREATER_THAN_OR_EQUAL_TO:
797 if (longValue > longRule.getMaximum()) return new MatchNoneQuery();
798 return NumericRangeQuery.newLongRange(field, longValue, longRule.getMaximum(), true, true);
799 case LESS_THAN:
800 if (longValue < longRule.getMinimum()) return new MatchNoneQuery();
801 return NumericRangeQuery.newLongRange(field, longRule.getMinimum(), longValue, true, false);
802 case LESS_THAN_OR_EQUAL_TO:
803 if (longValue < longRule.getMinimum()) return new MatchNoneQuery();
804 return NumericRangeQuery.newLongRange(field, longRule.getMinimum(), longValue, true, true);
805 case LIKE:
806
807 assert false;
808 return null;
809 }
810 break;
811 case INT:
812 NumericRule<Integer> intRule = (NumericRule<Integer>)rule;
813 int intValue = factories.getLongFactory().create(value).intValue();
814 switch (operator) {
815 case EQUAL_TO:
816 if (intValue < intRule.getMinimum() || intValue > intRule.getMaximum()) return new MatchNoneQuery();
817 return NumericRangeQuery.newIntRange(field, intValue, intValue, true, true);
818 case NOT_EQUAL_TO:
819 if (intValue < intRule.getMinimum() || intValue > intRule.getMaximum()) return new MatchAllDocsQuery();
820 Query query = NumericRangeQuery.newIntRange(field, intValue, intValue, true, true);
821 return new NotQuery(query);
822 case GREATER_THAN:
823 if (intValue > intRule.getMaximum()) return new MatchNoneQuery();
824 return NumericRangeQuery.newIntRange(field, intValue, intRule.getMaximum(), false, true);
825 case GREATER_THAN_OR_EQUAL_TO:
826 if (intValue > intRule.getMaximum()) return new MatchNoneQuery();
827 return NumericRangeQuery.newIntRange(field, intValue, intRule.getMaximum(), true, true);
828 case LESS_THAN:
829 if (intValue < intRule.getMinimum()) return new MatchNoneQuery();
830 return NumericRangeQuery.newIntRange(field, intRule.getMinimum(), intValue, true, false);
831 case LESS_THAN_OR_EQUAL_TO:
832 if (intValue < intRule.getMinimum()) return new MatchNoneQuery();
833 return NumericRangeQuery.newIntRange(field, intRule.getMinimum(), intValue, true, true);
834 case LIKE:
835
836 assert false;
837 return null;
838 }
839 break;
840 case DOUBLE:
841 NumericRule<Double> dRule = (NumericRule<Double>)rule;
842 double doubleValue = factories.getDoubleFactory().create(value);
843 switch (operator) {
844 case EQUAL_TO:
845 if (doubleValue < dRule.getMinimum() || doubleValue > dRule.getMaximum()) return new MatchNoneQuery();
846 return NumericRangeQuery.newDoubleRange(field, doubleValue, doubleValue, true, true);
847 case NOT_EQUAL_TO:
848 if (doubleValue < dRule.getMinimum() || doubleValue > dRule.getMaximum()) return new MatchAllDocsQuery();
849 Query query = NumericRangeQuery.newDoubleRange(field, doubleValue, doubleValue, true, true);
850 return new NotQuery(query);
851 case GREATER_THAN:
852 if (doubleValue > dRule.getMaximum()) return new MatchNoneQuery();
853 return NumericRangeQuery.newDoubleRange(field, doubleValue, dRule.getMaximum(), false, true);
854 case GREATER_THAN_OR_EQUAL_TO:
855 if (doubleValue > dRule.getMaximum()) return new MatchNoneQuery();
856 return NumericRangeQuery.newDoubleRange(field, doubleValue, dRule.getMaximum(), true, true);
857 case LESS_THAN:
858 if (doubleValue < dRule.getMinimum()) return new MatchNoneQuery();
859 return NumericRangeQuery.newDoubleRange(field, dRule.getMinimum(), doubleValue, true, false);
860 case LESS_THAN_OR_EQUAL_TO:
861 if (doubleValue < dRule.getMinimum()) return new MatchNoneQuery();
862 return NumericRangeQuery.newDoubleRange(field, dRule.getMinimum(), doubleValue, true, true);
863 case LIKE:
864
865 assert false;
866 return null;
867 }
868 break;
869 case FLOAT:
870 NumericRule<Float> fRule = (NumericRule<Float>)rule;
871 float floatValue = factories.getDoubleFactory().create(value).floatValue();
872 switch (operator) {
873 case EQUAL_TO:
874 if (floatValue < fRule.getMinimum() || floatValue > fRule.getMaximum()) return new MatchNoneQuery();
875 return NumericRangeQuery.newFloatRange(field, floatValue, floatValue, true, true);
876 case NOT_EQUAL_TO:
877 if (floatValue < fRule.getMinimum() || floatValue > fRule.getMaximum()) return new MatchAllDocsQuery();
878 Query query = NumericRangeQuery.newFloatRange(field, floatValue, floatValue, true, true);
879 return new NotQuery(query);
880 case GREATER_THAN:
881 if (floatValue > fRule.getMaximum()) return new MatchNoneQuery();
882 return NumericRangeQuery.newFloatRange(field, floatValue, fRule.getMaximum(), false, true);
883 case GREATER_THAN_OR_EQUAL_TO:
884 if (floatValue > fRule.getMaximum()) return new MatchNoneQuery();
885 return NumericRangeQuery.newFloatRange(field, floatValue, fRule.getMaximum(), true, true);
886 case LESS_THAN:
887 if (floatValue < fRule.getMinimum()) return new MatchNoneQuery();
888 return NumericRangeQuery.newFloatRange(field, fRule.getMinimum(), floatValue, true, false);
889 case LESS_THAN_OR_EQUAL_TO:
890 if (floatValue < fRule.getMinimum()) return new MatchNoneQuery();
891 return NumericRangeQuery.newFloatRange(field, fRule.getMinimum(), floatValue, true, true);
892 case LIKE:
893
894 assert false;
895 return null;
896 }
897 break;
898 case BOOLEAN:
899 boolean booleanValue = factories.getBooleanFactory().create(value);
900 stringValue = stringFactory.create(value);
901 switch (operator) {
902 case EQUAL_TO:
903 return new TermQuery(new Term(field, stringValue));
904 case NOT_EQUAL_TO:
905 return new TermQuery(new Term(field, stringFactory.create(!booleanValue)));
906 case GREATER_THAN:
907 if (!booleanValue) {
908 return new TermQuery(new Term(field, stringFactory.create(true)));
909 }
910
911 return new MatchNoneQuery();
912 case GREATER_THAN_OR_EQUAL_TO:
913 return new TermQuery(new Term(field, stringFactory.create(true)));
914 case LESS_THAN:
915 if (booleanValue) {
916 return new TermQuery(new Term(field, stringFactory.create(false)));
917 }
918
919 return new MatchNoneQuery();
920 case LESS_THAN_OR_EQUAL_TO:
921 return new TermQuery(new Term(field, stringFactory.create(false)));
922 case LIKE:
923
924 assert false;
925 return null;
926 }
927 break;
928 case BINARY:
929
930 assert false;
931 return null;
932 }
933 return null;
934 }
935
936
937
938
939
940
941
942 @Override
943 public Query findNodesWith( ReferenceValue referenceValue,
944 Operator operator,
945 Object value ) {
946 String field = referenceValue.getPropertyName();
947 if (field == null) field = LuceneSearchWorkspace.ContentIndex.REFERENCES;
948 ValueFactories factories = processor.valueFactories;
949 String stringValue = processor.stringFactory.create(value);
950 switch (operator) {
951 case EQUAL_TO:
952 return CompareStringQuery.createQueryForNodesWithFieldEqualTo(stringValue, field, factories, true);
953 case NOT_EQUAL_TO:
954 return new NotQuery(CompareStringQuery.createQueryForNodesWithFieldEqualTo(stringValue, field, factories, true));
955 case GREATER_THAN:
956 return CompareStringQuery.createQueryForNodesWithFieldGreaterThan(stringValue, field, factories, true);
957 case GREATER_THAN_OR_EQUAL_TO:
958 return CompareStringQuery.createQueryForNodesWithFieldGreaterThanOrEqualTo(stringValue, field, factories, true);
959 case LESS_THAN:
960 return CompareStringQuery.createQueryForNodesWithFieldLessThan(stringValue, field, factories, true);
961 case LESS_THAN_OR_EQUAL_TO:
962 return CompareStringQuery.createQueryForNodesWithFieldLessThanOrEqualTo(stringValue, field, factories, true);
963 case LIKE:
964 return findNodesLike(field, stringValue, false);
965 }
966 return null;
967 }
968
969 public Query findNodesWithNumericRange( PropertyValue propertyValue,
970 Object lowerValue,
971 Object upperValue,
972 boolean includesLower,
973 boolean includesUpper ) {
974 String field = processor.stringFactory.create(propertyValue.getPropertyName());
975 return findNodesWithNumericRange(field, lowerValue, upperValue, includesLower, includesUpper);
976 }
977
978 public Query findNodesWithNumericRange( NodeDepth depth,
979 Object lowerValue,
980 Object upperValue,
981 boolean includesLower,
982 boolean includesUpper ) {
983 return findNodesWithNumericRange(ContentIndex.DEPTH, lowerValue, upperValue, includesLower, includesUpper);
984 }
985
986 protected Query findNodesWithNumericRange( String field,
987 Object lowerValue,
988 Object upperValue,
989 boolean includesLower,
990 boolean includesUpper ) {
991 Name fieldName = processor.nameFactory.create(field);
992 IndexRules.Rule rule = workspace.rules.getRule(fieldName);
993 if (rule == null || rule.isSkipped()) return new MatchNoneQuery();
994 FieldType type = rule.getType();
995 ValueFactories factories = processor.valueFactories;
996 switch (type) {
997 case DATE:
998 long lowerDate = factories.getLongFactory().create(lowerValue);
999 long upperDate = factories.getLongFactory().create(upperValue);
1000 return NumericRangeQuery.newLongRange(field, lowerDate, upperDate, includesLower, includesUpper);
1001 case LONG:
1002 long lowerLong = factories.getLongFactory().create(lowerValue);
1003 long upperLong = factories.getLongFactory().create(upperValue);
1004 return NumericRangeQuery.newLongRange(field, lowerLong, upperLong, includesLower, includesUpper);
1005 case DOUBLE:
1006 double lowerDouble = factories.getDoubleFactory().create(lowerValue);
1007 double upperDouble = factories.getDoubleFactory().create(upperValue);
1008 return NumericRangeQuery.newDoubleRange(field, lowerDouble, upperDouble, includesLower, includesUpper);
1009 case FLOAT:
1010 float lowerFloat = factories.getDoubleFactory().create(lowerValue).floatValue();
1011 float upperFloat = factories.getDoubleFactory().create(upperValue).floatValue();
1012 return NumericRangeQuery.newFloatRange(field, lowerFloat, upperFloat, includesLower, includesUpper);
1013 case INT:
1014 int lowerInt = factories.getLongFactory().create(lowerValue).intValue();
1015 int upperInt = factories.getLongFactory().create(upperValue).intValue();
1016 return NumericRangeQuery.newIntRange(field, lowerInt, upperInt, includesLower, includesUpper);
1017 case BOOLEAN:
1018 lowerInt = factories.getBooleanFactory().create(lowerValue).booleanValue() ? 1 : 0;
1019 upperInt = factories.getBooleanFactory().create(upperValue).booleanValue() ? 1 : 0;
1020 return NumericRangeQuery.newIntRange(field, lowerInt, upperInt, includesLower, includesUpper);
1021 case STRING:
1022 case REFERENCE:
1023 case WEAK_REFERENCE:
1024 case BINARY:
1025 assert false;
1026 }
1027 return new MatchNoneQuery();
1028 }
1029
1030 protected String likeExpresionForWildcardPath( String path ) {
1031 if (path.equals("/") || path.equals("%")) return path;
1032 StringBuilder sb = new StringBuilder();
1033 for (String segment : path.split("/")) {
1034 if (segment.length() == 0) continue;
1035 sb.append("/");
1036 sb.append(segment);
1037 if (segment.equals("%") || segment.equals("_")) continue;
1038 if (!segment.endsWith("]") && !segment.endsWith("]%")) {
1039 sb.append("[1]");
1040 }
1041 }
1042 if (path.endsWith("/")) sb.append("/");
1043 return sb.toString();
1044 }
1045
1046 public Query findNodesWith( NodePath nodePath,
1047 Operator operator,
1048 Object value,
1049 boolean caseSensitive ) {
1050 if (!caseSensitive) value = processor.stringFactory.create(value).toLowerCase();
1051 Path pathValue = operator != Operator.LIKE ? processor.pathFactory.create(value) : null;
1052 Query query = null;
1053 switch (operator) {
1054 case EQUAL_TO:
1055 return findNodeAt(pathValue);
1056 case NOT_EQUAL_TO:
1057 return new NotQuery(findNodeAt(pathValue));
1058 case LIKE:
1059 String likeExpression = processor.stringFactory.create(value);
1060 likeExpression = likeExpresionForWildcardPath(likeExpression);
1061 query = findNodesLike(ContentIndex.PATH, likeExpression, caseSensitive);
1062 break;
1063 case GREATER_THAN:
1064 query = ComparePathQuery.createQueryForNodesWithPathGreaterThan(pathValue,
1065 ContentIndex.PATH,
1066 processor.valueFactories,
1067 caseSensitive);
1068 break;
1069 case GREATER_THAN_OR_EQUAL_TO:
1070 query = ComparePathQuery.createQueryForNodesWithPathGreaterThanOrEqualTo(pathValue,
1071 ContentIndex.PATH,
1072 processor.valueFactories,
1073 caseSensitive);
1074 break;
1075 case LESS_THAN:
1076 query = ComparePathQuery.createQueryForNodesWithPathLessThan(pathValue,
1077 ContentIndex.PATH,
1078 processor.valueFactories,
1079 caseSensitive);
1080 break;
1081 case LESS_THAN_OR_EQUAL_TO:
1082 query = ComparePathQuery.createQueryForNodesWithPathLessThanOrEqualTo(pathValue,
1083 ContentIndex.PATH,
1084 processor.valueFactories,
1085 caseSensitive);
1086 break;
1087 }
1088 return query;
1089 }
1090
1091 public Query findNodesWith( NodeName nodeName,
1092 Operator operator,
1093 Object value,
1094 boolean caseSensitive ) {
1095 ValueFactories factories = processor.valueFactories;
1096 String stringValue = processor.stringFactory.create(value);
1097 if (!caseSensitive) stringValue = stringValue.toLowerCase();
1098 Path.Segment segment = operator != Operator.LIKE ? processor.pathFactory.createSegment(stringValue) : null;
1099
1100 boolean includeSns = stringValue.indexOf('[') != -1;
1101 int snsIndex = operator != Operator.LIKE ? segment.getIndex() : 0;
1102 Query query = null;
1103 switch (operator) {
1104 case EQUAL_TO:
1105 if (!includeSns) {
1106 return new TermQuery(new Term(ContentIndex.NODE_NAME, stringValue));
1107 }
1108 BooleanQuery booleanQuery = new BooleanQuery();
1109 booleanQuery.add(new TermQuery(new Term(ContentIndex.NODE_NAME, stringValue)), Occur.MUST);
1110 booleanQuery.add(NumericRangeQuery.newIntRange(ContentIndex.SNS_INDEX, snsIndex, snsIndex, true, true),
1111 Occur.MUST);
1112 return booleanQuery;
1113 case NOT_EQUAL_TO:
1114 if (!includeSns) {
1115 return new NotQuery(new TermQuery(new Term(ContentIndex.NODE_NAME, stringValue)));
1116 }
1117 booleanQuery = new BooleanQuery();
1118 booleanQuery.add(new TermQuery(new Term(ContentIndex.NODE_NAME, stringValue)), Occur.MUST);
1119 booleanQuery.add(NumericRangeQuery.newIntRange(ContentIndex.SNS_INDEX, snsIndex, snsIndex, true, true),
1120 Occur.MUST);
1121 return new NotQuery(booleanQuery);
1122 case GREATER_THAN:
1123 query = CompareNameQuery.createQueryForNodesWithNameGreaterThan(segment,
1124 ContentIndex.NODE_NAME,
1125 ContentIndex.SNS_INDEX,
1126 factories,
1127 caseSensitive,
1128 includeSns);
1129 break;
1130 case GREATER_THAN_OR_EQUAL_TO:
1131 query = CompareNameQuery.createQueryForNodesWithNameGreaterThanOrEqualTo(segment,
1132 ContentIndex.NODE_NAME,
1133 ContentIndex.SNS_INDEX,
1134 factories,
1135 caseSensitive,
1136 includeSns);
1137 break;
1138 case LESS_THAN:
1139 query = CompareNameQuery.createQueryForNodesWithNameLessThan(segment,
1140 ContentIndex.NODE_NAME,
1141 ContentIndex.SNS_INDEX,
1142 factories,
1143 caseSensitive,
1144 includeSns);
1145 break;
1146 case LESS_THAN_OR_EQUAL_TO:
1147 query = CompareNameQuery.createQueryForNodesWithNameLessThanOrEqualTo(segment,
1148 ContentIndex.NODE_NAME,
1149 ContentIndex.SNS_INDEX,
1150 factories,
1151 caseSensitive,
1152 includeSns);
1153 break;
1154 case LIKE:
1155
1156 String likeExpression = stringValue;
1157 int openBracketIndex = likeExpression.indexOf('[');
1158 if (openBracketIndex != -1) {
1159 String localNameExpression = likeExpression.substring(0, openBracketIndex);
1160 String snsIndexExpression = likeExpression.substring(openBracketIndex);
1161 Query localNameQuery = CompareStringQuery.createQueryForNodesWithFieldLike(localNameExpression,
1162 ContentIndex.NODE_NAME,
1163 factories,
1164 caseSensitive);
1165 Query snsQuery = createSnsIndexQuery(snsIndexExpression);
1166 if (localNameQuery == null) {
1167 if (snsQuery == null) {
1168 query = new MatchNoneQuery();
1169 } else {
1170
1171 query = snsQuery;
1172 }
1173 } else {
1174
1175 if (snsQuery == null) {
1176 query = localNameQuery;
1177 } else {
1178
1179 booleanQuery = new BooleanQuery();
1180 booleanQuery.add(localNameQuery, Occur.MUST);
1181 booleanQuery.add(snsQuery, Occur.MUST);
1182 query = booleanQuery;
1183 }
1184 }
1185 } else {
1186
1187 query = CompareStringQuery.createQueryForNodesWithFieldLike(likeExpression,
1188 ContentIndex.NODE_NAME,
1189 factories,
1190 caseSensitive);
1191 }
1192 assert query != null;
1193 break;
1194 }
1195 return query;
1196 }
1197
1198 public Query findNodesWith( NodeLocalName nodeName,
1199 Operator operator,
1200 Object value,
1201 boolean caseSensitive ) {
1202 String nameValue = processor.stringFactory.create(value);
1203 Query query = null;
1204 switch (operator) {
1205 case LIKE:
1206 String likeExpression = processor.stringFactory.create(value);
1207 query = findNodesLike(ContentIndex.LOCAL_NAME, likeExpression, caseSensitive);
1208 break;
1209 case EQUAL_TO:
1210 query = CompareStringQuery.createQueryForNodesWithFieldEqualTo(nameValue,
1211 ContentIndex.LOCAL_NAME,
1212 processor.valueFactories,
1213 caseSensitive);
1214 break;
1215 case NOT_EQUAL_TO:
1216 query = CompareStringQuery.createQueryForNodesWithFieldEqualTo(nameValue,
1217 ContentIndex.LOCAL_NAME,
1218 processor.valueFactories,
1219 caseSensitive);
1220 query = new NotQuery(query);
1221 break;
1222 case GREATER_THAN:
1223 query = CompareStringQuery.createQueryForNodesWithFieldGreaterThan(nameValue,
1224 ContentIndex.LOCAL_NAME,
1225 processor.valueFactories,
1226 caseSensitive);
1227 break;
1228 case GREATER_THAN_OR_EQUAL_TO:
1229 query = CompareStringQuery.createQueryForNodesWithFieldGreaterThanOrEqualTo(nameValue,
1230 ContentIndex.LOCAL_NAME,
1231 processor.valueFactories,
1232 caseSensitive);
1233 break;
1234 case LESS_THAN:
1235 query = CompareStringQuery.createQueryForNodesWithFieldLessThan(nameValue,
1236 ContentIndex.LOCAL_NAME,
1237 processor.valueFactories,
1238 caseSensitive);
1239 break;
1240 case LESS_THAN_OR_EQUAL_TO:
1241 query = CompareStringQuery.createQueryForNodesWithFieldLessThanOrEqualTo(nameValue,
1242 ContentIndex.LOCAL_NAME,
1243 processor.valueFactories,
1244 caseSensitive);
1245 break;
1246 }
1247 return query;
1248 }
1249
1250 public Query findNodesWith( NodeDepth depthConstraint,
1251 Operator operator,
1252 Object value ) {
1253 int depth = processor.valueFactories.getLongFactory().create(value).intValue();
1254 switch (operator) {
1255 case EQUAL_TO:
1256 return NumericRangeQuery.newIntRange(ContentIndex.DEPTH, depth, depth, true, true);
1257 case NOT_EQUAL_TO:
1258 Query query = NumericRangeQuery.newIntRange(ContentIndex.DEPTH, depth, depth, true, true);
1259 return new NotQuery(query);
1260 case GREATER_THAN:
1261 return NumericRangeQuery.newIntRange(ContentIndex.DEPTH, depth, MAX_DEPTH, false, true);
1262 case GREATER_THAN_OR_EQUAL_TO:
1263 return NumericRangeQuery.newIntRange(ContentIndex.DEPTH, depth, MAX_DEPTH, true, true);
1264 case LESS_THAN:
1265 return NumericRangeQuery.newIntRange(ContentIndex.DEPTH, MIN_DEPTH, depth, true, false);
1266 case LESS_THAN_OR_EQUAL_TO:
1267 return NumericRangeQuery.newIntRange(ContentIndex.DEPTH, MIN_DEPTH, depth, true, true);
1268 case LIKE:
1269
1270 return null;
1271 }
1272 return null;
1273 }
1274
1275 protected Query createLocalNameQuery( String likeExpression,
1276 boolean caseSensitive ) {
1277 if (likeExpression == null) return null;
1278 return CompareStringQuery.createQueryForNodesWithFieldLike(likeExpression,
1279 ContentIndex.LOCAL_NAME,
1280 processor.valueFactories,
1281 caseSensitive);
1282 }
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295 protected Query createSnsIndexQuery( String likeExpression ) {
1296 if (likeExpression == null) return null;
1297 likeExpression = likeExpression.trim();
1298 if (likeExpression.length() == 0) return null;
1299
1300
1301 assert likeExpression.charAt(0) == '[';
1302 likeExpression = likeExpression.substring(1);
1303
1304
1305 int closeBracketIndex = likeExpression.indexOf(']');
1306 if (closeBracketIndex != -1) {
1307 likeExpression = likeExpression.substring(0, closeBracketIndex);
1308 }
1309 if (likeExpression.equals("_")) {
1310
1311 return NumericRangeQuery.newIntRange(ContentIndex.SNS_INDEX, MIN_SNS_INDEX, 9, true, true);
1312 }
1313 if (likeExpression.equals("%")) {
1314
1315 return NumericRangeQuery.newIntRange(ContentIndex.SNS_INDEX, MIN_SNS_INDEX, MAX_SNS_INDEX, true, true);
1316 }
1317 if (likeExpression.indexOf('_') != -1) {
1318 if (likeExpression.indexOf('%') != -1) {
1319
1320 return findNodesLike(ContentIndex.SNS_INDEX, likeExpression, true);
1321 }
1322
1323 int firstWildcardChar = likeExpression.indexOf('_');
1324 if (firstWildcardChar + 1 < likeExpression.length()) {
1325
1326 int secondWildcardChar = likeExpression.indexOf('_', firstWildcardChar + 1);
1327 if (secondWildcardChar != -1) {
1328
1329 return findNodesLike(ContentIndex.SNS_INDEX, likeExpression, true);
1330 }
1331 }
1332
1333 String lowerExpression = likeExpression.replace('_', '0');
1334 String upperExpression = likeExpression.replace('_', '9');
1335 try {
1336
1337 int lowerSns = Integer.parseInt(lowerExpression);
1338 int upperSns = Integer.parseInt(upperExpression);
1339 return NumericRangeQuery.newIntRange(ContentIndex.SNS_INDEX, lowerSns, upperSns, true, true);
1340 } catch (NumberFormatException e) {
1341
1342 return new MatchNoneQuery();
1343 }
1344 }
1345 if (likeExpression.indexOf('%') != -1) {
1346
1347 return findNodesLike(ContentIndex.SNS_INDEX, likeExpression, true);
1348 }
1349
1350 try {
1351
1352 int sns = Integer.parseInt(likeExpression);
1353 return NumericRangeQuery.newIntRange(ContentIndex.SNS_INDEX, sns, sns, true, true);
1354 } catch (NumberFormatException e) {
1355
1356 return new MatchNoneQuery();
1357 }
1358 }
1359
1360
1361
1362
1363 protected static class DualIndexTupleCollector extends TupleCollector {
1364 private final LuceneSearchSession session;
1365 private final LinkedList<Object[]> tuples = new LinkedList<Object[]>();
1366 private final Columns columns;
1367 private final int numValues;
1368 private final boolean recordScore;
1369 private final int scoreIndex;
1370 private final FieldSelector fieldSelector;
1371 private final int locationIndex;
1372 private Scorer scorer;
1373 private IndexReader currentReader;
1374 private int docOffset;
1375
1376 protected DualIndexTupleCollector( LuceneSearchSession session,
1377 Columns columns ) {
1378 this.session = session;
1379 this.columns = columns;
1380 assert this.columns != null;
1381 this.numValues = this.columns.getTupleSize();
1382 assert this.numValues >= 0;
1383 assert this.columns.getSelectorNames().size() == 1;
1384 final String selectorName = this.columns.getSelectorNames().get(0);
1385 this.locationIndex = this.columns.getLocationIndex(selectorName);
1386 this.recordScore = this.columns.hasFullTextSearchScores();
1387 this.scoreIndex = this.recordScore ? this.columns.getFullTextSearchScoreIndexFor(selectorName) : -1;
1388
1389
1390 final Set<String> fieldNames = new HashSet<String>(this.columns.getColumnNames());
1391 fieldNames.add(ContentIndex.LOCATION_ID_PROPERTIES);
1392 fieldNames.add(ContentIndex.PATH);
1393 this.fieldSelector = new FieldSelector() {
1394 private static final long serialVersionUID = 1L;
1395
1396 public FieldSelectorResult accept( String fieldName ) {
1397 return fieldNames.contains(fieldName) ? FieldSelectorResult.LOAD : FieldSelectorResult.NO_LOAD;
1398 }
1399 };
1400 }
1401
1402
1403
1404
1405 @Override
1406 public LinkedList<Object[]> getTuples() {
1407 return tuples;
1408 }
1409
1410
1411
1412
1413
1414
1415 @Override
1416 public boolean acceptsDocsOutOfOrder() {
1417 return true;
1418 }
1419
1420
1421
1422
1423
1424
1425 public int getLocationIndex() {
1426 return locationIndex;
1427 }
1428
1429
1430
1431
1432
1433
1434 @Override
1435 public void setNextReader( IndexReader reader,
1436 int docBase ) {
1437 this.currentReader = reader;
1438 this.docOffset = docBase;
1439 }
1440
1441
1442
1443
1444
1445
1446 @Override
1447 public void setScorer( Scorer scorer ) {
1448 this.scorer = scorer;
1449 }
1450
1451
1452
1453
1454
1455
1456 @Override
1457 public void collect( int doc ) throws IOException {
1458 int docId = doc + docOffset;
1459 Object[] tuple = new Object[numValues];
1460 Document document = currentReader.document(docId, fieldSelector);
1461 for (String columnName : columns.getColumnNames()) {
1462 int index = columns.getColumnIndexForName(columnName);
1463
1464 tuple[index] = document.get(columnName);
1465 }
1466
1467
1468 if (recordScore) {
1469 assert scorer != null;
1470 tuple[scoreIndex] = scorer.score();
1471 }
1472
1473
1474 tuple[locationIndex] = session.readLocation(document);
1475 tuples.add(tuple);
1476 }
1477 }
1478
1479
1480
1481
1482 protected static class FullTextSearchTupleCollector extends TupleCollector {
1483 private final List<Object[]> tuples;
1484 private final FieldSelector fieldSelector;
1485 private final LuceneSearchSession session;
1486 private Scorer scorer;
1487 private IndexReader currentReader;
1488 private int docOffset;
1489
1490 protected FullTextSearchTupleCollector( LuceneSearchSession session,
1491 List<Object[]> tuples ) {
1492 assert session != null;
1493 assert tuples != null;
1494 this.session = session;
1495 this.tuples = tuples;
1496 this.fieldSelector = LOCATION_FIELDS_SELECTOR;
1497 }
1498
1499
1500
1501
1502 @Override
1503 public List<Object[]> getTuples() {
1504 return tuples;
1505 }
1506
1507
1508
1509
1510
1511
1512 @Override
1513 public boolean acceptsDocsOutOfOrder() {
1514 return true;
1515 }
1516
1517
1518
1519
1520
1521
1522 @Override
1523 public void setNextReader( IndexReader reader,
1524 int docBase ) {
1525 this.currentReader = reader;
1526 this.docOffset = docBase;
1527 }
1528
1529
1530
1531
1532
1533
1534 @Override
1535 public void setScorer( Scorer scorer ) {
1536 this.scorer = scorer;
1537 }
1538
1539
1540
1541
1542
1543
1544 @Override
1545 public void collect( int doc ) throws IOException {
1546 int docId = doc + docOffset;
1547 Object[] tuple = new Object[2];
1548 Document document = currentReader.document(docId, fieldSelector);
1549
1550 tuple[0] = session.readLocation(document);
1551
1552 tuple[1] = scorer.score();
1553
1554 tuples.add(tuple);
1555 }
1556 }
1557 }