1 |
| package photospace.search; |
2 |
| |
3 |
| import java.io.*; |
4 |
| import java.util.*; |
5 |
| import org.apache.commons.logging.*; |
6 |
| import org.apache.lucene.document.*; |
7 |
| import org.apache.lucene.index.*; |
8 |
| import org.apache.lucene.queryParser.*; |
9 |
| import org.apache.lucene.search.*; |
10 |
| import photospace.meta.*; |
11 |
| |
12 |
| public class Searcher |
13 |
| { |
14 |
| private static final Log log = LogFactory.getLog(Searcher.class); |
15 |
| |
16 |
| public static final int ASCENDING = 1; |
17 |
| public static final int DESCENDING = -1; |
18 |
| |
19 |
| |
20 |
| public static final Sort CREATED_ASCENDING = new Sort(new SortField(DocumentFactory.SORT_FIELD, SortField.STRING)); |
21 |
| public static final Sort CREATED_DESCENDING = new Sort(new SortField(DocumentFactory.SORT_FIELD, SortField.STRING, true)); |
22 |
| |
23 |
| public static final Query PHOTO_CONSTRAINT = new TermQuery(new Term(DocumentFactory.TYPE_FIELD, DocumentFactory.PHOTO_TYPE)); |
24 |
| |
25 |
| private SearchIndex index; |
26 |
| private DocumentFactory factory = new DocumentFactory(); |
27 |
| |
28 |
9
| public Meta browse(String path, int sort, int start, int end) throws IOException
|
29 |
| { |
30 |
9
| return browse(path, null, sort, start, end);
|
31 |
| } |
32 |
| |
33 |
10
| public Meta browse(String path, Query constraint, int sort, int start, int end) throws IOException
|
34 |
| { |
35 |
10
| Document doc = findDocument(path);
|
36 |
1
| if (doc == null) return null;
|
37 |
| |
38 |
9
| Meta meta = factory.createMeta(doc);
|
39 |
9
| assignParents(meta);
|
40 |
| |
41 |
1
| if (!(meta instanceof CollectionMeta)) return meta;
|
42 |
| |
43 |
8
| CollectionMeta collection = (CollectionMeta) meta;
|
44 |
| |
45 |
8
| Sort order = (sort == ASCENDING) ? CREATED_ASCENDING : CREATED_DESCENDING;
|
46 |
| |
47 |
8
| Query query = new TermQuery(new Term(DocumentFactory.PARENT_FIELD, collection.getPath()));
|
48 |
8
| if (constraint != null)
|
49 |
| { |
50 |
1
| BooleanQuery constrained = new BooleanQuery();
|
51 |
1
| constrained.add(query, true, false);
|
52 |
1
| constrained.add(constraint, true, false);
|
53 |
1
| query = constrained;
|
54 |
| } |
55 |
| |
56 |
8
| SearchResult children = search(query, null, order, start, end);
|
57 |
8
| collection.addFiles(children.getFiles());
|
58 |
8
| collection.setStart(children.getStart());
|
59 |
8
| collection.setEnd(children.getEnd());
|
60 |
8
| collection.setTotal(children.getTotal());
|
61 |
| |
62 |
8
| return collection;
|
63 |
| } |
64 |
| |
65 |
9
| private void assignParents(Meta meta) throws IOException
|
66 |
| { |
67 |
5
| if (meta.getParentPath() == null) return;
|
68 |
4
| CollectionMeta parent = (CollectionMeta) browse(meta.getParentPath(), DESCENDING, -1, -1);
|
69 |
4
| meta.setParent(parent);
|
70 |
| } |
71 |
| |
72 |
| |
73 |
| |
74 |
| |
75 |
0
| public Collection getFieldNames() throws IOException
|
76 |
| { |
77 |
0
| IndexReader reader = index.getReader();
|
78 |
0
| List names = new ArrayList(reader.getFieldNames(true));
|
79 |
0
| Collections.sort(names);
|
80 |
0
| return names;
|
81 |
| } |
82 |
| |
83 |
| |
84 |
| |
85 |
| |
86 |
1
| public Collection getDocumentMatches(String path) throws IOException
|
87 |
| { |
88 |
1
| Collection matches = new ArrayList();
|
89 |
| |
90 |
1
| Document doc = findDocument(path);
|
91 |
0
| if (doc == null) return matches;
|
92 |
| |
93 |
1
| for (Enumeration fields = doc.fields(); fields.hasMoreElements(); )
|
94 |
| { |
95 |
9
| Field field = (Field) fields.nextElement();
|
96 |
9
| matches.addAll(getFieldMatches(field.name(), field.stringValue()));
|
97 |
| } |
98 |
| |
99 |
1
| return matches;
|
100 |
| } |
101 |
| |
102 |
| |
103 |
| |
104 |
| |
105 |
3
| public Collection getDocumentMatches(String path, String field) throws IOException
|
106 |
| { |
107 |
3
| Collection matches = new ArrayList();
|
108 |
| |
109 |
3
| Document doc = findDocument(path);
|
110 |
0
| if (doc == null) return matches;
|
111 |
| |
112 |
3
| String[] values = doc.getValues(field);
|
113 |
0
| if (values == null || values.length == 0 || "".equals(values[0])) return matches;
|
114 |
| |
115 |
3
| for (int i = 0; i < values.length; i++)
|
116 |
| { |
117 |
4
| matches.addAll(getFieldMatches(field, values[i]));
|
118 |
| } |
119 |
| |
120 |
3
| return matches;
|
121 |
| } |
122 |
| |
123 |
14
| private Document findDocument(String path) throws IOException
|
124 |
| { |
125 |
14
| IndexReader reader = index.getReader();
|
126 |
14
| TermDocs docs = reader.termDocs(new Term("path", path));
|
127 |
14
| if (! docs.next())
|
128 |
| { |
129 |
1
| log.warn("No document found for path " + path);
|
130 |
1
| return null;
|
131 |
| } |
132 |
13
| return reader.document(docs.doc());
|
133 |
| } |
134 |
| |
135 |
2
| public Collection getFieldMatches(String field) throws IOException
|
136 |
| { |
137 |
2
| return getFieldMatches(field, "");
|
138 |
| } |
139 |
| |
140 |
15
| private Collection getFieldMatches(String field, String value)
|
141 |
| throws IOException |
142 |
| { |
143 |
15
| IndexReader reader = index.getReader();
|
144 |
15
| Collection values = new ArrayList();
|
145 |
15
| TermEnum terms = reader.terms(new Term(field, value));
|
146 |
15
| while (field.equals(terms.term().field()) && ("".equals(value) || value.equals(terms.term().text())))
|
147 |
| { |
148 |
17
| if (!"".equals(terms.term().text())) values.add(new Match(terms.term(), terms.docFreq()));
|
149 |
1
| if (!terms.next()) break;
|
150 |
| } |
151 |
15
| return values;
|
152 |
| } |
153 |
| |
154 |
5
| public SearchResult search(String query, int sort, int start, int end) throws ParseException, IOException
|
155 |
| { |
156 |
5
| return search(query, null, sort, start, end);
|
157 |
| } |
158 |
| |
159 |
6
| public SearchResult search(String query, Query constraint, int sort, int start, int end) throws ParseException, IOException
|
160 |
| { |
161 |
6
| ParseResult search = parseQuery(query);
|
162 |
6
| if (search.filter != null && empty(search.query))
|
163 |
| { |
164 |
1
| search.query = new TermQuery(new Term(DocumentFactory.ALL_FIELD, DocumentFactory.ALL_VALUE));
|
165 |
| } |
166 |
6
| if (constraint != null)
|
167 |
| { |
168 |
1
| BooleanQuery constrained = new BooleanQuery();
|
169 |
1
| constrained.add(search.query, true, false);
|
170 |
1
| constrained.add(constraint, true, false);
|
171 |
1
| search.query = constrained;
|
172 |
| } |
173 |
6
| Sort order = (sort == ASCENDING) ? CREATED_ASCENDING : CREATED_DESCENDING;
|
174 |
6
| SearchResult result = search(search.query, search.filter, order, start, end);
|
175 |
6
| result.setQuery(query);
|
176 |
6
| result.setName(query);
|
177 |
6
| result.setTitle("[" + query + "]");
|
178 |
6
| result.setDescription("Search results for: " + query);
|
179 |
6
| return result;
|
180 |
| } |
181 |
| |
182 |
6
| private ParseResult parseQuery(String queryString) throws ParseException
|
183 |
| { |
184 |
6
| MetaAnalyzer analyzer = new MetaAnalyzer();
|
185 |
6
| Query query = QueryParser.parse(queryString, DocumentFactory.TEXT_FIELD, analyzer);
|
186 |
6
| ParseResult result = new ParseResult();
|
187 |
6
| result.query = query;
|
188 |
6
| if (analyzer.hasPoint())
|
189 |
| { |
190 |
1
| result.filter = new LocationFilter(analyzer.getLatitude(), analyzer.getLongitude(), analyzer.getRadius());
|
191 |
| } |
192 |
6
| return result;
|
193 |
| } |
194 |
| |
195 |
2
| protected SearchResult search(Query query)
|
196 |
| throws IOException |
197 |
| { |
198 |
2
| return search(query, null, Searcher.CREATED_DESCENDING);
|
199 |
| } |
200 |
| |
201 |
1
| private boolean empty(Query query)
|
202 |
| { |
203 |
1
| return query instanceof BooleanQuery && ((BooleanQuery) query).getClauses().length == 0;
|
204 |
| } |
205 |
| |
206 |
5
| protected SearchResult search(Query query, Filter filter, Sort sort)
|
207 |
| throws IOException |
208 |
| { |
209 |
5
| return search(query, filter, sort, 0, -1);
|
210 |
| } |
211 |
| |
212 |
24
| protected SearchResult search(Query query, Filter filter, Sort sort, int start, int end)
|
213 |
| throws IOException |
214 |
| { |
215 |
0
| if (start < -1) throw new IllegalArgumentException("Start value of " + start + " must be -1 or greater");
|
216 |
0
| if (end < -1) throw new IllegalArgumentException("End value of " + end + " must be be -1 or greater");
|
217 |
| |
218 |
24
| log.debug("query=" + query + ", filter=" + filter + ", sort=" + sort);
|
219 |
| |
220 |
24
| SearchResult result = new SearchResult();
|
221 |
24
| result.setQuery(query.toString());
|
222 |
24
| result.setName(query.toString());
|
223 |
24
| result.setTitle("[" + query.toString() + "]");
|
224 |
24
| result.setDescription("Search results for: " + query.toString());
|
225 |
24
| result.setCreated(new Date());
|
226 |
| |
227 |
24
| IndexReader reader = index.getReader();
|
228 |
24
| IndexSearcher searcher = new IndexSearcher(reader);
|
229 |
| |
230 |
24
| Hits hits = searcher.search(query, filter, sort);
|
231 |
| |
232 |
24
| result.setStart(start);
|
233 |
24
| result.setEnd(end < start && start < 0 ? start : (end >= hits.length() || end < start ? hits.length() - 1 : end));
|
234 |
24
| result.setTotal(hits.length());
|
235 |
| |
236 |
7
| if (end <= start && start < 0) return result;
|
237 |
| |
238 |
17
| float threshold = 0.0f;
|
239 |
| |
240 |
17
| for (int i = result.getStart() < 0 ? 0 : result.getStart(); i <= result.getEnd(); i++)
|
241 |
| { |
242 |
0
| if (hits.score(i) < threshold) break;
|
243 |
42
| Meta meta = factory.createMeta(hits.doc(i));
|
244 |
42
| try
|
245 |
| { |
246 |
42
| if (meta instanceof CollectionMeta)
|
247 |
| { |
248 |
3
| BooleanQuery b = new BooleanQuery();
|
249 |
3
| b.add(new BooleanClause(new TermQuery(new Term(DocumentFactory.PARENT_FIELD, meta.getPath())), true, false));
|
250 |
3
| b.add(new BooleanClause(PHOTO_CONSTRAINT, true, false));
|
251 |
| |
252 |
3
| SearchResult children = search(b, null, CREATED_ASCENDING, 0, 3);
|
253 |
3
| CollectionMeta collection = (CollectionMeta) meta;
|
254 |
3
| collection.setTotal(children.getTotal());
|
255 |
3
| collection.setStart(children.getStart());
|
256 |
3
| collection.setEnd(children.getEnd());
|
257 |
3
| collection.addFiles(children.getFiles());
|
258 |
| } |
259 |
| } |
260 |
| catch (Exception e) |
261 |
| { |
262 |
0
| throw new RuntimeException(e);
|
263 |
| } |
264 |
42
| result.addFile(meta);
|
265 |
| } |
266 |
| |
267 |
17
| result.setDuration(new Date().getTime() - result.getCreated().getTime());
|
268 |
| |
269 |
17
| return result;
|
270 |
| } |
271 |
| |
272 |
11
| public void setIndex(SearchIndex index)
|
273 |
| { |
274 |
11
| this.index = index;
|
275 |
| } |
276 |
| |
277 |
| private static class ParseResult |
278 |
| { |
279 |
| Query query; |
280 |
| Filter filter; |
281 |
| } |
282 |
| } |