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.jcr;
25
26 import org.apache.lucene.analysis.snowball.SnowballAnalyzer;
27 import org.apache.lucene.util.Version;
28 import org.modeshape.common.collection.Problems;
29 import org.modeshape.common.collection.SimpleProblems;
30 import org.modeshape.common.i18n.I18n;
31 import org.modeshape.common.text.TextEncoder;
32 import org.modeshape.common.text.UrlEncoder;
33 import org.modeshape.common.util.Logger;
34 import org.modeshape.graph.ExecutionContext;
35 import org.modeshape.graph.Graph;
36 import org.modeshape.graph.GraphI18n;
37 import org.modeshape.graph.connector.RepositoryConnectionFactory;
38 import org.modeshape.graph.observe.Changes;
39 import org.modeshape.graph.observe.Observable;
40 import org.modeshape.graph.observe.Observer;
41 import org.modeshape.graph.property.Path;
42 import org.modeshape.graph.query.QueryContext;
43 import org.modeshape.graph.query.QueryEngine;
44 import org.modeshape.graph.query.QueryResults;
45 import org.modeshape.graph.query.QueryResults.Columns;
46 import org.modeshape.graph.query.model.QueryCommand;
47 import org.modeshape.graph.query.model.TypeSystem;
48 import org.modeshape.graph.query.optimize.Optimizer;
49 import org.modeshape.graph.query.optimize.RuleBasedOptimizer;
50 import org.modeshape.graph.query.plan.CanonicalPlanner;
51 import org.modeshape.graph.query.plan.PlanHints;
52 import org.modeshape.graph.query.plan.PlanNode;
53 import org.modeshape.graph.query.plan.Planner;
54 import org.modeshape.graph.query.process.AbstractAccessComponent;
55 import org.modeshape.graph.query.process.ProcessingComponent;
56 import org.modeshape.graph.query.process.Processor;
57 import org.modeshape.graph.query.process.QueryProcessor;
58 import org.modeshape.graph.query.process.SelectComponent.Analyzer;
59 import org.modeshape.graph.query.validate.Schemata;
60 import org.modeshape.graph.request.AccessQueryRequest;
61 import org.modeshape.graph.request.InvalidWorkspaceException;
62 import org.modeshape.graph.request.processor.RequestProcessor;
63 import org.modeshape.graph.search.SearchEngine;
64 import org.modeshape.graph.search.SearchEngineIndexer;
65 import org.modeshape.graph.search.SearchEngineProcessor;
66 import org.modeshape.search.lucene.IndexRules;
67 import org.modeshape.search.lucene.LuceneConfiguration;
68 import org.modeshape.search.lucene.LuceneConfigurations;
69 import org.modeshape.search.lucene.LuceneSearchEngine;
70
71 import javax.jcr.RepositoryException;
72 import javax.jcr.query.InvalidQueryException;
73 import java.io.File;
74 import java.util.List;
75 import java.util.Map;
76 import java.util.Set;
77 import java.util.concurrent.ExecutorService;
78 import java.util.concurrent.Executors;
79
80
81
82
83 abstract class RepositoryQueryManager {
84
85 protected final String sourceName;
86
87 RepositoryQueryManager( String sourceName ) {
88 this.sourceName = sourceName;
89 }
90
91 public abstract QueryResults query( String workspaceName,
92 QueryCommand query,
93 Schemata schemata,
94 PlanHints hints,
95 Map<String, Object> variables ) throws InvalidQueryException;
96
97 public abstract QueryResults search( String workspaceName,
98 String searchExpression,
99 int maxRowCount,
100 int offset ) throws InvalidQueryException;
101
102
103
104
105
106
107
108 public void reindexContent() {
109
110 }
111
112
113
114
115
116
117
118
119 public void reindexContent( JcrWorkspace workspace ) {
120
121 }
122
123
124
125
126
127
128
129
130
131
132 public void reindexContent( JcrWorkspace workspace,
133 String path,
134 int depth ) {
135
136 }
137
138 static class PushDown extends RepositoryQueryManager {
139 private final ExecutionContext context;
140 private final RepositoryConnectionFactory connectionFactory;
141
142 PushDown( String sourceName,
143 ExecutionContext context,
144 RepositoryConnectionFactory connectionFactory ) {
145 super(sourceName);
146 this.context = context;
147 this.connectionFactory = connectionFactory;
148 }
149
150 private Graph workspaceGraph( String workspaceName ) {
151 Graph graph = Graph.create(this.sourceName, connectionFactory, context);
152
153 if (workspaceName != null) {
154 graph.useWorkspace(workspaceName);
155 }
156
157 return graph;
158 }
159
160 @Override
161 public QueryResults query( String workspaceName,
162 QueryCommand query,
163 Schemata schemata,
164 PlanHints hints,
165 Map<String, Object> variables ) {
166 Graph.BuildQuery builder = workspaceGraph(workspaceName).query(query, schemata);
167 if (variables != null) builder.using(variables);
168 if (hints != null) builder.using(hints);
169 return builder.execute();
170 }
171
172 @Override
173 public QueryResults search( String workspaceName,
174 String searchExpression,
175 int maxRowCount,
176 int offset ) {
177 return workspaceGraph(workspaceName).search(searchExpression, maxRowCount, offset);
178 }
179
180 }
181
182 static class Disabled extends RepositoryQueryManager {
183
184 Disabled( String sourceName ) {
185 super(sourceName);
186 }
187
188
189
190
191
192
193
194 @Override
195 public QueryResults query( String workspaceName,
196 QueryCommand query,
197 Schemata schemata,
198 PlanHints hints,
199 Map<String, Object> variables ) throws InvalidQueryException {
200 throw new InvalidQueryException(JcrI18n.queryIsDisabledInRepository.text(this.sourceName));
201 }
202
203
204
205
206
207
208 @Override
209 public QueryResults search( String workspaceName,
210 String searchExpression,
211 int maxRowCount,
212 int offset ) throws InvalidQueryException {
213 throw new InvalidQueryException(JcrI18n.queryIsDisabledInRepository.text(this.sourceName));
214 }
215 }
216
217 static class SelfContained extends RepositoryQueryManager {
218 private final ExecutionContext context;
219 private final String sourceName;
220 private final LuceneConfiguration configuration;
221 private final SearchEngine searchEngine;
222 private final Observer searchObserver;
223 private final ExecutorService service;
224 private final QueryEngine queryEngine;
225 private final RepositoryConnectionFactory connectionFactory;
226
227 SelfContained( ExecutionContext context,
228 String nameOfSourceToBeSearchable,
229 RepositoryConnectionFactory connectionFactory,
230 Observable observable,
231 RepositoryNodeTypeManager nodeTypeManager,
232 String indexDirectory,
233 boolean updateIndexesSynchronously ) throws RepositoryException {
234 super(nameOfSourceToBeSearchable);
235
236 this.context = context;
237 this.sourceName = nameOfSourceToBeSearchable;
238 this.connectionFactory = connectionFactory;
239
240 TextEncoder encoder = new UrlEncoder();
241 if (indexDirectory != null) {
242 File indexDir = new File(indexDirectory);
243 if (indexDir.exists()) {
244
245 if (!indexDir.isDirectory()) {
246
247 I18n msg = JcrI18n.searchIndexDirectoryOptionSpecifiesFileNotDirectory;
248 throw new RepositoryException(msg.text(indexDirectory, sourceName));
249 }
250 if (!indexDir.canWrite()) {
251
252 I18n msg = JcrI18n.searchIndexDirectoryOptionSpecifiesDirectoryThatCannotBeWrittenTo;
253 throw new RepositoryException(msg.text(indexDirectory, sourceName));
254 }
255 if (!indexDir.canRead()) {
256
257 I18n msg = JcrI18n.searchIndexDirectoryOptionSpecifiesDirectoryThatCannotBeRead;
258 throw new RepositoryException(msg.text(indexDirectory, sourceName));
259 }
260
261 } else {
262
263 if (!indexDir.mkdirs()) {
264 I18n msg = JcrI18n.searchIndexDirectoryOptionSpecifiesDirectoryThatCannotBeCreated;
265 throw new RepositoryException(msg.text(indexDirectory, sourceName));
266 }
267
268 }
269 configuration = LuceneConfigurations.using(indexDir, encoder, encoder);
270 } else {
271
272 configuration = LuceneConfigurations.inMemory();
273 }
274 assert configuration != null;
275
276
277 IndexRules indexRules = nodeTypeManager.getRepositorySchemata().getIndexRules();
278
279
280 org.apache.lucene.analysis.Analyzer analyzer = new SnowballAnalyzer(Version.LUCENE_30, "English");
281 boolean verifyWorkspaces = false;
282 searchEngine = new LuceneSearchEngine(nameOfSourceToBeSearchable, connectionFactory, verifyWorkspaces, configuration,
283 indexRules, analyzer);
284
285
286 if (updateIndexesSynchronously) {
287 this.service = null;
288 this.searchObserver = new Observer() {
289 @SuppressWarnings( "synthetic-access" )
290 public void notify( Changes changes ) {
291 if (changes.getSourceName().equals(sourceName)) {
292 process(changes);
293 }
294 }
295 };
296 } else {
297
298 this.service = Executors.newCachedThreadPool();
299 this.searchObserver = new Observer() {
300 @SuppressWarnings( "synthetic-access" )
301 public void notify( final Changes changes ) {
302 if (changes.getSourceName().equals(sourceName)) {
303 service.submit(new Runnable() {
304 public void run() {
305 process(changes);
306 }
307 });
308 }
309 }
310 };
311 }
312 observable.register(this.searchObserver);
313
314
315 Planner planner = new CanonicalPlanner();
316 Optimizer optimizer = new RuleBasedOptimizer();
317 Processor processor = new QueryProcessor() {
318
319
320
321
322
323
324
325
326
327 @Override
328 protected ProcessingComponent createAccessComponent( QueryCommand originalQuery,
329 QueryContext context,
330 PlanNode accessNode,
331 Columns resultColumns,
332 Analyzer analyzer ) {
333 return new AccessQueryProcessor((GraphQueryContext)context, resultColumns, accessNode);
334 }
335 };
336 this.queryEngine = new QueryEngine(planner, optimizer, processor);
337
338
339 reindexContent();
340 }
341
342 protected void process( Changes changes ) {
343 try {
344 searchEngine.index(context, changes.getChangeRequests());
345 } catch (RuntimeException e) {
346 Logger.getLogger(getClass()).error(e, JcrI18n.errorUpdatingQueryIndexes, e.getLocalizedMessage());
347 }
348 }
349
350 @Override
351 public QueryResults query( String workspaceName,
352 QueryCommand query,
353 Schemata schemata,
354 PlanHints hints,
355 Map<String, Object> variables ) {
356 TypeSystem typeSystem = context.getValueFactories().getTypeSystem();
357 SearchEngineProcessor processor = searchEngine.createProcessor(context, null, true);
358 try {
359 QueryContext context = new GraphQueryContext(schemata, typeSystem, hints, new SimpleProblems(), variables,
360 processor, workspaceName);
361 return queryEngine.execute(context, query);
362 } finally {
363 processor.close();
364 }
365 }
366
367 @Override
368 public QueryResults search( String workspaceName,
369 String searchExpression,
370 int maxRowCount,
371 int offset ) {
372 Graph graph = Graph.create(sourceName, connectionFactory, context);
373
374 if (workspaceName != null) {
375 graph.useWorkspace(workspaceName);
376 }
377
378 return graph.search(searchExpression, maxRowCount, offset);
379 }
380
381
382
383
384
385
386 @Override
387 public void reindexContent() {
388
389 Set<String> workspaces = Graph.create(sourceName, connectionFactory, context).getWorkspaces();
390
391
392 SearchEngineIndexer indexer = new SearchEngineIndexer(context, searchEngine, connectionFactory);
393 try {
394 for (String workspace : workspaces) {
395 indexer.index(workspace);
396 }
397 } finally {
398 indexer.close();
399 }
400
401 }
402
403
404
405
406
407
408 @Override
409 public void reindexContent( JcrWorkspace workspace ) {
410 SearchEngineIndexer indexer = new SearchEngineIndexer(context, searchEngine, connectionFactory);
411 try {
412 indexer.index(workspace.getName());
413 } finally {
414 indexer.close();
415 }
416 }
417
418
419
420
421
422
423 @Override
424 public void reindexContent( JcrWorkspace workspace,
425 String path,
426 int depth ) {
427 Path at = workspace.context().getValueFactories().getPathFactory().create(path);
428 SearchEngineIndexer indexer = new SearchEngineIndexer(context, searchEngine, connectionFactory);
429 try {
430 indexer.index(workspace.getName(), at, depth);
431 } finally {
432 indexer.close();
433 }
434 }
435
436 protected class GraphQueryContext extends QueryContext {
437 private final RequestProcessor processor;
438 private final String workspaceName;
439
440 protected GraphQueryContext( Schemata schemata,
441 TypeSystem typeSystem,
442 PlanHints hints,
443 Problems problems,
444 Map<String, Object> variables,
445 RequestProcessor processor,
446 String workspaceName ) {
447 super(schemata, typeSystem, hints, problems, variables);
448 this.processor = processor;
449 this.workspaceName = workspaceName;
450 }
451
452
453
454
455 public RequestProcessor getProcessor() {
456 return processor;
457 }
458
459
460
461
462 public String getWorkspaceName() {
463 return workspaceName;
464 }
465 }
466
467 protected static class AccessQueryProcessor extends AbstractAccessComponent {
468 private final AccessQueryRequest accessRequest;
469
470 protected AccessQueryProcessor( GraphQueryContext context,
471 Columns columns,
472 PlanNode accessNode ) {
473 super(context, columns, accessNode);
474 accessRequest = new AccessQueryRequest(context.getWorkspaceName(), sourceName, getColumns(), andedConstraints,
475 limit, context.getSchemata(), context.getVariables());
476 context.getProcessor().process(accessRequest);
477 }
478
479
480
481
482
483
484 public AccessQueryRequest getAccessRequest() {
485 return accessRequest;
486 }
487
488
489
490
491
492
493 @Override
494 public List<Object[]> execute() {
495 if (accessRequest.getError() != null) {
496 I18n msg = GraphI18n.errorWhilePerformingQuery;
497 getContext().getProblems().addError(accessRequest.getError(),
498 msg,
499 accessNode.getString(),
500 accessRequest.workspace(),
501 sourceName,
502 accessRequest.getError().getLocalizedMessage());
503 return emptyTuples();
504 }
505 return accessRequest.getTuples();
506 }
507
508 }
509
510 }
511 }