View Javadoc

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 }