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