1 /*
2 * ModeShape (http://www.modeshape.org)
3 * See the COPYRIGHT.txt file distributed with this work for information
4 * regarding copyright ownership. Some portions may be licensed
5 * to Red Hat, Inc. under one or more contributor license agreements.
6 * See the AUTHORS.txt file in the distribution for a full listing of
7 * individual contributors.
8 *
9 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10 * is licensed to you under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation; either version 2.1 of
12 * the License, or (at your option) any later version.
13 *
14 * ModeShape is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this software; if not, write to the Free
21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23 */
24 package org.modeshape.search.lucene.query;
25
26 import java.io.IOException;
27 import org.apache.lucene.document.Document;
28 import org.apache.lucene.document.FieldSelector;
29 import org.apache.lucene.document.FieldSelectorResult;
30 import org.apache.lucene.index.IndexReader;
31 import org.apache.lucene.search.Query;
32 import org.apache.lucene.search.Searcher;
33 import org.apache.lucene.search.Weight;
34 import org.modeshape.graph.property.Path;
35 import org.modeshape.graph.property.PathFactory;
36 import org.modeshape.graph.property.ValueComparators;
37 import org.modeshape.graph.property.ValueFactories;
38 import org.modeshape.graph.property.ValueFactory;
39 import org.modeshape.graph.query.model.Comparison;
40
41 /**
42 * A Lucene {@link Query} implementation that is used to apply a {@link Comparison} constraint against the name of nodes. This
43 * query implementation works by using the {@link Query#weight(Searcher) weight} and
44 * {@link Weight#scorer(IndexReader, boolean, boolean) scorer} of the wrapped query to score (and return) only those documents
45 * that correspond to nodes with Names that satisfy the constraint.
46 */
47 public class CompareNameQuery extends CompareQuery<Path.Segment> {
48
49 private static final long serialVersionUID = 1L;
50 protected static final Evaluator<Path.Segment> IS_LESS_THAN = new Evaluator<Path.Segment>() {
51 private static final long serialVersionUID = 1L;
52
53 public boolean satisfiesConstraint( Path.Segment nodeValue,
54 Path.Segment constraintValue ) {
55 return ValueComparators.PATH_SEGMENT_COMPARATOR.compare(nodeValue, constraintValue) < 0;
56 }
57
58 @Override
59 public String toString() {
60 return " < ";
61 }
62 };
63 protected static final Evaluator<Path.Segment> IS_LESS_THAN_OR_EQUAL_TO = new Evaluator<Path.Segment>() {
64 private static final long serialVersionUID = 1L;
65
66 public boolean satisfiesConstraint( Path.Segment nodeValue,
67 Path.Segment constraintValue ) {
68 return ValueComparators.PATH_SEGMENT_COMPARATOR.compare(nodeValue, constraintValue) <= 0;
69 }
70
71 @Override
72 public String toString() {
73 return " <= ";
74 }
75 };
76 protected static final Evaluator<Path.Segment> IS_GREATER_THAN = new Evaluator<Path.Segment>() {
77 private static final long serialVersionUID = 1L;
78
79 public boolean satisfiesConstraint( Path.Segment nodeValue,
80 Path.Segment constraintValue ) {
81 return ValueComparators.PATH_SEGMENT_COMPARATOR.compare(nodeValue, constraintValue) > 0;
82 }
83
84 @Override
85 public String toString() {
86 return " > ";
87 }
88 };
89 protected static final Evaluator<Path.Segment> IS_GREATER_THAN_OR_EQUAL_TO = new Evaluator<Path.Segment>() {
90 private static final long serialVersionUID = 1L;
91
92 public boolean satisfiesConstraint( Path.Segment nodeValue,
93 Path.Segment constraintValue ) {
94 return ValueComparators.PATH_SEGMENT_COMPARATOR.compare(nodeValue, constraintValue) >= 0;
95 }
96
97 @Override
98 public String toString() {
99 return " >= ";
100 }
101 };
102
103 protected static final Evaluator<Path.Segment> IS_LESS_THAN_NO_SNS = new Evaluator<Path.Segment>() {
104 private static final long serialVersionUID = 1L;
105
106 public boolean satisfiesConstraint( Path.Segment nodeValue,
107 Path.Segment constraintValue ) {
108 return ValueComparators.PATH_SEGMENT_NAME_COMPARATOR.compare(nodeValue, constraintValue) < 0;
109 }
110
111 @Override
112 public String toString() {
113 return " < ";
114 }
115 };
116 protected static final Evaluator<Path.Segment> IS_LESS_THAN_OR_EQUAL_TO_NO_SNS = new Evaluator<Path.Segment>() {
117 private static final long serialVersionUID = 1L;
118
119 public boolean satisfiesConstraint( Path.Segment nodeValue,
120 Path.Segment constraintValue ) {
121 return ValueComparators.PATH_SEGMENT_NAME_COMPARATOR.compare(nodeValue, constraintValue) <= 0;
122 }
123
124 @Override
125 public String toString() {
126 return " <= ";
127 }
128 };
129 protected static final Evaluator<Path.Segment> IS_GREATER_THAN_NO_SNS = new Evaluator<Path.Segment>() {
130 private static final long serialVersionUID = 1L;
131
132 public boolean satisfiesConstraint( Path.Segment nodeValue,
133 Path.Segment constraintValue ) {
134 return ValueComparators.PATH_SEGMENT_NAME_COMPARATOR.compare(nodeValue, constraintValue) > 0;
135 }
136
137 @Override
138 public String toString() {
139 return " > ";
140 }
141 };
142 protected static final Evaluator<Path.Segment> IS_GREATER_THAN_OR_EQUAL_TO_NO_SNS = new Evaluator<Path.Segment>() {
143 private static final long serialVersionUID = 1L;
144
145 public boolean satisfiesConstraint( Path.Segment nodeValue,
146 Path.Segment constraintValue ) {
147 return ValueComparators.PATH_SEGMENT_NAME_COMPARATOR.compare(nodeValue, constraintValue) >= 0;
148 }
149
150 @Override
151 public String toString() {
152 return " >= ";
153 }
154 };
155
156 /**
157 * Construct a {@link Query} implementation that scores documents such that the node represented by the document has a name
158 * that is greater than the supplied constraint name.
159 *
160 * @param constraintValue the constraint value; may not be null
161 * @param localNameField the name of the document field containing the local name value; may not be null
162 * @param snsIndexFieldName the name of the document field containing the same-name-sibling index; may not be null
163 * @param factories the value factories that can be used during the scoring; may not be null
164 * @param caseSensitive true if the comparison should be done in a case-sensitive manner, or false if it is to be
165 * case-insensitive
166 * @param includeSns true if the SNS index should be considered, or false if the SNS value should be ignored
167 * @return the query; never null
168 */
169 public static CompareNameQuery createQueryForNodesWithNameGreaterThan( Path.Segment constraintValue,
170 String localNameField,
171 String snsIndexFieldName,
172 ValueFactories factories,
173 boolean caseSensitive,
174 boolean includeSns ) {
175 return new CompareNameQuery(localNameField, snsIndexFieldName, constraintValue, factories.getPathFactory(),
176 factories.getStringFactory(), factories.getLongFactory(),
177 includeSns ? IS_GREATER_THAN : IS_GREATER_THAN_NO_SNS, caseSensitive);
178 }
179
180 /**
181 * Construct a {@link Query} implementation that scores documents such that the node represented by the document has a name
182 * that is greater than or equal to the supplied constraint name.
183 *
184 * @param constraintValue the constraint value; may not be null
185 * @param localNameField the name of the document field containing the local name value; may not be null
186 * @param snsIndexFieldName the name of the document field containing the same-name-sibling index; may not be null
187 * @param factories the value factories that can be used during the scoring; may not be null
188 * @param caseSensitive true if the comparison should be done in a case-sensitive manner, or false if it is to be
189 * case-insensitive
190 * @param includeSns true if the SNS index should be considered, or false if the SNS value should be ignored
191 * @return the query; never null
192 */
193 public static CompareNameQuery createQueryForNodesWithNameGreaterThanOrEqualTo( Path.Segment constraintValue,
194 String localNameField,
195 String snsIndexFieldName,
196 ValueFactories factories,
197 boolean caseSensitive,
198 boolean includeSns ) {
199 return new CompareNameQuery(localNameField, snsIndexFieldName, constraintValue, factories.getPathFactory(),
200 factories.getStringFactory(), factories.getLongFactory(),
201 includeSns ? IS_GREATER_THAN_OR_EQUAL_TO : IS_GREATER_THAN_OR_EQUAL_TO_NO_SNS, caseSensitive);
202 }
203
204 /**
205 * Construct a {@link Query} implementation that scores documents such that the node represented by the document has a name
206 * that is less than the supplied constraint name.
207 *
208 * @param constraintValue the constraint value; may not be null
209 * @param localNameField the name of the document field containing the local name value; may not be null
210 * @param snsIndexFieldName the name of the document field containing the same-name-sibling index; may not be null
211 * @param factories the value factories that can be used during the scoring; may not be null
212 * @param caseSensitive true if the comparison should be done in a case-sensitive manner, or false if it is to be
213 * case-insensitive
214 * @param includeSns true if the SNS index should be considered, or false if the SNS value should be ignored
215 * @return the query; never null
216 */
217 public static CompareNameQuery createQueryForNodesWithNameLessThan( Path.Segment constraintValue,
218 String localNameField,
219 String snsIndexFieldName,
220 ValueFactories factories,
221 boolean caseSensitive,
222 boolean includeSns ) {
223 return new CompareNameQuery(localNameField, snsIndexFieldName, constraintValue, factories.getPathFactory(),
224 factories.getStringFactory(), factories.getLongFactory(),
225 includeSns ? IS_LESS_THAN : IS_LESS_THAN_NO_SNS, caseSensitive);
226 }
227
228 /**
229 * Construct a {@link Query} implementation that scores documents such that the node represented by the document has a name
230 * that is less than or equal to the supplied constraint name.
231 *
232 * @param constraintValue the constraint value; may not be null
233 * @param localNameField the name of the document field containing the local name value; may not be null
234 * @param snsIndexFieldName the name of the document field containing the same-name-sibling index; may not be null
235 * @param factories the value factories that can be used during the scoring; may not be null
236 * @param caseSensitive true if the comparison should be done in a case-sensitive manner, or false if it is to be
237 * case-insensitive
238 * @param includeSns true if the SNS index should be considered, or false if the SNS value should be ignored
239 * @return the query; never null
240 */
241 public static CompareNameQuery createQueryForNodesWithNameLessThanOrEqualTo( Path.Segment constraintValue,
242 String localNameField,
243 String snsIndexFieldName,
244 ValueFactories factories,
245 boolean caseSensitive,
246 boolean includeSns ) {
247 return new CompareNameQuery(localNameField, snsIndexFieldName, constraintValue, factories.getPathFactory(),
248 factories.getStringFactory(), factories.getLongFactory(),
249 includeSns ? IS_LESS_THAN_OR_EQUAL_TO : IS_LESS_THAN_OR_EQUAL_TO_NO_SNS, caseSensitive);
250 }
251
252 private final String snsIndexFieldName;
253 private final ValueFactory<Long> longFactory;
254 private final PathFactory pathFactory;
255 private final boolean caseSensitive;
256
257 /**
258 * Construct a {@link Query} implementation that scores nodes according to the supplied comparator.
259 *
260 * @param localNameField the name of the document field containing the local name value; may not be null
261 * @param snsIndexFieldName the name of the document field containing the same-name-sibling index; may not be null
262 * @param constraintValue the constraint path; may not be null
263 * @param pathFactory the path factory that can be used during the scoring; may not be null
264 * @param stringFactory the string factory that can be used during the scoring; may not be null
265 * @param longFactory the long factory that can be used during the scoring; may not be null
266 * @param evaluator the {@link CompareQuery.Evaluator} implementation that returns whether the node path satisfies the
267 * constraint; may not be null
268 * @param caseSensitive true if the comparison should be done in a case-sensitive manner, or false if it is to be
269 * case-insensitive
270 */
271 protected CompareNameQuery( final String localNameField,
272 final String snsIndexFieldName,
273 Path.Segment constraintValue,
274 PathFactory pathFactory,
275 ValueFactory<String> stringFactory,
276 ValueFactory<Long> longFactory,
277 Evaluator<Path.Segment> evaluator,
278 boolean caseSensitive ) {
279 super(localNameField, constraintValue, null, stringFactory, evaluator, new FieldSelector() {
280 private static final long serialVersionUID = 1L;
281
282 public FieldSelectorResult accept( String fieldName ) {
283 if (fieldName.equals(localNameField)) return FieldSelectorResult.LOAD;
284 if (fieldName.equals(snsIndexFieldName)) return FieldSelectorResult.LOAD;
285 return FieldSelectorResult.NO_LOAD;
286 }
287 });
288 this.snsIndexFieldName = snsIndexFieldName;
289 this.longFactory = longFactory;
290 this.pathFactory = pathFactory;
291 this.caseSensitive = caseSensitive;
292 assert this.snsIndexFieldName != null;
293 assert this.longFactory != null;
294 }
295
296 /**
297 * {@inheritDoc}
298 *
299 * @see org.modeshape.search.lucene.query.CompareQuery#readFromDocument(org.apache.lucene.index.IndexReader, int)
300 */
301 @Override
302 protected Path.Segment readFromDocument( IndexReader reader,
303 int docId ) throws IOException {
304 Document doc = reader.document(docId, fieldSelector);
305 String localName = doc.get(fieldName);
306 if (!caseSensitive) localName = localName.toLowerCase();
307 int sns = longFactory.create(doc.get(snsIndexFieldName)).intValue();
308 return pathFactory.createSegment(localName, sns);
309 }
310
311 /**
312 * {@inheritDoc}
313 *
314 * @see org.apache.lucene.search.Query#clone()
315 */
316 @Override
317 public Object clone() {
318 return new CompareNameQuery(fieldName, snsIndexFieldName, constraintValue, pathFactory, stringFactory, longFactory,
319 evaluator, caseSensitive);
320 }
321 }