Author: rhauch
Date: 2009-11-18 14:39:05 -0500 (Wed, 18 Nov 2009)
New Revision: 1329
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/
trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchEngine.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchEngineException.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchException.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchProvider.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/search/
trunk/dna-graph/src/test/java/org/jboss/dna/graph/search/SearchEngineTest.java
trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexSearchProvider.java
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneConfiguration.java
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneConfigurations.java
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneException.java
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryComponent.java
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneSession.java
Removed:
trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfiguration.java
trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfigurations.java
trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexLayout.java
trunk/dna-search/src/main/java/org/jboss/dna/search/IndexLayout.java
trunk/dna-search/src/main/java/org/jboss/dna/search/IndexSession.java
trunk/dna-search/src/main/java/org/jboss/dna/search/KitchenSinkIndexLayout.java
trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngine.java
trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngineException.java
trunk/dna-search/src/main/java/org/jboss/dna/search/filters/ResultFilter.java
trunk/dna-search/src/test/java/org/jboss/dna/search/SearchEngineTest.java
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java
trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties
trunk/dna-search/src/main/java/org/jboss/dna/search/IndexRules.java
trunk/dna-search/src/main/java/org/jboss/dna/search/SearchI18n.java
trunk/dna-search/src/main/resources/org/jboss/dna/search/SearchI18n.properties
Log:
DNA-467 refactored the search engine components, moving the general-purpose classes (i.e.,
those that don't depend on Lucene) into 'dna-graph'.
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java 2009-11-18 19:37:57
UTC (rev 1328)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java 2009-11-18 19:39:05
UTC (rev 1329)
@@ -160,6 +160,16 @@
public static I18n expectingLiteralAndUnableToParseAsDouble;
public static I18n expectingLiteralAndUnableToParseAsDate;
+ /* Search */
+ public static I18n errorWhileIndexingContentAtPath;
+ public static I18n errorWhileRemovingContentAtPath;
+ public static I18n errorWhileUpdatingContent;
+ public static I18n errorWhileCommittingIndexChanges;
+ public static I18n errorWhileOptimizingIndexes;
+ public static I18n errorWhilePerformingSearch;
+ public static I18n errorWhilePerformingQuery;
+ public static I18n errorWhileRemovingIndexesForWorkspace;
+
static {
try {
I18n.initialize(GraphI18n.class);
Copied: trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchEngine.java (from
rev 1328, trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngine.java)
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchEngine.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchEngine.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -0,0 +1,807 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.graph.search;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import net.jcip.annotations.GuardedBy;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.i18n.I18n;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Graph;
+import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.Subgraph;
+import org.jboss.dna.graph.SubgraphNode;
+import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
+import org.jboss.dna.graph.connector.RepositorySource;
+import org.jboss.dna.graph.connector.RepositorySourceException;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.jboss.dna.graph.request.ChangeRequest;
+import org.jboss.dna.graph.request.InvalidWorkspaceException;
+import org.jboss.dna.graph.search.SearchProvider.Session;
+
+/**
+ * A component that acts as a search engine for the content within a single {@link
RepositorySource}. This engine manages a set of
+ * indexes and provides search functionality for each of the workspaces within the
source, and provides various methods to
+ * (re)index the content contained with source's workspaces and keep the indexes
up-to-date via changes.
+ */
+@ThreadSafe
+public class SearchEngine {
+
+ /**
+ * The default maximum number of changes that can be made to an index before the
indexes are automatically optimized is * * *
+ * * {@value}
+ */
+ public static final int DEFAULT_MAX_CHANGES_BEFORE_AUTOMATIC_OPTIMIZATION = 0;
+
+ protected final ExecutionContext context;
+ private final String sourceName;
+ private final RepositoryConnectionFactory connectionFactory;
+ protected final SearchProvider indexLayout;
+ private final int maxChangesBeforeAutomaticOptimization;
+ @GuardedBy( "workspacesLock" )
+ private final Map<String, Workspace> workspacesByName = new HashMap<String,
Workspace>();
+ private final ReadWriteLock workspacesLock = new ReentrantReadWriteLock();
+
+ /**
+ * Create a search engine instance given the supplied {@link ExecutionContext
execution context}, name of the
+ * {@link RepositorySource}, the {@link RepositoryConnectionFactory factory for
RepositorySource connections}, and the
+ * {@link SearchProvider search provider}.
+ *
+ * @param context the execution context for indexing and optimization operations
+ * @param sourceName the name of the {@link RepositorySource}
+ * @param connectionFactory the connection factory
+ * @param indexLayout the specification of the Lucene index layout
+ * @param maxChangesBeforeAutomaticOptimization the number of changes that can be
made to the index before the indexes are
+ * automatically optimized; may be 0 or a negative number if no automatic
optimization should be done
+ * @throws IllegalArgumentException if any of the parameters (other than indexing
strategy) are null
+ */
+ public SearchEngine( ExecutionContext context,
+ String sourceName,
+ RepositoryConnectionFactory connectionFactory,
+ SearchProvider indexLayout,
+ int maxChangesBeforeAutomaticOptimization ) {
+ CheckArg.isNotNull(context, "context");
+ CheckArg.isNotNull(sourceName, "sourceName");
+ CheckArg.isNotNull(connectionFactory, "connectionFactory");
+ this.sourceName = sourceName;
+ this.connectionFactory = connectionFactory;
+ this.indexLayout = indexLayout;
+ this.context = context;
+ this.maxChangesBeforeAutomaticOptimization =
maxChangesBeforeAutomaticOptimization < 0 ? 0 : maxChangesBeforeAutomaticOptimization;
+ }
+
+ /**
+ * Create a search engine instance given the supplied {@link ExecutionContext
execution context}, name of the
+ * {@link RepositorySource}, the {@link RepositoryConnectionFactory factory for
RepositorySource connections}, and the
+ * {@link SearchProvider search provider} that defines where each workspace's
indexes should be placed.
+ *
+ * @param context the execution context for indexing and optimization operations
+ * @param sourceName the name of the {@link RepositorySource}
+ * @param connectionFactory the connection factory
+ * @param indexLayout the specification of the Lucene index layout
+ * @throws IllegalArgumentException if any of the parameters (other than indexing
strategy) are null
+ */
+ public SearchEngine( ExecutionContext context,
+ String sourceName,
+ RepositoryConnectionFactory connectionFactory,
+ SearchProvider indexLayout ) {
+ this(context, sourceName, connectionFactory, indexLayout,
DEFAULT_MAX_CHANGES_BEFORE_AUTOMATIC_OPTIMIZATION);
+ }
+
+ /**
+ * Get the name of the RepositorySource that this engine is to use.
+ *
+ * @return the source name; never null
+ */
+ public String getSourceName() {
+ return sourceName;
+ }
+
+ /**
+ * Get the context in which all indexing operations execute.
+ *
+ * @return the execution context; never null
+ */
+ public ExecutionContext getContext() {
+ return context;
+ }
+
+ /**
+ * @return maxChangesBeforeAutomaticOptimization
+ */
+ public int getMaxChangesBeforeAutomaticOptimization() {
+ return maxChangesBeforeAutomaticOptimization;
+ }
+
+ /**
+ * Utility to create a Graph for the source.
+ *
+ * @return the graph instance; never null
+ */
+ final Graph graph() {
+ return Graph.create(sourceName, connectionFactory, context);
+ }
+
+ /**
+ * Utility to obtain the root path.
+ *
+ * @return the root path; never null
+ */
+ final Path rootPath() {
+ return context.getValueFactories().getPathFactory().createRootPath();
+ }
+
+ /**
+ * Utility to obtain a readable string representation of the supplied path.
+ *
+ * @param path the path
+ * @return the readable string representation; may be null if path is null
+ */
+ final String readable( Path path ) {
+ return context.getValueFactories().getStringFactory().create(path);
+ }
+
+ /**
+ * Index all of the content at or below the supplied path in the named workspace
within the {@link #getSourceName() source}.
+ * If the starting point is the root node, then this method will drop the existing
index(es) and rebuild from the content in
+ * the workspace and source.
+ * <p>
+ * This method operates synchronously and returns when the requested indexing is
completed.
+ * </p>
+ *
+ * @param workspaceName the name of the workspace
+ * @param startingPoint the location that represents the content to be indexed; must
have a path
+ * @param depthPerRead the depth of each subgraph read operation
+ * @throws IllegalArgumentException if the workspace name or location are null
+ * @throws RepositorySourceException if there is a problem accessing the content
+ * @throws SearchEngineException if there is a problem updating the indexes
+ * @throws InvalidWorkspaceException if the workspace does not exist
+ */
+ public void index( String workspaceName,
+ Location startingPoint,
+ int depthPerRead ) throws RepositorySourceException,
SearchEngineException {
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ CheckArg.isNotNull(startingPoint, "startingPoint");
+ assert startingPoint.hasPath();
+
+ Workspace workspace = getWorkspace(workspaceName);
+ if (startingPoint.getPath().isRoot()) {
+ // More efficient to just start over with a new index ...
+ workspace.execute(true, addContent(startingPoint, depthPerRead));
+ } else {
+ // Have to first remove the content below the starting point, then add it
again ...
+ workspace.execute(false, removeContent(startingPoint),
addContent(startingPoint, depthPerRead));
+ }
+ }
+
+ /**
+ * Index all of the content at or below the supplied path in the named workspace
within the {@link #getSourceName() source}.
+ * If the starting point is the root node, then this method will drop the existing
index(es) and rebuild from the content in
+ * the workspace and source.
+ * <p>
+ * This method operates synchronously and returns when the requested indexing is
completed.
+ * </p>
+ *
+ * @param workspaceName the name of the workspace
+ * @param startingPoint the path that represents the content to be indexed
+ * @param depthPerRead the depth of each subgraph read operation
+ * @throws IllegalArgumentException if the workspace name or path are null
+ * @throws RepositorySourceException if there is a problem accessing the content
+ * @throws SearchEngineException if there is a problem updating the indexes
+ * @throws InvalidWorkspaceException if the workspace does not exist
+ */
+ public void index( String workspaceName,
+ Path startingPoint,
+ int depthPerRead ) throws RepositorySourceException,
SearchEngineException {
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ CheckArg.isNotNull(startingPoint, "startingPoint");
+ index(workspaceName, Location.create(startingPoint), depthPerRead);
+ }
+
+ /**
+ * Index all of the content in the named workspace within the {@link #getSourceName()
source}. This method operates
+ * synchronously and returns when the requested indexing is completed.
+ *
+ * @param workspaceName the name of the workspace
+ * @param depthPerRead the depth of each subgraph read operation
+ * @throws IllegalArgumentException if the workspace name is null
+ * @throws RepositorySourceException if there is a problem accessing the content
+ * @throws SearchEngineException if there is a problem updating the indexes
+ * @throws InvalidWorkspaceException if the workspace does not exist
+ */
+ public void index( String workspaceName,
+ int depthPerRead ) throws RepositorySourceException,
SearchEngineException {
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ index(workspaceName, rootPath(), depthPerRead);
+ }
+
+ /**
+ * Index (or re-index) all of the content in all of the workspaces within the source.
This method operates synchronously and
+ * returns when the requested indexing is completed.
+ *
+ * @param depthPerRead the depth of each subgraph read operation
+ * @throws RepositorySourceException if there is a problem accessing the content
+ * @throws SearchEngineException if there is a problem updating the indexes
+ */
+ public void index( int depthPerRead ) throws RepositorySourceException,
SearchEngineException {
+ Path rootPath = rootPath();
+ for (String workspaceName : graph().getWorkspaces()) {
+ index(workspaceName, rootPath, depthPerRead);
+ }
+ }
+
+ /**
+ * Update the indexes with the supplied set of changes to the content.
+ *
+ * @param changes the set of changes to the content
+ * @throws IllegalArgumentException if the path is null
+ * @throws RepositorySourceException if there is a problem accessing the content
+ * @throws SearchEngineException if there is a problem updating the indexes
+ */
+ public void index( final Iterable<ChangeRequest> changes ) throws
SearchEngineException {
+ // First break up all the changes into different collections, one collection per
workspace ...
+ Map<String, Collection<ChangeRequest>> changesByWorkspace = new
HashMap<String, Collection<ChangeRequest>>();
+ for (ChangeRequest request : changes) {
+ String workspaceName = request.changedWorkspace();
+ Collection<ChangeRequest> changesForWorkspace =
changesByWorkspace.get(workspaceName);
+ if (changesForWorkspace == null) {
+ changesForWorkspace = new LinkedList<ChangeRequest>();
+ changesByWorkspace.put(workspaceName, changesForWorkspace);
+ }
+ changesForWorkspace.add(request);
+ }
+ // Now update the indexes for each workspace (serially). This minimizes the time
that each workspace
+ // locks its indexes for writing.
+ for (Map.Entry<String, Collection<ChangeRequest>> entry :
changesByWorkspace.entrySet()) {
+ String workspaceName = entry.getKey();
+ Collection<ChangeRequest> changesForWorkspace = entry.getValue();
+ getWorkspace(workspaceName).execute(false,
updateContent(changesForWorkspace));
+ }
+ }
+
+ /**
+ * Invoke the engine's garbage collection on all indexes used by all workspaces
in the source. This method reclaims space and
+ * optimizes the index. This should be done on a periodic basis after changes are
made to the engine's indexes.
+ *
+ * @throws SearchEngineException if there is a problem during optimization
+ */
+ public void optimize() throws SearchEngineException {
+ for (String workspaceName : graph().getWorkspaces()) {
+ getWorkspace(workspaceName).execute(false, optimizeContent());
+ }
+ }
+
+ /**
+ * Invoke the engine's garbage collection for the indexes associated with the
specified workspace. This method reclaims space
+ * and optimizes the index. This should be done on a periodic basis after changes are
made to the engine's indexes.
+ *
+ * @param workspaceName the name of the workspace
+ * @throws IllegalArgumentException if the workspace name is null
+ * @throws SearchEngineException if there is a problem during optimization
+ * @throws InvalidWorkspaceException if the workspace does not exist
+ */
+ public void optimize( String workspaceName ) throws SearchEngineException {
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ getWorkspace(workspaceName).execute(false, optimizeContent());
+ }
+
+ /**
+ * Perform a full-text search of the content in the named workspace, given the
maximum number of results and the offset
+ * defining the first result the caller is interested in.
+ *
+ * @param context the execution context in which the search is to take place; may not
be null
+ * @param workspaceName the name of the workspace
+ * @param fullTextSearch the full-text search to be performed; may not be null
+ * @param maxResults the maximum number of results that are to be returned; always
positive
+ * @param offset the number of initial results to skip, or 0 if the first results are
to be returned
+ * @return the activity that will perform the work
+ * @throws IllegalArgumentException if the execution context or workspace name are
null
+ * @throws SearchEngineException if there is a problem during optimization
+ * @throws InvalidWorkspaceException if the workspace does not exist
+ */
+ public List<Location> fullTextSearch( ExecutionContext context,
+ String workspaceName,
+ String fullTextSearch,
+ int maxResults,
+ int offset ) {
+ CheckArg.isNotNull(context, "context");
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ Search searchActivity = searchContent(context, fullTextSearch, maxResults,
offset);
+ getWorkspace(workspaceName).execute(false, searchActivity);
+ return searchActivity.getResults();
+ }
+
+ /**
+ * Perform a query of the content in the named workspace, given the Abstract Query
Model representation of the query.
+ *
+ * @param context the execution context in which the search is to take place; may not
be null
+ * @param workspaceName the name of the workspace
+ * @param query the query that is to be executed, in the form of the Abstract Query
Model
+ * @param schemata the definition of the tables and views that can be used in the
query; may not be null
+ * @return the query results; never null
+ * @throws IllegalArgumentException if the context, query, or schemata references are
null
+ */
+ public QueryResults query( ExecutionContext context,
+ String workspaceName,
+ QueryCommand query,
+ Schemata schemata ) {
+ CheckArg.isNotNull(context, "context");
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ CheckArg.isNotNull(query, "query");
+ CheckArg.isNotNull(schemata, "schemata");
+ QueryContext queryContext = new QueryContext(context, schemata);
+ Query queryActivity = queryContent(queryContext, query);
+ getWorkspace(workspaceName).execute(false, queryActivity);
+ return queryActivity.getResults();
+ }
+
+ /**
+ * Remove the supplied index from the search engine. This is typically done when the
workspace has been deleted from the
+ * source, or when
+ *
+ * @param workspaceName the name of the workspace
+ * @throws IllegalArgumentException if the workspace name is null
+ * @throws SearchEngineException if there is a problem removing the workspace
+ */
+ public void removeWorkspace( String workspaceName ) throws SearchEngineException {
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ try {
+ workspacesLock.writeLock().lock();
+ // Check whether another thread got in and created the engine while we waited
...
+ Workspace workspace = workspacesByName.remove(workspaceName);
+ if (workspace != null) {
+ indexLayout.destroyIndexes(context, getSourceName(), workspaceName);
+ }
+ } catch (IOException e) {
+ String message =
GraphI18n.errorWhileRemovingIndexesForWorkspace.text(sourceName, workspaceName,
e.getMessage());
+ throw new SearchEngineException(message, e);
+ } finally {
+ workspacesLock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Remove from the search engine all workspace-related indexes, thereby cleaning up
any resources used by this search engine.
+ *
+ * @throws SearchEngineException if there is a problem removing any of the workspace
+ */
+ public void removeWorkspaces() throws SearchEngineException {
+ try {
+ workspacesLock.writeLock().lock();
+ for (String workspaceName : new
HashSet<String>(workspacesByName.keySet())) {
+ removeWorkspace(workspaceName);
+ }
+ } finally {
+ workspacesLock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Get the search engine for the workspace with the supplied name.
+ *
+ * @param workspaceName the name of the workspace
+ * @return the workspace's search engine
+ * @throws InvalidWorkspaceException if the workspace does not exist
+ */
+ protected Workspace getWorkspace( String workspaceName ) {
+ Workspace workspace = null;
+ try {
+ workspacesLock.readLock().lock();
+ workspace = workspacesByName.get(workspaceName);
+ } finally {
+ workspacesLock.readLock().unlock();
+ }
+
+ if (workspace == null) {
+ // Verify the workspace does exist ...
+ if (!graph().getWorkspaces().contains(workspaceName)) {
+ String msg =
GraphI18n.workspaceDoesNotExistInRepository.text(workspaceName, getSourceName());
+ throw new InvalidWorkspaceException(msg);
+ }
+ try {
+ workspacesLock.writeLock().lock();
+ // Check whether another thread got in and created the engine while we
waited ...
+ workspace = workspacesByName.get(workspaceName);
+ if (workspace == null) {
+ // Create the engine and register it ...
+ workspace = new Workspace(workspaceName);
+ workspacesByName.put(workspaceName, workspace);
+ }
+ } finally {
+ workspacesLock.writeLock().unlock();
+ }
+ }
+ return workspace;
+ }
+
+ protected class Workspace {
+ private final String sourceName;
+ private final String workspaceName;
+ protected final AtomicInteger modifiedNodesSinceLastOptimize = new
AtomicInteger(0);
+
+ protected Workspace( String workspaceName ) {
+ this.workspaceName = workspaceName;
+ this.sourceName = getSourceName();
+ }
+
+ /**
+ * Get the workspace name.
+ *
+ * @return the workspace name; never null
+ */
+ public String getWorkspaceName() {
+ return workspaceName;
+ }
+
+ /**
+ * Execute the supplied activities against the indexes.
+ *
+ * @param overwrite true if the existing indexes should be overwritten, or false
if they should be used
+ * @param activities the activities to execute
+ * @throws SearchEngineException if there is a problem performing the activities
+ */
+ protected final void execute( boolean overwrite,
+ Activity... activities ) throws
SearchEngineException {
+ // Determine if the activities are readonly ...
+ boolean readOnly = true;
+ for (Activity activity : activities) {
+ if (!(activity instanceof ReadOnlyActivity)) {
+ readOnly = false;
+ break;
+ }
+ }
+
+ // Create a session ...
+ Session session = indexLayout.createSession(context, sourceName,
workspaceName, overwrite, readOnly);
+ assert session != null;
+
+ // Execute the various activities ...
+ Throwable error = null;
+ try {
+ int numChanges = 0;
+ for (Activity activity : activities) {
+ try {
+ numChanges += activity.execute(session);
+ } catch (RuntimeException e) {
+ error = e;
+ throw e;
+ }
+ }
+ if (numChanges > 0) {
+ numChanges =
this.modifiedNodesSinceLastOptimize.addAndGet(numChanges);
+ // Determine if there have been enough changes made to run the
optimizer ...
+ int maxChanges = getMaxChangesBeforeAutomaticOptimization();
+ if (maxChanges > 0 && numChanges >= maxChanges) {
+ Activity optimizer = optimizeContent();
+ try {
+ optimizer.execute(session);
+ } catch (RuntimeException e) {
+ error = e;
+ throw e;
+ }
+ }
+ }
+ } finally {
+ try {
+ if (error == null) {
+ session.commit();
+ } else {
+ session.rollback();
+ }
+ } catch (RuntimeException e2) {
+ // We don't want to lose the existing error, if there is one ...
+ if (error == null) {
+ I18n msg = GraphI18n.errorWhileCommittingIndexChanges;
+ throw new SearchEngineException(msg.text(workspaceName,
sourceName, e2.getMessage()), e2);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Create an activity that will optimize the indexes.
+ *
+ * @return the activity that will perform the work
+ */
+ protected Activity optimizeContent() {
+ return new Activity() {
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.search.SearchEngine.Activity#execute(org.jboss.dna.graph.search.SearchProvider.Session)
+ */
+ public int execute( Session session ) {
+ session.optimize();
+ return 0; // no lines changed
+ }
+
+ public String messageFor( Throwable error,
+ String sourceName,
+ String workspaceName ) {
+ return GraphI18n.errorWhileOptimizingIndexes.text(sourceName,
workspaceName, error.getMessage());
+ }
+ };
+ }
+
+ /**
+ * Create an activity that will read from the source the content at the supplied
location and add the content to the search
+ * index.
+ *
+ * @param location the location of the content to read; may not be null
+ * @param depthPerRead the depth of each read operation; always positive
+ * @return the activity that will perform the work
+ */
+ protected Activity addContent( final Location location,
+ final int depthPerRead ) {
+ return new Activity() {
+ public int execute( Session session ) {
+
+ // Create a queue that we'll use to walk the content ...
+ LinkedList<Location> locationsToRead = new
LinkedList<Location>();
+ locationsToRead.add(location);
+ int count = 0;
+
+ // Now read and index the content ...
+ Graph graph = graph();
+ graph.useWorkspace(session.getWorkspaceName());
+ while (!locationsToRead.isEmpty()) {
+ Location location = locationsToRead.poll();
+ if (location == null) continue;
+ Subgraph subgraph =
graph.getSubgraphOfDepth(depthPerRead).at(location);
+ // Index all of the nodes within this subgraph ...
+ for (SubgraphNode node : subgraph) {
+ // Index the node ...
+ session.index(node);
+ ++count;
+
+ // Process the children ...
+ for (Location child : node.getChildren()) {
+ if (!subgraph.includes(child)) {
+ // Record this location as needing to be read ...
+ locationsToRead.add(child);
+ }
+ }
+ }
+ }
+ return count;
+ }
+
+ public String messageFor( Throwable error,
+ String sourceName,
+ String workspaceName ) {
+ String path = readable(location.getPath());
+ return GraphI18n.errorWhileIndexingContentAtPath.text(path,
workspaceName, sourceName, error.getMessage());
+ }
+ };
+ }
+
+ /**
+ * Create an activity that will remove from the indexes all documents that represent
content at or below the specified
+ * location.
+ *
+ * @param location the location of the content to removed; may not be null
+ * @return the activity that will perform the work
+ */
+ protected Activity removeContent( final Location location ) {
+ return new Activity() {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.search.SearchEngine.Activity#execute(org.jboss.dna.graph.search.SearchProvider.Session)
+ */
+ public int execute( Session session ) {
+ // Delete the content at/below the path ...
+ return session.deleteBelow(location.getPath());
+ }
+
+ public String messageFor( Throwable error,
+ String sourceName,
+ String workspaceName ) {
+ String path = readable(location.getPath());
+ return GraphI18n.errorWhileRemovingContentAtPath.text(path,
workspaceName, sourceName, error.getMessage());
+ }
+ };
+ }
+
+ /**
+ * Create an activity that will update the indexes with changes that were already
made to the content.
+ *
+ * @param changes the changes that have been made to the content; may not be null
+ * @return the activity that will perform the work
+ */
+ protected Activity updateContent( final Iterable<ChangeRequest> changes ) {
+ return new Activity() {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.search.SearchEngine.Activity#execute(org.jboss.dna.graph.search.SearchProvider.Session)
+ */
+ public int execute( Session session ) {
+ return session.apply(changes);
+ }
+
+ public String messageFor( Throwable error,
+ String sourceName,
+ String workspaceName ) {
+ return GraphI18n.errorWhileUpdatingContent.text(workspaceName,
sourceName, error.getMessage());
+ }
+ };
+ }
+
+ /**
+ * Create an activity that will perform a full-text search given the supplied query.
+ *
+ * @param context the context in which the search is to be performed; may not be
null
+ * @param fullTextSearch the full-text search to be performed; may not be null
+ * @param maxResults the maximum number of results that are to be returned; always
positive
+ * @param offset the number of initial results to skip, or 0 if the first results are
to be returned
+ * @return the activity that will perform the work; never null
+ */
+ protected Search searchContent( final ExecutionContext context,
+ final String fullTextSearch,
+ final int maxResults,
+ final int offset ) {
+ final List<Location> results = new ArrayList<Location>(maxResults);
+ return new Search() {
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.search.SearchEngine.Activity#execute(org.jboss.dna.graph.search.SearchProvider.Session)
+ */
+ public int execute( Session session ) {
+ session.search(context, fullTextSearch, maxResults, offset, results);
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.search.SearchEngine.Activity#messageFor(java.lang.Throwable,
java.lang.String,
+ * java.lang.String)
+ */
+ public String messageFor( Throwable error,
+ String sourceName,
+ String workspaceName ) {
+ return GraphI18n.errorWhilePerformingSearch.text(fullTextSearch,
workspaceName, sourceName, error.getMessage());
+ }
+
+ public List<Location> getResults() {
+ return results;
+ }
+ };
+ }
+
+ /**
+ * Create an activity that will perform a query against the index.
+ *
+ * @param context the context in which the search is to be performed; may not be
null
+ * @param query the query to be performed; may not be null
+ * @return the activity that will perform the query; never null
+ */
+ protected Query queryContent( final QueryContext context,
+ final QueryCommand query ) {
+ return new Query() {
+ private QueryResults results = null;
+
+ public int execute( Session session ) throws SearchException {
+ results = session.query(context, query);
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.search.SearchEngine.Activity#messageFor(java.lang.Throwable,
java.lang.String,
+ * java.lang.String)
+ */
+ public String messageFor( Throwable error,
+ String sourceName,
+ String workspaceName ) {
+ return GraphI18n.errorWhilePerformingQuery.text(query, workspaceName,
sourceName, error.getMessage());
+ }
+
+ public QueryResults getResults() {
+ return results;
+ }
+ };
+ }
+
+ /**
+ * Interface for activities that will be executed against a workspace. These
activities don't have to commit or roll back the
+ * writer, nor do they have to translate the exceptions, since this is done by the
+ * {@link Workspace#execute(boolean, Activity...)} method.
+ */
+ protected interface Activity {
+
+ /**
+ * Perform the activity by using the index writer.
+ *
+ * @param indexSession the index session that should be used by the activity;
never null
+ * @return the number of changes that were made by this activity
+ */
+ int execute( Session indexSession );
+
+ /**
+ * Translate an exception obtained during {@link #execute(Session) execution}
into a single message.
+ *
+ * @param t the exception
+ * @param sourceName the name of the source
+ * @param workspaceName the name of the workspace
+ * @return the error message
+ */
+ String messageFor( Throwable t,
+ String sourceName,
+ String workspaceName );
+ }
+
+ /**
+ * A read-only activity.
+ */
+ protected interface ReadOnlyActivity extends Activity {
+ }
+
+ /**
+ * A search activity.
+ */
+ protected interface Search extends ReadOnlyActivity {
+ /**
+ * Get the results of the search.
+ *
+ * @return the list of {@link Location} objects for each node satisfying the
results; never null
+ */
+ List<Location> getResults();
+ }
+
+ /**
+ * A query activity.
+ */
+ protected interface Query extends ReadOnlyActivity {
+ /**
+ * Get the results of the query.
+ *
+ * @return the results of a query; never null
+ */
+ QueryResults getResults();
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchEngine.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Copied:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchEngineException.java (from
rev 1328, trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngineException.java)
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchEngineException.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchEngineException.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -0,0 +1,67 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.graph.search;
+
+/**
+ * An exception that represents a problem within a search engine.
+ */
+public class SearchEngineException extends RuntimeException {
+
+ /**
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ */
+ public SearchEngineException() {
+ }
+
+ /**
+ * @param message
+ */
+ public SearchEngineException( String message ) {
+ super(message);
+
+ }
+
+ /**
+ * @param cause
+ */
+ public SearchEngineException( Throwable cause ) {
+ super(cause);
+
+ }
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public SearchEngineException( String message,
+ Throwable cause ) {
+ super(message, cause);
+
+ }
+
+}
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchException.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchException.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchException.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -0,0 +1,97 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.graph.search;
+
+/**
+ * An exception signalling an error during a search.
+ */
+public class SearchException extends RuntimeException {
+
+ /**
+ */
+ private static final long serialVersionUID = 1L;
+
+ private final String expression;
+
+ /**
+ * Create an exception with the search expression.
+ *
+ * @param expression the search expression
+ */
+ public SearchException( String expression ) {
+ super();
+ this.expression = expression;
+ }
+
+ /**
+ * Create an exception with the search expression and a message.
+ *
+ * @param expression the search expression
+ * @param message the exception message
+ */
+ public SearchException( String expression,
+ String message ) {
+ super(message);
+ assert expression != null;
+ this.expression = expression;
+ }
+
+ /**
+ * Construct a system failure exception with another exception that is the cause of
the failure.
+ *
+ * @param expression the search expression
+ * @param cause the original cause of the failure
+ */
+ public SearchException( String expression,
+ Throwable cause ) {
+ super(cause);
+ assert expression != null;
+ this.expression = expression;
+ }
+
+ /**
+ * Construct a system failure exception with a single message and another exception
that is the cause of the failure.
+ *
+ * @param expression the search expression
+ * @param message the message describing the failure
+ * @param cause the original cause of the failure
+ */
+ public SearchException( String expression,
+ String message,
+ Throwable cause ) {
+ super(message, cause);
+ assert expression != null;
+ this.expression = expression;
+ }
+
+ /**
+ * Get the search expression.
+ *
+ * @return the search expression; never null
+ */
+ public String getSearchExpression() {
+ return expression;
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchException.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchProvider.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchProvider.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchProvider.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -0,0 +1,178 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.graph.search;
+
+import java.io.IOException;
+import java.util.List;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.Node;
+import org.jboss.dna.graph.connector.RepositorySource;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.jboss.dna.graph.request.ChangeRequest;
+
+/**
+ * The representation of a single layout of one or more Lucene indexes.
+ */
+@ThreadSafe
+public interface SearchProvider {
+
+ /**
+ * Create a new session to the indexes.
+ *
+ * @param context the execution context for which this session is to be established;
may not be null
+ * @param sourceName the name of the source; may not be null
+ * @param workspaceName the name of the workspace; may not be null
+ * @param overwrite true if the existing indexes should be overwritten, or false if
they should be used
+ * @param readOnly true if the resulting session can be optimized for use in
read-only situations, or false if the session
+ * needs to allow calling the write methods
+ * @return the session to the indexes; never null
+ */
+ Session createSession( ExecutionContext context,
+ String sourceName,
+ String workspaceName,
+ boolean overwrite,
+ boolean readOnly );
+
+ /**
+ * Destroy the indexes for the workspace with the supplied name.
+ *
+ * @param context the execution context in which the destruction should be performed;
may not be null
+ * @param sourceName the name of the source; may not be null
+ * @param workspaceName the name of the workspace; may not be null
+ * @return true if the indexes for the workspace were destroyed, or false if there
was no such workspace index
+ * @throws IOException if there is a problem destroying the indexes
+ */
+ boolean destroyIndexes( ExecutionContext context,
+ String sourceName,
+ String workspaceName ) throws IOException;
+
+ /**
+ * A stateful session that is used to interact with the search provider to search a
particular source and workspace.
+ */
+ public interface Session {
+
+ /**
+ * Get the name of the {@link RepositorySource repository source} for which this
session exists. A session instance will
+ * always return the same name.
+ *
+ * @return the source name; never null
+ */
+ String getSourceName();
+
+ /**
+ * Get the name of the workspace for which this session exists. A session
instance will always return the same name.
+ *
+ * @return the workspace name; never null
+ */
+ String getWorkspaceName();
+
+ /**
+ * Get the execution context in which this session is operating.
+ *
+ * @return the execution context; never null
+ */
+ ExecutionContext getContext();
+
+ /**
+ * Return whether this session made changes to the indexed state.
+ *
+ * @return true if change were made, or false otherwise
+ */
+ boolean hasChanges();
+
+ /**
+ * Perform a full-text search given the supplied query.
+ *
+ * @param context the context in which the search should be executed; may not be
null
+ * @param fullTextString the full-text query; never null or blank
+ * @param maxResults the maximum number of results that are to be returned;
always positive
+ * @param offset the number of initial results to skip, or 0 if the first results
are to be returned
+ * @param results the list where the results should be accumulated; never null
+ */
+ void search( ExecutionContext context,
+ String fullTextString,
+ int maxResults,
+ int offset,
+ List<Location> results );
+
+ /**
+ * Perform a query of the content. The {@link QueryCommand query} is supplied in
the form of the Abstract Query Model,
+ * with the {@link Schemata} that defines the tables and views that are available
to the query, and the set of index
+ * readers (and writers) that should be used.
+ *
+ * @param queryContext the context in which the query should be executed; may not
be null
+ * @param query the query; never null
+ * @return the results of the query; never null
+ */
+ QueryResults query( QueryContext queryContext,
+ QueryCommand query );
+
+ /**
+ * Index the node. Changes are recorded only when {@link #commit()} is called.
+ *
+ * @param node the node to be indexed; never null
+ */
+ void index( Node node );
+
+ /**
+ * Update the indexes to reflect the supplied changes to the graph content.
+ *
+ * @param changes the set of changes to the content
+ * @return the (approximate) number of nodes that were affected by the changes
+ * @throws SearchEngineException if there is a problem executing the query
+ */
+ int apply( Iterable<ChangeRequest> changes );
+
+ /**
+ * Remove from the index(es) all of the information pertaining to the nodes at or
below the supplied path.
+ *
+ * @param path the path identifying the graph content that is to be removed;
never null
+ * @return the (approximate) number of nodes that were affected by the changes
+ */
+ int deleteBelow( Path path );
+
+ /**
+ * Optimize the indexes, if required.
+ */
+ void optimize();
+
+ /**
+ * Close this session by committing all of the changes. This session is no longer
usable after this method is called.
+ */
+ void commit();
+
+ /**
+ * Close this session by rolling back all of the changes that have been made.
This session is no longer usable after this
+ * method is called.
+ */
+ void rollback();
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/search/SearchProvider.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties
===================================================================
--- trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties 2009-11-18
19:37:57 UTC (rev 1328)
+++ trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties 2009-11-18
19:39:05 UTC (rev 1329)
@@ -147,3 +147,13 @@
expectingLiteralAndUnableToParseAsLong = Expecting literal and unable to parse
'{0}' at line {1}, column {2} as a long
expectingLiteralAndUnableToParseAsDouble = Expecting literal and unable to parse
'{0}' at line {1}, column {2} as a double
expectingLiteralAndUnableToParseAsDate = Expecting literal and unable to parse
'{0}' at line {1}, column {2} as a date
+
+# Search
+errorWhileIndexingContentAtPath = Error while indexing the content at "{0}" in
the "{1}" workspace of the "{2}" source: {3}
+errorWhileRemovingContentAtPath = Error while removing the content at/below
"{0}" in the "{1}" workspace of the "{2}" source: {3}
+errorWhileUpdatingContent = Error while updating content in the "{0}" workspace
of the "{1}" source: {2}
+errorWhileCommittingIndexChanges = Error while committing changes to the indexes for the
"{0}" workspace of the "{1}" source: {2}
+errorWhileOptimizingIndexes = Error while optimizing the indexes for the "{0}"
workspace of the "{1}" source: {2}
+errorWhilePerformingSearch = Error while searching for "{0}" in the
"{1}" workspace of the "{2}" source: {3}
+errorWhilePerformingQuery = Error while performing the query "{0}" against the
content in the "{1}" workspace of the "{2}" source: {3}
+errorWhileRemovingIndexesForWorkspace = Error while removing the indexes for the
"{0}" workspace of the "{1}" source: {2}
Copied: trunk/dna-graph/src/test/java/org/jboss/dna/graph/search/SearchEngineTest.java
(from rev 1328,
trunk/dna-search/src/test/java/org/jboss/dna/search/SearchEngineTest.java)
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/search/SearchEngineTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/search/SearchEngineTest.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -0,0 +1,254 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.graph.search;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.stub;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import java.util.List;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Graph;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.Node;
+import org.jboss.dna.graph.connector.RepositoryConnection;
+import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
+import org.jboss.dna.graph.connector.RepositorySourceException;
+import org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.Query;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.jboss.dna.graph.request.InvalidWorkspaceException;
+import org.jboss.dna.graph.search.SearchProvider.Session;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SearchEngineTest {
+
+ private SearchEngine engine;
+ private ExecutionContext context;
+ private String sourceName;
+ private String workspaceName1;
+ private String workspaceName2;
+ private InMemoryRepositorySource source;
+ private RepositoryConnectionFactory connectionFactory;
+ private SearchProvider provider;
+ private SearchProvider.Session sessionWs1;
+ private SearchProvider.Session sessionWs2;
+ private SearchProvider.Session sessionDefault;
+ private Graph content;
+
+ @Before
+ public void beforeEach() throws Exception {
+ context = new ExecutionContext();
+ sourceName = "sourceA";
+ workspaceName1 = "workspace1";
+ workspaceName2 = "workspace2";
+
+ // Set up the source and graph instance ...
+ source = new InMemoryRepositorySource();
+ source.setName(sourceName);
+ content = Graph.create(source, context);
+
+ // Create the workspaces ...
+ content.createWorkspace().named(workspaceName1);
+ content.createWorkspace().named(workspaceName2);
+
+ // Set up the connection factory ...
+ connectionFactory = new RepositoryConnectionFactory() {
+ @SuppressWarnings( "synthetic-access" )
+ public RepositoryConnection createConnection( String sourceName ) throws
RepositorySourceException {
+ return source.getConnection();
+ }
+ };
+
+ // Set up the index layout ...
+ provider = mock(SearchProvider.class);
+ sessionWs1 = mockSession(provider, workspaceName1);
+ sessionWs2 = mockSession(provider, workspaceName2);
+ sessionDefault = mockSession(provider, "");
+
+ // Now set up the search engine ...
+ engine = new SearchEngine(context, sourceName, connectionFactory, provider);
+ }
+
+ protected Session mockSession( SearchProvider mockProvider,
+ String workspaceName ) {
+ Session session = mock(Session.class);
+ stub(mockProvider.createSession(context, sourceName, workspaceName, false,
false)).toReturn(session);
+ stub(mockProvider.createSession(context, sourceName, workspaceName, false,
true)).toReturn(session);
+ stub(mockProvider.createSession(context, sourceName, workspaceName, true,
false)).toReturn(session);
+ stub(mockProvider.createSession(context, sourceName, workspaceName, true,
true)).toReturn(session);
+ stub(session.getWorkspaceName()).toReturn(workspaceName);
+ stub(session.getSourceName()).toReturn(sourceName);
+ return session;
+ }
+
+ protected Path path( String path ) {
+ return context.getValueFactories().getPathFactory().create(path);
+ }
+
+ protected void loadContent() throws Exception {
+ // Load some content ...
+ content.useWorkspace(workspaceName1);
+
content.importXmlFrom(getClass().getClassLoader().getResourceAsStream("cars.xml")).into("/");
+ content.useWorkspace(workspaceName2);
+
content.importXmlFrom(getClass().getClassLoader().getResourceAsStream("aircraft.xml")).into("/");
+ }
+
+ @Test
+ public void shouldReturnSearchWorkspaceForExistingWorkspaceInSource() {
+ SearchEngine.Workspace workspace = engine.getWorkspace(workspaceName1);
+ assertThat(workspace, is(notNullValue()));
+ assertThat(workspace.modifiedNodesSinceLastOptimize.get(), is(0));
+ assertThat(workspace.getWorkspaceName(), is(workspaceName1));
+ }
+
+ @Test( expected = InvalidWorkspaceException.class )
+ public void shouldFailToReturnSearchWorkspaceForNonExistantWorkspaceInSource() {
+ engine.getWorkspace(workspaceName1 + "foobar");
+ }
+
+ @Test
+ public void shouldDoNothingDuringRemoveWorkspaceIfWorkspaceHasNotBeenLoaded() throws
Exception {
+ engine.removeWorkspace(workspaceName1);
+ verifyZeroInteractions(provider);
+ }
+
+ @Test
+ public void shouldForwardRemoveWorkspaceToIndexLayout() throws Exception {
+ engine.getWorkspace(workspaceName1);
+ engine.removeWorkspace(workspaceName1);
+ verify(provider).destroyIndexes(context, sourceName, workspaceName1);
+ verifyNoMoreInteractions(provider);
+ }
+
+ @Test
+ public void shouldForwardRemoveWorkspaceToIndexLayoutForEachWorkspaceThatWasLoaded()
throws Exception {
+ engine.getWorkspace(workspaceName1);
+ engine.removeWorkspaces();
+ verify(provider).destroyIndexes(context, sourceName, workspaceName1);
+ verifyZeroInteractions(provider);
+ }
+
+ @Test
+ public void shouldForwardRemoveWorkspaceToIndexLayoutForAllWorkspacesThatWereLoaded()
throws Exception {
+ engine.getWorkspace(workspaceName1);
+ engine.getWorkspace(workspaceName2);
+ engine.removeWorkspaces();
+ verify(provider).destroyIndexes(context, sourceName, workspaceName1);
+ verify(provider).destroyIndexes(context, sourceName, workspaceName2);
+ verifyNoMoreInteractions(provider);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldFailIfNullWorkspaceNamePassedToRemoveWorkspace() throws Exception
{
+ engine.removeWorkspace(null);
+ }
+
+ @Test
+ public void shouldForwardOptimizeOfWorkspaceToIndexSession() throws Exception {
+ engine.optimize(workspaceName1);
+ verify(sessionWs1).optimize();
+ verify(sessionWs1).commit();
+ verifyNoMoreInteractions(sessionWs1);
+ }
+
+ @Test
+ public void shouldForwardOptimizeOfAllWorkspacesToEachIndexSession() throws Exception
{
+ engine.optimize(); // will find all three workspaces
+ verify(sessionWs1).optimize();
+ verify(sessionWs1).commit();
+ verifyNoMoreInteractions(sessionWs1);
+ verify(sessionWs2).optimize();
+ verify(sessionWs2).commit();
+ verifyNoMoreInteractions(sessionWs2);
+ verify(sessionDefault).optimize();
+ verify(sessionDefault).commit();
+ verifyNoMoreInteractions(sessionDefault);
+ }
+
+ @Test
+ public void shouldForwardIndexOfWorkspaceToIndexSession() throws Exception {
+ loadContent();
+ engine.index(workspaceName1, 3);
+ verify(sessionWs1, times(18)).index((Node)anyObject());
+ verify(sessionWs1).commit();
+ }
+
+ @Test
+ public void shouldForwardIndexOfSubgraphInWorkspaceToIndexSession() throws Exception
{
+ loadContent();
+ engine.index(workspaceName1, path("/Cars"), 3);
+ verify(sessionWs1).deleteBelow(path("/Cars"));
+ verify(sessionWs1, times(17)).index((Node)anyObject());
+ verify(sessionWs1).commit();
+ }
+
+ @Test
+ public void shouldForwardIndexEntireWorkspaceToIndexSession() throws Exception {
+ loadContent();
+ engine.index(workspaceName1, path("/"), 3);
+ verify(sessionWs1, times(18)).index((Node)anyObject());
+ verify(sessionWs1).commit();
+ }
+
+ @Test
+ public void shouldForwardIndexOfAllWorkspacesToEachIndexSession() throws Exception {
+ loadContent();
+ engine.index(3); // will find all three workspaces
+ verify(sessionWs1, times(18)).index((Node)anyObject());
+ verify(sessionWs1).commit();
+ verify(sessionWs2, times(24)).index((Node)anyObject());
+ verify(sessionWs2).commit();
+ verify(sessionDefault, times(1)).index((Node)anyObject());
+ verify(sessionDefault).commit();
+ }
+
+ @SuppressWarnings( "unchecked" )
+ @Test
+ public void shouldForwardSearchToIndexSession() throws Exception {
+ String query = "term1 term2";
+ engine.fullTextSearch(context, workspaceName1, query, 3, 0);
+ verify(sessionWs1).search(eq(context), eq(query), eq(3), eq(0),
(List<Location>)anyObject());
+ verify(sessionWs1).commit();
+ }
+
+ @Test
+ public void shouldForwardQueryToIndexSession() throws Exception {
+ Query query = mock(Query.class);
+ Schemata schemata = mock(Schemata.class);
+ engine.query(context, workspaceName1, query, schemata);
+ verify(sessionWs1).query(eq(new QueryContext(context, schemata)), eq(query));
+ verify(sessionWs1).commit();
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/search/SearchEngineTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Deleted: trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfiguration.java
===================================================================
---
trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfiguration.java 2009-11-18
19:37:57 UTC (rev 1328)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfiguration.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -1,59 +0,0 @@
-/*
- * JBoss DNA (
http://www.jboss.org/dna)
- * See the COPYRIGHT.txt file distributed with this work for information
- * regarding copyright ownership. Some portions may be licensed
- * to Red Hat, Inc. under one or more contributor license agreements.
- * See the AUTHORS.txt file in the distribution for a full listing of
- * individual contributors.
- *
- * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
- * is licensed to you under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * JBoss DNA is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.search;
-
-import net.jcip.annotations.ThreadSafe;
-import org.apache.lucene.store.Directory;
-
-/**
- * Interface used to obtain the Lucene {@link Directory} instance that should be used for
a workspace given the name of the
- * workspace. There are several implementations (see {@link DirectoryConfigurations}),
but custom implementations can always be
- * used.
- */
-@ThreadSafe
-public interface DirectoryConfiguration {
- /**
- * Get the {@link Directory} that should be used for the workspace with the supplied
name.
- *
- * @param workspaceName the workspace name
- * @param indexName the name of the index to be created
- * @return the directory; never null
- * @throws IllegalArgumentException if the workspace name is null
- * @throws SearchEngineException if there is a problem creating the directory
- */
- Directory getDirectory( String workspaceName,
- String indexName ) throws SearchEngineException;
-
- /**
- * Destroy the {@link Directory} that is used for the workspace with the supplied
name.
- *
- * @param workspaceName the workspace name
- * @param indexName the name of the index to be created
- * @return true if the directory existed and was destroyed, or false if the directory
didn't exist
- * @throws IllegalArgumentException if the workspace name is null
- * @throws SearchEngineException if there is a problem creating the directory
- */
- boolean destroyDirectory( String workspaceName,
- String indexName ) throws SearchEngineException;
-}
Deleted: trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfigurations.java
===================================================================
---
trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfigurations.java 2009-11-18
19:37:57 UTC (rev 1328)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfigurations.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -1,426 +0,0 @@
-/*
- * JBoss DNA (
http://www.jboss.org/dna)
- * See the COPYRIGHT.txt file distributed with this work for information
- * regarding copyright ownership. Some portions may be licensed
- * to Red Hat, Inc. under one or more contributor license agreements.
- * See the AUTHORS.txt file in the distribution for a full listing of
- * individual contributors.
- *
- * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
- * is licensed to you under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * JBoss DNA is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.search;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.concurrent.ConcurrentHashMap;
-import net.jcip.annotations.Immutable;
-import net.jcip.annotations.ThreadSafe;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.store.FSDirectory;
-import org.apache.lucene.store.LockFactory;
-import org.apache.lucene.store.RAMDirectory;
-import org.jboss.dna.common.i18n.I18n;
-import org.jboss.dna.common.text.NoOpEncoder;
-import org.jboss.dna.common.text.TextEncoder;
-import org.jboss.dna.common.util.CheckArg;
-import org.jboss.dna.common.util.FileUtil;
-import org.jboss.dna.common.util.HashCode;
-
-/**
- * A family of {@link DirectoryConfiguration} implementations.
- */
-public class DirectoryConfigurations {
-
- /**
- * Return a new {@link DirectoryConfiguration} that creates in-memory directories.
- *
- * @return the new directory configuration; never null
- */
- public static final DirectoryConfiguration inMemory() {
- return new RamDirectoryFactory();
- }
-
- /**
- * Return a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
- * folder, where the workspace name is used to create the workspace folder. Note that
this has ramifications on the allowable
- * workspace names.
- *
- * @param parent the parent folder
- * @return the new directory configuration; never null
- * @throws IllegalArgumentException if the parent file is null
- */
- public static final DirectoryConfiguration using( File parent ) {
- return new FileSystemDirectoryFromNameFactory(parent);
- }
-
- /**
- * Return a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
- * folder, where the workspace name is used to create the workspace folder. Note that
this has ramifications on the allowable
- * workspace names.
- *
- * @param parent the parent folder
- * @param lockFactory the lock factory; may be null
- * @return the new directory configuration; never null
- * @throws IllegalArgumentException if the parent file is null
- */
- public static final DirectoryConfiguration using( File parent,
- LockFactory lockFactory ) {
- return new FileSystemDirectoryFromNameFactory(parent, lockFactory);
- }
-
- /**
- * Return a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
- * folder, where the workspace name is used to create the workspace folder. Note that
this has ramifications on the allowable
- * workspace names.
- *
- * @param parent the parent folder
- * @param workspaceNameEncoder the encoder that should be used for encoding the
workspace name into a directory name
- * @param indexNameEncoder the encoder that should be used for encoding the index
name into a directory name
- * @return the new directory configuration; never null
- * @throws IllegalArgumentException if the parent file is null
- */
- public static final DirectoryConfiguration using( File parent,
- TextEncoder workspaceNameEncoder,
- TextEncoder indexNameEncoder ) {
- return new FileSystemDirectoryFromNameFactory(parent, workspaceNameEncoder,
indexNameEncoder);
- }
-
- /**
- * Return a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
- * folder, where the workspace name is used to create the workspace folder. Note that
this has ramifications on the allowable
- * workspace names.
- *
- * @param parent the parent folder
- * @param lockFactory the lock factory; may be null
- * @param workspaceNameEncoder the encoder that should be used for encoding the
workspace name into a directory name
- * @param indexNameEncoder the encoder that should be used for encoding the index
name into a directory name
- * @return the new directory configuration; never null
- * @throws IllegalArgumentException if the parent file is null
- */
- public static final DirectoryConfiguration using( File parent,
- LockFactory lockFactory,
- TextEncoder workspaceNameEncoder,
- TextEncoder indexNameEncoder ) {
- return new FileSystemDirectoryFromNameFactory(parent, lockFactory,
workspaceNameEncoder, indexNameEncoder);
- }
-
- /**
- * A {@link DirectoryConfiguration} implementation that creates {@link Directory}
instances of the supplied type for each
- * workspace and pools the results, ensuring that the same {@link Directory} instance
is always returned for the same
- * workspace name.
- *
- * @param <DirectoryType> the concrete type of the directory
- */
- @ThreadSafe
- protected static abstract class PoolingDirectoryFactory<DirectoryType extends
Directory> implements DirectoryConfiguration {
- private final ConcurrentHashMap<IndexId, DirectoryType> directories = new
ConcurrentHashMap<IndexId, DirectoryType>();
-
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.search.DirectoryConfiguration#getDirectory(java.lang.String,
java.lang.String)
- */
- public Directory getDirectory( String workspaceName,
- String indexName ) throws SearchEngineException {
- CheckArg.isNotNull(workspaceName, "workspaceName");
- IndexId id = new IndexId(workspaceName, indexName);
- DirectoryType result = directories.get(id);
- if (result == null) {
- DirectoryType newDirectory = createDirectory(workspaceName, indexName);
- result = directories.putIfAbsent(id, newDirectory);
- if (result == null) result = newDirectory;
- }
- return result;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.search.DirectoryConfiguration#destroyDirectory(java.lang.String,
java.lang.String)
- */
- public boolean destroyDirectory( String workspaceName,
- String indexName ) throws SearchEngineException
{
- CheckArg.isNotNull(workspaceName, "workspaceName");
- IndexId id = new IndexId(workspaceName, indexName);
- DirectoryType result = directories.remove(id);
- return result != null ? doDestroy(result) : false;
- }
-
- /**
- * Method implemented by subclasses to create a new Directory implementation.
- *
- * @param workspaceName the name of the workspace for which the {@link Directory}
is to be created; never null
- * @param indexName the name of the index to be created
- * @return the new directory; may not be null
- * @throws SearchEngineException if there is a problem creating the directory
- */
- protected abstract DirectoryType createDirectory( String workspaceName,
- String indexName ) throws
SearchEngineException;
-
- protected abstract boolean doDestroy( DirectoryType directory ) throws
SearchEngineException;
- }
-
- /**
- * A {@link DirectoryConfiguration} implementation that creates {@link RAMDirectory}
instances for each workspace and index
- * name. Each factory instance maintains a pool of {@link RAMDirectory} instances,
ensuring that the same {@link RAMDirectory}
- * is always returned for the same workspace name.
- */
- @ThreadSafe
- public static class RamDirectoryFactory extends
PoolingDirectoryFactory<RAMDirectory> {
- protected RamDirectoryFactory() {
- }
-
- @Override
- protected RAMDirectory createDirectory( String workspaceName,
- String indexName ) {
- return new RAMDirectory();
- }
-
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.search.DirectoryConfigurations.PoolingDirectoryFactory#doDestroy(org.apache.lucene.store.Directory)
- */
- @Override
- protected boolean doDestroy( RAMDirectory directory ) throws
SearchEngineException {
- return directory != null;
- }
- }
-
- /**
- * A {@link DirectoryConfiguration} implementation that creates {@link FSDirectory}
instances for each workspace and index
- * name. This factory is created with a parent directory under which all workspace
and index directories are created.
- * <p>
- * This uses the supplied encoders to translate the workspace and index names into
valid directory names. By default, no
- * encoding is performed, meaning that the workspace and index names are used
explicitly as directory names. This default
- * behavior, then, means that not all values of workspace names or index names will
work. If you want to be sure that all
- * workspace names work, supply an encoder for the workspace names. (Index names are
currently such that they will always be
- * valid directory names, but you can always supply an encoder if you'd like.)
- * </p>
- */
- public static class FileSystemDirectoryFromNameFactory extends
PoolingDirectoryFactory<FSDirectory> {
- private final File parentFile;
- private final LockFactory lockFactory;
- private final TextEncoder workspaceNameEncoder;
- private final TextEncoder indexNameEncoder;
-
- /**
- * Create a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
- * folder, where the workspace name is used to create the workspace folder. Note
that this has ramifications on the
- * allowable workspace names.
- *
- * @param parent the parent folder
- * @throws IllegalArgumentException if the parent file is null
- */
- protected FileSystemDirectoryFromNameFactory( File parent ) {
- this(parent, null, null, null);
- }
-
- /**
- * Create a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
- * folder, where the workspace name is used to create the workspace folder. Note
that this has ramifications on the
- * allowable workspace names.
- *
- * @param parent the parent folder
- * @param lockFactory the lock factory; may be null
- * @throws IllegalArgumentException if the parent file is null
- */
- protected FileSystemDirectoryFromNameFactory( File parent,
- LockFactory lockFactory ) {
- this(parent, lockFactory, null, null);
- }
-
- /**
- * Create a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
- * folder, where the workspace name is used to create the workspace folder. Note
that this has ramifications on the
- * allowable workspace names.
- *
- * @param parent the parent folder
- * @param workspaceNameEncoder the encoder that should be used for encoding the
workspace name into a directory name
- * @param indexNameEncoder the encoder that should be used for encoding the index
name into a directory name
- * @throws IllegalArgumentException if the parent file is null
- */
- protected FileSystemDirectoryFromNameFactory( File parent,
- TextEncoder workspaceNameEncoder,
- TextEncoder indexNameEncoder ) {
- this(parent, null, workspaceNameEncoder, indexNameEncoder);
- }
-
- /**
- * Create a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
- * folder, where the workspace name is used to create the workspace folder. Note
that this has ramifications on the
- * allowable workspace names.
- *
- * @param parent the parent folder
- * @param lockFactory the lock factory; may be null
- * @param workspaceNameEncoder the encoder that should be used for encoding the
workspace name into a directory name
- * @param indexNameEncoder the encoder that should be used for encoding the index
name into a directory name
- * @throws IllegalArgumentException if the parent file is null
- */
- protected FileSystemDirectoryFromNameFactory( File parent,
- LockFactory lockFactory,
- TextEncoder workspaceNameEncoder,
- TextEncoder indexNameEncoder ) {
- CheckArg.isNotNull(parent, "parent");
- this.parentFile = parent;
- this.lockFactory = lockFactory;
- this.workspaceNameEncoder = workspaceNameEncoder != null ?
workspaceNameEncoder : new NoOpEncoder();
- this.indexNameEncoder = indexNameEncoder != null ? indexNameEncoder : new
NoOpEncoder();
- }
-
- @Override
- protected FSDirectory createDirectory( String workspaceName,
- String indexName ) {
- File workspaceFile = new File(parentFile,
workspaceNameEncoder.encode(workspaceName));
- if (!workspaceFile.exists()) {
- workspaceFile.mkdirs();
- } else {
- if (!workspaceFile.isDirectory()) {
- I18n msg = SearchI18n.locationForIndexesIsNotDirectory;
- throw new
SearchEngineException(msg.text(workspaceFile.getAbsolutePath(), workspaceName));
- }
- if (!workspaceFile.canRead()) {
- I18n msg = SearchI18n.locationForIndexesCannotBeRead;
- throw new
SearchEngineException(msg.text(workspaceFile.getAbsolutePath(), workspaceName));
- }
- if (!workspaceFile.canWrite()) {
- I18n msg = SearchI18n.locationForIndexesCannotBeWritten;
- throw new
SearchEngineException(msg.text(workspaceFile.getAbsolutePath(), workspaceName));
- }
- }
- File directory = workspaceFile;
- if (indexName != null) {
- File indexFile = new File(workspaceFile,
indexNameEncoder.encode(indexName));
- if (!indexFile.exists()) {
- indexFile.mkdirs();
- } else {
- if (!indexFile.isDirectory()) {
- I18n msg = SearchI18n.locationForIndexesIsNotDirectory;
- throw new
SearchEngineException(msg.text(indexFile.getAbsolutePath(), workspaceName));
- }
- if (!indexFile.canRead()) {
- I18n msg = SearchI18n.locationForIndexesCannotBeRead;
- throw new
SearchEngineException(msg.text(indexFile.getAbsolutePath(), workspaceName));
- }
- if (!indexFile.canWrite()) {
- I18n msg = SearchI18n.locationForIndexesCannotBeWritten;
- throw new
SearchEngineException(msg.text(indexFile.getAbsolutePath(), workspaceName));
- }
- }
- directory = indexFile;
- }
- try {
- return create(directory, lockFactory);
- } catch (IOException e) {
- throw new SearchEngineException(e);
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.search.DirectoryConfigurations.PoolingDirectoryFactory#doDestroy(org.apache.lucene.store.Directory)
- */
- @Override
- protected boolean doDestroy( FSDirectory directory ) throws SearchEngineException
{
- File file = directory.getFile();
- if (file.exists()) {
- return FileUtil.delete(file);
- }
- return false;
- }
-
- /**
- * Override this method to define which subclass of {@link FSDirectory} should be
created.
- *
- * @param directory the file system directory; never null
- * @param lockFactory the lock factory; may be null
- * @return the {@link FSDirectory} instance
- * @throws IOException if there is a problem creating the FSDirectory instance
- */
- protected FSDirectory create( File directory,
- LockFactory lockFactory ) throws IOException {
- return FSDirectory.open(directory, lockFactory);
- }
- }
-
- @Immutable
- protected static final class IndexId {
- private final String workspaceName;
- private final String indexName;
- private final int hc;
-
- protected IndexId( String workspaceName,
- String indexName ) {
- assert workspaceName != null;
- this.workspaceName = workspaceName;
- this.indexName = indexName;
- this.hc = HashCode.compute(this.workspaceName, this.indexName);
- }
-
- /**
- * @return indexName
- */
- public String getIndexName() {
- return indexName;
- }
-
- /**
- * @return workspaceName
- */
- public String getWorkspaceName() {
- return workspaceName;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see java.lang.Object#hashCode()
- */
- @Override
- public int hashCode() {
- return hc;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see java.lang.Object#equals(java.lang.Object)
- */
- @Override
- public boolean equals( Object obj ) {
- if (obj == this) return true;
- if (obj instanceof IndexId) {
- IndexId that = (IndexId)obj;
- if (this.hashCode() != that.hashCode()) return false;
- if (!this.workspaceName.equals(that.workspaceName)) return false;
- if (!this.indexName.equals(that.indexName)) return false;
- return true;
- }
- return false;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see java.lang.Object#toString()
- */
- @Override
- public String toString() {
- return indexName != null ? workspaceName + "/" + this.indexName :
this.workspaceName;
- }
- }
-}
Deleted: trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexLayout.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexLayout.java 2009-11-18
19:37:57 UTC (rev 1328)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexLayout.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -1,1580 +0,0 @@
-/*
- * JBoss DNA (
http://www.jboss.org/dna)
- * See the COPYRIGHT.txt file distributed with this work for information
- * regarding copyright ownership. Some portions may be licensed
- * to Red Hat, Inc. under one or more contributor license agreements.
- * See the AUTHORS.txt file in the distribution for a full listing of
- * individual contributors.
- *
- * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
- * is licensed to you under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * JBoss DNA is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.search;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-import java.util.UUID;
-import net.jcip.annotations.ThreadSafe;
-import org.apache.lucene.analysis.Analyzer;
-import org.apache.lucene.analysis.standard.StandardAnalyzer;
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.Field;
-import org.apache.lucene.document.FieldSelector;
-import org.apache.lucene.document.FieldSelectorResult;
-import org.apache.lucene.document.NumericField;
-import org.apache.lucene.document.Field.Index;
-import org.apache.lucene.document.Field.Store;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.index.Term;
-import org.apache.lucene.index.IndexWriter.MaxFieldLength;
-import org.apache.lucene.queryParser.ParseException;
-import org.apache.lucene.queryParser.QueryParser;
-import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.Collector;
-import org.apache.lucene.search.FieldCache;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.MatchAllDocsQuery;
-import org.apache.lucene.search.NumericRangeQuery;
-import org.apache.lucene.search.PrefixQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.ScoreDoc;
-import org.apache.lucene.search.Scorer;
-import org.apache.lucene.search.TermQuery;
-import org.apache.lucene.search.TopDocs;
-import org.apache.lucene.search.WildcardQuery;
-import org.apache.lucene.search.BooleanClause.Occur;
-import org.apache.lucene.search.regex.JavaUtilRegexCapabilities;
-import org.apache.lucene.search.regex.RegexQuery;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.util.Version;
-import org.jboss.dna.common.i18n.I18n;
-import org.jboss.dna.common.text.NoOpEncoder;
-import org.jboss.dna.common.text.TextEncoder;
-import org.jboss.dna.common.util.Logger;
-import org.jboss.dna.graph.ExecutionContext;
-import org.jboss.dna.graph.Location;
-import org.jboss.dna.graph.Node;
-import org.jboss.dna.graph.property.Binary;
-import org.jboss.dna.graph.property.DateTime;
-import org.jboss.dna.graph.property.DateTimeFactory;
-import org.jboss.dna.graph.property.Name;
-import org.jboss.dna.graph.property.Path;
-import org.jboss.dna.graph.property.PathFactory;
-import org.jboss.dna.graph.property.Property;
-import org.jboss.dna.graph.property.PropertyType;
-import org.jboss.dna.graph.property.ValueFactories;
-import org.jboss.dna.graph.property.ValueFactory;
-import org.jboss.dna.graph.query.QueryContext;
-import org.jboss.dna.graph.query.QueryEngine;
-import org.jboss.dna.graph.query.QueryResults;
-import org.jboss.dna.graph.query.QueryResults.Columns;
-import org.jboss.dna.graph.query.model.Length;
-import org.jboss.dna.graph.query.model.NodeDepth;
-import org.jboss.dna.graph.query.model.NodeLocalName;
-import org.jboss.dna.graph.query.model.NodeName;
-import org.jboss.dna.graph.query.model.NodePath;
-import org.jboss.dna.graph.query.model.Operator;
-import org.jboss.dna.graph.query.model.PropertyValue;
-import org.jboss.dna.graph.query.model.QueryCommand;
-import org.jboss.dna.graph.query.model.Visitors;
-import org.jboss.dna.graph.query.optimize.Optimizer;
-import org.jboss.dna.graph.query.optimize.OptimizerRule;
-import org.jboss.dna.graph.query.optimize.RuleBasedOptimizer;
-import org.jboss.dna.graph.query.plan.CanonicalPlanner;
-import org.jboss.dna.graph.query.plan.PlanHints;
-import org.jboss.dna.graph.query.plan.PlanNode;
-import org.jboss.dna.graph.query.plan.Planner;
-import org.jboss.dna.graph.query.process.ProcessingComponent;
-import org.jboss.dna.graph.query.process.QueryProcessor;
-import org.jboss.dna.graph.request.ChangeRequest;
-import org.jboss.dna.search.IndexRules.Rule;
-import org.jboss.dna.search.query.CompareLengthQuery;
-import org.jboss.dna.search.query.CompareNameQuery;
-import org.jboss.dna.search.query.ComparePathQuery;
-import org.jboss.dna.search.query.CompareStringQuery;
-import org.jboss.dna.search.query.MatchNoneQuery;
-import org.jboss.dna.search.query.NotQuery;
-import org.jboss.dna.search.query.UuidsQuery;
-
-/**
- * A simple {@link IndexLayout} implementation that relies upon two separate indexes: one
for the node content and a second one
- * for paths and UUIDs.
- */
-@ThreadSafe
-public abstract class DualIndexLayout implements IndexLayout {
-
- protected static final long MIN_DATE = 0;
- protected static final long MAX_DATE = Long.MAX_VALUE;
- protected static final long MIN_LONG = Long.MIN_VALUE;
- protected static final long MAX_LONG = Long.MAX_VALUE;
- protected static final double MIN_DOUBLE = Double.MIN_VALUE;
- protected static final double MAX_DOUBLE = Double.MAX_VALUE;
- protected static final int MIN_DEPTH = 0;
- protected static final int MAX_DEPTH = 100;
-
- protected static final String PATHS_INDEX_NAME = "paths";
- protected static final String CONTENT_INDEX_NAME = "content";
-
- protected static final String UUID_FIELD = "uuid";
- protected static final String FULL_TEXT_SUFFIX = "/fs"; // the slash
character is not allowed in a property name unescaped
-
- static class PathIndex {
- public static final String PATH = "path";
- public static final String LOCAL_NAME = "name";
- public static final String SNS_INDEX = "sns";
- public static final String UUID = UUID_FIELD;
- public static final String DEPTH = "depth";
- }
-
- static class ContentIndex {
- public static final String UUID = UUID_FIELD;
- public static final String FULL_TEXT = "fts";
- }
-
- /**
- * The number of results that should be returned when performing queries while
deleting entire branches of content. The
- * current value is {@value} .
- */
- protected static final int SIZE_OF_DELETE_BATCHES = 1000;
-
- private ThreadLocal<DateFormat> dateFormatter = new
ThreadLocal<DateFormat>() {
- @Override
- protected DateFormat initialValue() {
- return new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
- }
- };
-
- /**
- * Obtain an immutable {@link FieldSelector} instance that accesses the UUID field.
- */
- protected static final FieldSelector UUID_FIELD_SELECTOR = new FieldSelector() {
- private static final long serialVersionUID = 1L;
-
- public FieldSelectorResult accept( String fieldName ) {
- return PathIndex.UUID.equals(fieldName) ? FieldSelectorResult.LOAD_AND_BREAK
: FieldSelectorResult.NO_LOAD;
- }
- };
-
- /**
- * Get the date formatter that can be reused safely within the current thread.
- *
- * @return the date formatter; never null
- */
- protected DateFormat dateFormatter() {
- return dateFormatter.get();
- }
-
- /**
- * Get the text encoder that should be used to encode namespaces in the search
index.
- *
- * @return the namespace text encoder; never null
- */
- protected TextEncoder getNamespaceEncoder() {
- return new NoOpEncoder();
- }
-
- /**
- * Create a Lucene {@link Analyzer} analyzer that should be used for indexing and
searching.
- *
- * @return the analyzer; never null
- */
- protected Analyzer createAnalyzer() {
- return new StandardAnalyzer(Version.LUCENE_CURRENT);
- }
-
- protected abstract class LuceneSession implements IndexSession {
- protected final ExecutionContext context;
- protected final String sourceName;
- protected final String workspaceName;
- protected final IndexRules rules;
- private final QueryEngine queryEngine;
- private final Analyzer analyzer;
- private final Directory pathsIndexDirectory;
- private final Directory contentIndexDirectory;
- protected final boolean overwrite;
- protected final boolean readOnly;
- protected final ValueFactory<String> stringFactory;
- protected final DateTimeFactory dateFactory;
- protected final PathFactory pathFactory;
- private int changeCount;
- private IndexReader pathsReader;
- private IndexWriter pathsWriter;
- private IndexSearcher pathsSearcher;
- private IndexReader contentReader;
- private IndexWriter contentWriter;
- private IndexSearcher contentSearcher;
-
- protected LuceneSession( ExecutionContext context,
- String sourceName,
- String workspaceName,
- IndexRules rules,
- Directory pathsIndexDirectory,
- Directory contentIndexDirectory,
- boolean overwrite,
- boolean readOnly ) {
- this.context = context;
- this.sourceName = sourceName;
- this.workspaceName = workspaceName;
- this.rules = rules;
- this.overwrite = overwrite;
- this.readOnly = readOnly;
- this.pathsIndexDirectory = pathsIndexDirectory;
- this.contentIndexDirectory = contentIndexDirectory;
- this.analyzer = createAnalyzer();
- this.stringFactory = context.getValueFactories().getStringFactory();
- this.dateFactory = context.getValueFactories().getDateFactory();
- this.pathFactory = context.getValueFactories().getPathFactory();
- assert this.context != null;
- assert this.sourceName != null;
- assert this.workspaceName != null;
- assert this.rules != null;
- assert this.analyzer != null;
- assert this.pathsIndexDirectory != null;
- assert this.contentIndexDirectory != null;
- assert this.stringFactory != null;
- assert this.dateFactory != null;
- // do this last ...
- this.queryEngine = createQueryProcessor();
- assert this.queryEngine != null;
- }
-
- /**
- * Create the field name that will be used to store the full-text searchable
property values.
- *
- * @param propertyName the name of the property; may not be null
- * @return the field name for the full-text searchable property values; never
null
- */
- protected String fullTextFieldName( String propertyName ) {
- return propertyName + FULL_TEXT_SUFFIX;
- }
-
- protected IndexReader getPathsReader() throws IOException {
- if (pathsReader == null) {
- pathsReader = IndexReader.open(pathsIndexDirectory, readOnly);
- }
- return pathsReader;
- }
-
- protected IndexReader getContentReader() throws IOException {
- if (contentReader == null) {
- contentReader = IndexReader.open(contentIndexDirectory, readOnly);
- }
- return contentReader;
- }
-
- protected IndexWriter getPathsWriter() throws IOException {
- assert !readOnly;
- if (pathsWriter == null) {
- pathsWriter = new IndexWriter(pathsIndexDirectory, analyzer, overwrite,
MaxFieldLength.UNLIMITED);
- }
- return pathsWriter;
- }
-
- protected IndexWriter getContentWriter() throws IOException {
- assert !readOnly;
- if (contentWriter == null) {
- contentWriter = new IndexWriter(contentIndexDirectory, analyzer,
overwrite, MaxFieldLength.UNLIMITED);
- }
- return contentWriter;
- }
-
- protected IndexSearcher getPathsSearcher() throws IOException {
- if (pathsSearcher == null) {
- pathsSearcher = new IndexSearcher(getPathsReader());
- }
- return pathsSearcher;
- }
-
- protected IndexSearcher getContentSearcher() throws IOException {
- if (contentSearcher == null) {
- contentSearcher = new IndexSearcher(getContentReader());
- }
- return contentSearcher;
- }
-
- protected boolean hasWriters() {
- return pathsWriter != null || contentWriter != null;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.jboss.dna.search.IndexSession#getContext()
- */
- public final ExecutionContext getContext() {
- return context;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.jboss.dna.search.IndexSession#getSourceName()
- */
- public final String getSourceName() {
- return sourceName;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.jboss.dna.search.IndexSession#getWorkspaceName()
- */
- public String getWorkspaceName() {
- return workspaceName;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.jboss.dna.search.IndexSession#hasChanges()
- */
- public boolean hasChanges() {
- return changeCount > 0;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.jboss.dna.search.IndexSession#index(org.jboss.dna.graph.Node)
- */
- public void index( Node node ) throws IOException {
- assert !readOnly;
- Location location = node.getLocation();
- UUID uuid = location.getUuid();
- if (uuid == null) uuid = UUID.randomUUID();
- Path path = location.getPath();
- String uuidStr = stringFactory.create(uuid);
- String pathStr = pathAsString(path, stringFactory);
- String nameStr = path.isRoot() ? "" :
stringFactory.create(path.getLastSegment().getName());
- int sns = path.isRoot() ? 1 : path.getLastSegment().getIndex();
-
- Logger logger = Logger.getLogger(getClass());
- if (logger.isTraceEnabled()) {
- logger.trace("indexing {0}", pathStr);
- }
-
- // Create a separate document for the path, which makes it easier to handle
moves since the path can
- // be changed without changing any other content fields ...
- Document doc = new Document();
- doc.add(new Field(PathIndex.PATH, pathStr, Field.Store.YES,
Field.Index.NOT_ANALYZED));
- doc.add(new Field(PathIndex.LOCAL_NAME, nameStr, Field.Store.YES,
Field.Index.ANALYZED));
- doc.add(new NumericField(PathIndex.LOCAL_NAME, Field.Store.YES,
true).setIntValue(sns));
- doc.add(new Field(PathIndex.UUID, uuidStr, Field.Store.YES,
Field.Index.NOT_ANALYZED));
- doc.add(new NumericField(PathIndex.DEPTH, Field.Store.YES,
true).setIntValue(path.size()));
- getPathsWriter().addDocument(doc);
-
- // Create the document for the content (properties) ...
- doc = new Document();
- doc.add(new Field(ContentIndex.UUID, uuidStr, Field.Store.YES,
Field.Index.NOT_ANALYZED));
- String stringValue = null;
- StringBuilder fullTextSearchValue = null;
- for (Property property : node.getProperties()) {
- Name name = property.getName();
- Rule rule = rules.getRule(name);
- if (rule.isSkipped()) continue;
- String nameString = stringFactory.create(name);
- if (rule.isDate()) {
- for (Object value : property) {
- if (value == null) continue;
- DateTime dateValue = dateFactory.create(value);
- // Add a separate field for each property value ...
- doc.add(new NumericField(nameString, rule.getStoreOption(),
true).setLongValue(dateValue.getMillisecondsInUtc()));
- // Dates are not added to the full-text search field (since this
wouldn't make sense)
- }
- continue;
- }
- for (Object value : property) {
- if (value == null) continue;
- if (value instanceof Binary) {
- // don't include binary values as individual fields but do
include them in the full-text search ...
- // TODO : add to full-text search ...
- continue;
- }
- stringValue = stringFactory.create(value);
- // Add a separate field for each property value ...
- doc.add(new Field(nameString, stringValue, rule.getStoreOption(),
rule.getIndexOption()));
-
- if (rule.isFullText()) {
- // Add this text to the full-text field ...
- if (fullTextSearchValue == null) {
- fullTextSearchValue = new StringBuilder();
- } else {
- fullTextSearchValue.append(' ');
- }
- fullTextSearchValue.append(stringValue);
-
- // Also create a full-text-searchable field ...
- String fullTextNameString = fullTextFieldName(nameString);
- doc.add(new Field(fullTextNameString, stringValue, Store.NO,
Index.ANALYZED));
- }
- }
- }
- // Add the full-text-search field ...
- if (fullTextSearchValue != null) {
- doc.add(new Field(ContentIndex.FULL_TEXT, fullTextSearchValue.toString(),
Field.Store.NO, Field.Index.ANALYZED));
- }
- getContentWriter().addDocument(doc);
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.jboss.dna.search.IndexSession#optimize()
- */
- public void optimize() throws IOException {
- getContentWriter().optimize();
- getPathsWriter().optimize();
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.jboss.dna.search.IndexSession#apply(java.lang.Iterable)
- */
- public int apply( Iterable<ChangeRequest> changes ) /*throws
IOException*/{
- for (ChangeRequest change : changes) {
- if (change != null) continue;
- }
- return 0;
- }
-
- /**
- * {@inheritDoc}
- * <p>
- * Because this strategy uses multiple indexes, and since there's no
correlation between the documents in those indexes,
- * we need to perform the delete in multiple steps. First, we need to perform a
query to find out which nodes exist below
- * a certain path. Then, we need to delete those nodes from the paths index.
Finally, we need to delete the corresponding
- * documents in the content index that represent those same nodes.
- * </p>
- * <p>
- * Since we don't know how many documents there will be, we perform these
steps in batches, where each batch limits the
- * number of results to a maximum number. We repeat batches as long as we find
more results. This approach has the
- * advantage that we'll never bring in a large number of results, and it
allows us to delete the documents from the
- * content node using a query.
- * </p>
- *
- * @see
org.jboss.dna.search.IndexSession#deleteBelow(org.jboss.dna.graph.property.Path)
- */
- public int deleteBelow( Path path ) throws IOException {
- assert !readOnly;
- // Perform a query using the reader to find those nodes at/below the path
...
- try {
- IndexReader pathReader = getPathsReader();
- IndexSearcher pathSearcher = new IndexSearcher(pathReader);
- String pathStr = stringFactory.create(path) + "/";
- PrefixQuery query = new PrefixQuery(new Term(PathIndex.PATH, pathStr));
- int numberDeleted = 0;
- while (true) {
- // Execute the query and get the results ...
- TopDocs results = pathSearcher.search(query,
SIZE_OF_DELETE_BATCHES);
- int numResultsInBatch = results.scoreDocs.length;
- // Walk the results, delete the doc, and add to the query that
we'll use against the content index ...
- IndexReader contentReader = getContentReader();
- for (ScoreDoc result : results.scoreDocs) {
- int docId = result.doc;
- // Find the UUID of the node ...
- Document doc = pathReader.document(docId, UUID_FIELD_SELECTOR);
- String uuid = doc.get(PathIndex.UUID);
- // Delete the document from the paths index ...
- pathReader.deleteDocument(docId);
- // Delete the corresponding document from the content index ...
- contentReader.deleteDocuments(new Term(ContentIndex.UUID,
uuid));
- }
- numberDeleted += numResultsInBatch;
- if (numResultsInBatch < SIZE_OF_DELETE_BATCHES) break;
- }
- return numberDeleted;
- } catch (FileNotFoundException e) {
- // There are no index files yet, so nothing to delete ...
- return 0;
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.search.IndexSession#search(org.jboss.dna.graph.ExecutionContext,
java.lang.String, int, int,
- * java.util.List)
- */
- public void search( ExecutionContext context,
- String fullTextString,
- int maxResults,
- int offset,
- List<Location> results ) throws IOException,
ParseException {
- assert fullTextString != null;
- assert fullTextString.length() > 0;
- assert offset >= 0;
- assert maxResults > 0;
- assert results != null;
-
- // Parse the full-text search and search against the 'fts' field ...
- QueryParser parser = new QueryParser(ContentIndex.FULL_TEXT,
createAnalyzer());
- Query query = parser.parse(fullTextString);
- TopDocs docs = getContentSearcher().search(query, maxResults + offset);
-
- // Collect the results ...
- IndexReader contentReader = getContentReader();
- IndexReader pathReader = getPathsReader();
- IndexSearcher pathSearcher = getPathsSearcher();
- ScoreDoc[] scoreDocs = docs.scoreDocs;
- int numberOfResults = scoreDocs.length;
- if (numberOfResults > offset) {
- // There are enough results to satisfy the offset ...
- PathFactory pathFactory = context.getValueFactories().getPathFactory();
- for (int i = offset, num = scoreDocs.length; i != num; ++i) {
- ScoreDoc result = scoreDocs[i];
- int docId = result.doc;
- // Find the UUID of the node (this UUID might be artificial, so we
have to find the path) ...
- Document doc = contentReader.document(docId, UUID_FIELD_SELECTOR);
- String uuid = doc.get(ContentIndex.UUID);
- // Find the path for this node (is there a better way to do this than
one search per UUID?) ...
- TopDocs pathDocs = pathSearcher.search(new TermQuery(new
Term(PathIndex.UUID, uuid)), 1);
- if (pathDocs.scoreDocs.length < 1) {
- // No path record found ...
- continue;
- }
- Document pathDoc = pathReader.document(pathDocs.scoreDocs[0].doc);
- String pathString = pathDoc.get(PathIndex.PATH);
- Path path = pathFactory.create(pathString);
- // Now add the location ...
- results.add(Location.create(path, UUID.fromString(uuid)));
- }
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.search.IndexSession#query(org.jboss.dna.graph.query.QueryContext,
- * org.jboss.dna.graph.query.model.QueryCommand)
- */
- public QueryResults query( QueryContext queryContext,
- QueryCommand query ) {
- return queryEngine.execute(queryContext, query);
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.jboss.dna.search.IndexSession#commit()
- */
- public void commit() throws IOException {
- IOException ioError = null;
- RuntimeException runtimeError = null;
- if (pathsReader != null) {
- try {
- pathsReader.close();
- } catch (IOException e) {
- ioError = e;
- } catch (RuntimeException e) {
- runtimeError = e;
- } finally {
- pathsReader = null;
- }
- }
- if (contentReader != null) {
- try {
- contentReader.close();
- } catch (IOException e) {
- if (ioError == null) ioError = e;
- } catch (RuntimeException e) {
- if (runtimeError == null) runtimeError = e;
- } finally {
- contentReader = null;
- }
- }
- if (pathsWriter != null) {
- try {
- pathsWriter.commit();
- } catch (IOException e) {
- ioError = e;
- } catch (RuntimeException e) {
- runtimeError = e;
- } finally {
- try {
- pathsWriter.close();
- } catch (IOException e) {
- ioError = e;
- } catch (RuntimeException e) {
- runtimeError = e;
- } finally {
- pathsWriter = null;
- }
- }
- }
- if (contentWriter != null) {
- try {
- contentWriter.commit();
- } catch (IOException e) {
- if (ioError == null) ioError = e;
- } catch (RuntimeException e) {
- if (runtimeError == null) runtimeError = e;
- } finally {
- try {
- contentWriter.close();
- } catch (IOException e) {
- ioError = e;
- } catch (RuntimeException e) {
- runtimeError = e;
- } finally {
- contentWriter = null;
- }
- }
- }
- if (ioError != null) throw ioError;
- if (runtimeError != null) throw runtimeError;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.jboss.dna.search.IndexSession#rollback()
- */
- public void rollback() throws IOException {
- IOException ioError = null;
- RuntimeException runtimeError = null;
- if (pathsReader != null) {
- try {
- pathsReader.close();
- } catch (IOException e) {
- ioError = e;
- } catch (RuntimeException e) {
- runtimeError = e;
- } finally {
- pathsReader = null;
- }
- }
- if (contentReader != null) {
- try {
- contentReader.close();
- } catch (IOException e) {
- if (ioError == null) ioError = e;
- } catch (RuntimeException e) {
- if (runtimeError == null) runtimeError = e;
- } finally {
- contentReader = null;
- }
- }
- if (pathsWriter != null) {
- try {
- pathsWriter.rollback();
- } catch (IOException e) {
- ioError = e;
- } catch (RuntimeException e) {
- runtimeError = e;
- } finally {
- try {
- pathsWriter.close();
- } catch (IOException e) {
- ioError = e;
- } catch (RuntimeException e) {
- runtimeError = e;
- } finally {
- pathsWriter = null;
- }
- }
- }
- if (contentWriter != null) {
- try {
- contentWriter.rollback();
- } catch (IOException e) {
- if (ioError == null) ioError = e;
- } catch (RuntimeException e) {
- if (runtimeError == null) runtimeError = e;
- } finally {
- try {
- contentWriter.close();
- } catch (IOException e) {
- ioError = e;
- } catch (RuntimeException e) {
- runtimeError = e;
- } finally {
- contentWriter = null;
- }
- }
- }
- if (ioError != null) throw ioError;
- if (runtimeError != null) throw runtimeError;
- }
-
- protected QueryEngine createQueryProcessor() {
- // Create the query engine ...
- Planner planner = new CanonicalPlanner();
- Optimizer optimizer = new RuleBasedOptimizer() {
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.graph.query.optimize.RuleBasedOptimizer#populateRuleStack(java.util.LinkedList,
- * org.jboss.dna.graph.query.plan.PlanHints)
- */
- @Override
- protected void populateRuleStack( LinkedList<OptimizerRule>
ruleStack,
- PlanHints hints ) {
- super.populateRuleStack(ruleStack, hints);
- // Add any custom rules here, either at the front of the stack or at
the end
- }
- };
- QueryProcessor processor = new QueryProcessor() {
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.graph.query.process.QueryProcessor#createAccessComponent(org.jboss.dna.graph.query.model.QueryCommand,
- * org.jboss.dna.graph.query.QueryContext,
org.jboss.dna.graph.query.plan.PlanNode,
- * org.jboss.dna.graph.query.QueryResults.Columns,
- * org.jboss.dna.graph.query.process.SelectComponent.Analyzer)
- */
- @Override
- protected ProcessingComponent createAccessComponent( QueryCommand
originalQuery,
- QueryContext
context,
- PlanNode
accessNode,
- Columns
resultColumns,
-
org.jboss.dna.graph.query.process.SelectComponent.Analyzer analyzer ) {
- try {
- return LuceneSession.this.createAccessComponent(originalQuery,
- context,
- accessNode,
- resultColumns,
- analyzer);
- } catch (IOException e) {
- I18n msg = SearchI18n.errorWhilePerformingQuery;
- context.getProblems().addError(e,
- msg,
- Visitors.readable(originalQuery),
- getWorkspaceName(),
- getSourceName(),
- e.getMessage());
- return null;
- }
- }
- };
-
- return new QueryEngine(planner, optimizer, processor);
- }
-
- protected abstract ProcessingComponent createAccessComponent( QueryCommand
originalQuery,
- QueryContext
context,
- PlanNode
accessNode,
- Columns
resultColumns,
-
org.jboss.dna.graph.query.process.SelectComponent.Analyzer analyzer )
- throws IOException;
-
- /**
- * Get the set of UUIDs for the children of the node at the given path.
- *
- * @param parentPath the path to the parent node; may not be null
- * @return the UUIDs of the child nodes; never null but possibly empty
- * @throws IOException if there is an error accessing the indexes
- */
- protected Set<UUID> getUuidsForChildrenOf( Path parentPath ) throws
IOException {
- // Find the path of the parent ...
- String stringifiedPath = pathAsString(parentPath, stringFactory);
- // Append a '/' to the parent path, so we'll only get decendants
...
- stringifiedPath = stringifiedPath + '/';
-
- // Create a query to find all the nodes below the parent path ...
- Query query = new PrefixQuery(new Term(PathIndex.PATH, stringifiedPath));
- // Include only the children ...
- int childrenDepth = parentPath.size() + 1;
- Query depthQuery = NumericRangeQuery.newIntRange(PathIndex.DEPTH,
childrenDepth, childrenDepth, true, true);
- // And combine ...
- BooleanQuery combinedQuery = new BooleanQuery();
- combinedQuery.add(query, Occur.MUST);
- combinedQuery.add(depthQuery, Occur.MUST);
- query = combinedQuery;
-
- // Now execute and collect the UUIDs ...
- UuidCollector uuidCollector = new UuidCollector();
- IndexSearcher searcher = getPathsSearcher();
- searcher.search(query, uuidCollector);
- return uuidCollector.getUuids();
- }
-
- /**
- * Get the set of UUIDs for the nodes that are descendants of the node at the
given path.
- *
- * @param parentPath the path to the parent node; may not be null and
<i>may not be the root node</i>
- * @param includeParent true if the parent node should be included in the
results, or false if only the descendants should
- * be included
- * @return the UUIDs of the nodes; never null but possibly empty
- * @throws IOException if there is an error accessing the indexes
- */
- protected Set<UUID> getUuidsForDescendantsOf( Path parentPath,
- boolean includeParent ) throws
IOException {
- assert !parentPath.isRoot();
-
- // Find the path of the parent ...
- String stringifiedPath = pathAsString(parentPath, stringFactory);
- if (!includeParent) {
- // Append a '/' to the parent path, and we'll only get
decendants ...
- stringifiedPath = stringifiedPath + '/';
- }
-
- // Create a prefix query ...
- Query query = new PrefixQuery(new Term(PathIndex.PATH, stringifiedPath));
-
- // Now execute and collect the UUIDs ...
- UuidCollector uuidCollector = new UuidCollector();
- IndexSearcher searcher = getPathsSearcher();
- searcher.search(query, uuidCollector);
- return uuidCollector.getUuids();
- }
-
- /**
- * Get the set containing the single UUID for the node at the given path.
- *
- * @param path the path to the node; may not be null
- * @return the UUID of the supplied node; or null if the node cannot be found
- * @throws IOException if there is an error accessing the indexes
- */
- protected UUID getUuidFor( Path path ) throws IOException {
- // Create a query to find all the nodes below the parent path ...
- IndexSearcher searcher = getPathsSearcher();
- String stringifiedPath = pathAsString(path, stringFactory);
- TermQuery query = new TermQuery(new Term(PathIndex.PATH, stringifiedPath));
-
- // Now execute and collect the UUIDs ...
- TopDocs topDocs = searcher.search(query, 1);
- if (topDocs.totalHits == 0) return null;
- Document pathDoc = getPathsReader().document(topDocs.scoreDocs[0].doc);
- String uuidString = pathDoc.get(PathIndex.UUID);
- return UUID.fromString(uuidString);
- }
-
- /**
- * Utility method to create a query to find all of the documents representing
nodes with the supplied UUIDs.
- *
- * @param uuids the UUIDs of the nodes that are to be found; may not be null
- * @return the query; never null
- */
- protected Query findAllNodesWithUuids( Set<UUID> uuids ) {
- if (uuids.isEmpty()) {
- // There are no children, so return a null query ...
- return new MatchNoneQuery();
- }
- if (uuids.size() == 1) {
- UUID uuid = uuids.iterator().next();
- if (uuid == null) return new MatchNoneQuery();
- return new TermQuery(new Term(ContentIndex.UUID, uuid.toString()));
- }
- if (uuids.size() < 50) {
- // Create an OR boolean query for all the UUIDs, since this is probably
more efficient ...
- BooleanQuery query = new BooleanQuery();
- for (UUID uuid : uuids) {
- Query uuidQuery = new TermQuery(new Term(ContentIndex.UUID,
uuid.toString()));
- query.add(uuidQuery, Occur.SHOULD);
- }
- return query;
- }
- // Returna query that will always find all of the UUIDs ...
- return new UuidsQuery(ContentIndex.UUID, uuids,
getContext().getValueFactories().getUuidFactory());
- }
-
- protected Query findAllNodesBelow( Path ancestorPath ) throws IOException {
- if (ancestorPath.isRoot()) {
- return new MatchAllDocsQuery();
- }
- Set<UUID> uuids = getUuidsForDescendantsOf(ancestorPath, false);
- return findAllNodesWithUuids(uuids);
- }
-
- /**
- * Return a query that can be used to find all of the documents that represent
nodes that are children of the node at the
- * supplied path.
- *
- * @param parentPath the path of the parent node.
- * @return the query; never null
- * @throws IOException if there is an error finding the UUIDs of the child nodes
- */
- protected Query findChildNodes( Path parentPath ) throws IOException {
- if (parentPath.isRoot()) {
- return new MatchAllDocsQuery();
- }
- Set<UUID> childUuids = getUuidsForChildrenOf(parentPath);
- return findAllNodesWithUuids(childUuids);
- }
-
- /**
- * Create a query that can be used to find the one document (or node) that exists
at the exact path supplied. This method
- * first queries the {@link PathIndex path index} to find the UUID of the node at
the supplied path, and then returns a
- * query that matches the UUID.
- *
- * @param path the path of the node
- * @return the query; never null
- * @throws IOException if there is an error finding the UUID for the supplied
path
- */
- protected Query findNodeAt( Path path ) throws IOException {
- UUID uuid = getUuidFor(path);
- if (uuid == null) return null;
- return new TermQuery(new Term(ContentIndex.UUID, uuid.toString()));
- }
-
- /**
- * Create a query that can be used to find documents (or nodes) that have a field
value that satisfies the supplied LIKE
- * expression.
- *
- * @param fieldName the name of the document field to search
- * @param likeExpression the JCR like expression
- * @return the query; never null
- */
- protected Query findNodesLike( String fieldName,
- String likeExpression ) {
- assert likeExpression != null;
- assert likeExpression.length() > 0;
-
- // '%' matches 0 or more characters
- // '_' matches any single character
- // '\x' matches 'x'
- // all other characters match themselves
-
- // Wildcard queries are a better match, but they can be slow and should not
be used
- // if the first character of the expression is a '%' or '_'
...
- char firstChar = likeExpression.charAt(0);
- if (firstChar != '%' && firstChar != '_') {
- // Create a wildcard query ...
- String expression = toWildcardExpression(likeExpression);
- return new WildcardQuery(new Term(fieldName, expression));
- }
- // Create a regex query,
- String regex = toRegularExpression(likeExpression);
- RegexQuery query = new RegexQuery(new Term(fieldName, regex));
- query.setRegexImplementation(new JavaUtilRegexCapabilities());
- return query;
- }
-
- protected Query findNodesWith( Length propertyLength,
- Operator operator,
- Object value ) {
- assert propertyLength != null;
- assert value != null;
- PropertyValue propertyValue = propertyLength.getPropertyValue();
- String field = stringFactory.create(propertyValue.getPropertyName());
- ValueFactories factories = context.getValueFactories();
- int length = factories.getLongFactory().create(value).intValue();
- switch (operator) {
- case EQUAL_TO:
- return CompareLengthQuery.createQueryForNodesWithFieldEqualTo(length,
field, factories);
- case NOT_EQUAL_TO:
- return
CompareLengthQuery.createQueryForNodesWithFieldNotEqualTo(length, field, factories);
- case GREATER_THAN:
- return
CompareLengthQuery.createQueryForNodesWithFieldGreaterThan(length, field, factories);
- case GREATER_THAN_OR_EQUAL_TO:
- return
CompareLengthQuery.createQueryForNodesWithFieldGreaterThanOrEqualTo(length, field,
factories);
- case LESS_THAN:
- return
CompareLengthQuery.createQueryForNodesWithFieldLessThan(length, field, factories);
- case LESS_THAN_OR_EQUAL_TO:
- return
CompareLengthQuery.createQueryForNodesWithFieldLessThanOrEqualTo(length, field,
factories);
- case LIKE:
- // This is not allowed ...
- assert false;
- break;
- }
- return null;
- }
-
- protected Query findNodesWith( PropertyValue propertyValue,
- Operator operator,
- Object value,
- boolean caseSensitive ) {
- String field = stringFactory.create(propertyValue.getPropertyName());
- PropertyType valueType = PropertyType.discoverType(value);
- ValueFactories factories = context.getValueFactories();
- switch (valueType) {
- case NAME:
- case PATH:
- case REFERENCE:
- case URI:
- case UUID:
- case STRING:
- String stringValue = stringFactory.create(value);
- if (valueType == PropertyType.PATH) {
- stringValue = pathAsString(pathFactory.create(value),
stringFactory);
- }
- if (!caseSensitive) stringValue = stringValue.toLowerCase();
- switch (operator) {
- case EQUAL_TO:
- return new TermQuery(new Term(field, stringValue));
- case NOT_EQUAL_TO:
- Query query = new TermQuery(new Term(field, stringValue));
- return new NotQuery(query);
- case GREATER_THAN:
- return
CompareStringQuery.createQueryForNodesWithFieldGreaterThan(stringValue,
-
field,
-
factories,
-
caseSensitive);
- case GREATER_THAN_OR_EQUAL_TO:
- return
CompareStringQuery.createQueryForNodesWithFieldGreaterThanOrEqualTo(stringValue,
-
field,
-
factories,
-
caseSensitive);
- case LESS_THAN:
- return
CompareStringQuery.createQueryForNodesWithFieldLessThan(stringValue,
-
field,
-
factories,
-
caseSensitive);
- case LESS_THAN_OR_EQUAL_TO:
- return
CompareStringQuery.createQueryForNodesWithFieldLessThanOrEqualTo(stringValue,
-
field,
-
factories,
-
caseSensitive);
- case LIKE:
- return findNodesLike(field, stringValue);
- }
- break;
- case DATE:
- long date = factories.getLongFactory().create(value);
- switch (operator) {
- case EQUAL_TO:
- return NumericRangeQuery.newLongRange(field, date, date,
true, true);
- case NOT_EQUAL_TO:
- Query query = NumericRangeQuery.newLongRange(field, date,
date, true, true);
- return new NotQuery(query);
- case GREATER_THAN:
- return NumericRangeQuery.newLongRange(field, date, MAX_DATE,
false, true);
- case GREATER_THAN_OR_EQUAL_TO:
- return NumericRangeQuery.newLongRange(field, date, MAX_DATE,
true, true);
- case LESS_THAN:
- return NumericRangeQuery.newLongRange(field, MIN_DATE, date,
true, false);
- case LESS_THAN_OR_EQUAL_TO:
- return NumericRangeQuery.newLongRange(field, MIN_DATE, date,
true, true);
- case LIKE:
- // This is not allowed ...
- assert false;
- return null;
- }
- break;
- case LONG:
- long longValue = factories.getLongFactory().create(value);
- switch (operator) {
- case EQUAL_TO:
- return NumericRangeQuery.newLongRange(field, longValue,
longValue, true, true);
- case NOT_EQUAL_TO:
- Query query = NumericRangeQuery.newLongRange(field,
longValue, longValue, true, true);
- return new NotQuery(query);
- case GREATER_THAN:
- return NumericRangeQuery.newLongRange(field, longValue,
MAX_LONG, false, true);
- case GREATER_THAN_OR_EQUAL_TO:
- return NumericRangeQuery.newLongRange(field, longValue,
MAX_LONG, true, true);
- case LESS_THAN:
- return NumericRangeQuery.newLongRange(field, MIN_LONG,
longValue, true, false);
- case LESS_THAN_OR_EQUAL_TO:
- return NumericRangeQuery.newLongRange(field, MIN_LONG,
longValue, true, true);
- case LIKE:
- // This is not allowed ...
- assert false;
- return null;
- }
- break;
- case DECIMAL:
- case DOUBLE:
- double doubleValue = factories.getDoubleFactory().create(value);
- switch (operator) {
- case EQUAL_TO:
- return NumericRangeQuery.newDoubleRange(field, doubleValue,
doubleValue, true, true);
- case NOT_EQUAL_TO:
- Query query = NumericRangeQuery.newDoubleRange(field,
doubleValue, doubleValue, true, true);
- return new NotQuery(query);
- case GREATER_THAN:
- return NumericRangeQuery.newDoubleRange(field, doubleValue,
MAX_DOUBLE, false, true);
- case GREATER_THAN_OR_EQUAL_TO:
- return NumericRangeQuery.newDoubleRange(field, doubleValue,
MAX_DOUBLE, true, true);
- case LESS_THAN:
- return NumericRangeQuery.newDoubleRange(field, MIN_DOUBLE,
doubleValue, true, false);
- case LESS_THAN_OR_EQUAL_TO:
- return NumericRangeQuery.newDoubleRange(field, MIN_DOUBLE,
doubleValue, true, true);
- case LIKE:
- // This is not allowed ...
- assert false;
- return null;
- }
- break;
- case BOOLEAN:
- boolean booleanValue = factories.getBooleanFactory().create(value);
- stringValue = stringFactory.create(value);
- switch (operator) {
- case EQUAL_TO:
- return new TermQuery(new Term(field, stringValue));
- case NOT_EQUAL_TO:
- return new TermQuery(new Term(field,
stringFactory.create(!booleanValue)));
- case GREATER_THAN:
- if (!booleanValue) {
- return new TermQuery(new Term(field,
stringFactory.create(true)));
- }
- // Can't be greater than 'true', per JCR spec
- return new MatchNoneQuery();
- case GREATER_THAN_OR_EQUAL_TO:
- return new TermQuery(new Term(field,
stringFactory.create(true)));
- case LESS_THAN:
- if (booleanValue) {
- return new TermQuery(new Term(field,
stringFactory.create(false)));
- }
- // Can't be less than 'false', per JCR spec
- return new MatchNoneQuery();
- case LESS_THAN_OR_EQUAL_TO:
- return new TermQuery(new Term(field,
stringFactory.create(false)));
- case LIKE:
- // This is not allowed ...
- assert false;
- return null;
- }
- break;
- case OBJECT:
- case BINARY:
- // This is not allowed ...
- assert false;
- return null;
- }
- return null;
- }
-
- protected Query findNodesWithNumericRange( PropertyValue propertyValue,
- Object lowerValue,
- Object upperValue,
- boolean includesLower,
- boolean includesUpper ) {
- String field = stringFactory.create(propertyValue.getPropertyName());
- return findNodesWithNumericRange(field, lowerValue, upperValue,
includesLower, includesUpper);
- }
-
- protected Query findNodesWithNumericRange( NodeDepth depth,
- Object lowerValue,
- Object upperValue,
- boolean includesLower,
- boolean includesUpper ) {
- return findNodesWithNumericRange(PathIndex.DEPTH, lowerValue, upperValue,
includesLower, includesUpper);
- }
-
- protected Query findNodesWithNumericRange( String field,
- Object lowerValue,
- Object upperValue,
- boolean includesLower,
- boolean includesUpper ) {
- PropertyType type = PropertyType.discoverType(lowerValue);
- assert type == PropertyType.discoverType(upperValue);
- ValueFactories factories = context.getValueFactories();
- switch (type) {
- case DATE:
- long lowerDate = factories.getLongFactory().create(lowerValue);
- long upperDate = factories.getLongFactory().create(upperValue);
- return NumericRangeQuery.newLongRange(field, lowerDate, upperDate,
includesLower, includesUpper);
- case LONG:
- long lowerLong = factories.getLongFactory().create(lowerValue);
- long upperLong = factories.getLongFactory().create(upperValue);
- return NumericRangeQuery.newLongRange(field, lowerLong, upperLong,
includesLower, includesUpper);
- case DECIMAL:
- case DOUBLE:
- double lowerDouble =
factories.getDoubleFactory().create(lowerValue);
- double upperDouble =
factories.getDoubleFactory().create(upperValue);
- return NumericRangeQuery.newDoubleRange(field, lowerDouble,
upperDouble, includesLower, includesUpper);
- default:
- // This is not allowed ...
- assert false;
- return null;
- }
- }
-
- protected Query findNodesWith( NodePath nodePath,
- Operator operator,
- Object value,
- boolean caseSensitive ) throws IOException {
- if (!caseSensitive) value = stringFactory.create(value).toLowerCase();
- Path pathValue = operator != Operator.LIKE ? pathFactory.create(value) :
null;
- Query query = null;
- switch (operator) {
- case EQUAL_TO:
- return findNodeAt(pathValue);
- case NOT_EQUAL_TO:
- return new NotQuery(findNodeAt(pathValue));
- case LIKE:
- String likeExpression = stringFactory.create(value);
- return findNodesLike(PathIndex.PATH, likeExpression);
- case GREATER_THAN:
- query =
ComparePathQuery.createQueryForNodesWithPathGreaterThan(pathValue,
-
PathIndex.PATH,
-
context.getValueFactories(),
-
caseSensitive);
- break;
- case GREATER_THAN_OR_EQUAL_TO:
- query =
ComparePathQuery.createQueryForNodesWithPathGreaterThanOrEqualTo(pathValue,
-
PathIndex.PATH,
-
context.getValueFactories(),
-
caseSensitive);
- break;
- case LESS_THAN:
- query =
ComparePathQuery.createQueryForNodesWithPathLessThan(pathValue,
-
PathIndex.PATH,
-
context.getValueFactories(),
-
caseSensitive);
- break;
- case LESS_THAN_OR_EQUAL_TO:
- query =
ComparePathQuery.createQueryForNodesWithPathLessThanOrEqualTo(pathValue,
-
PathIndex.PATH,
-
context.getValueFactories(),
-
caseSensitive);
- break;
- }
- // Now execute and collect the UUIDs ...
- UuidCollector uuidCollector = new UuidCollector();
- IndexSearcher searcher = getPathsSearcher();
- searcher.search(query, uuidCollector);
- return findAllNodesWithUuids(uuidCollector.getUuids());
- }
-
- protected Query findNodesWith( NodeName nodeName,
- Operator operator,
- Object value,
- boolean caseSensitive ) throws IOException {
- String stringValue = stringFactory.create(value);
- if (!caseSensitive) stringValue = stringValue.toLowerCase();
- Path.Segment segment = operator != Operator.LIKE ?
pathFactory.createSegment(stringValue) : null;
- int snsIndex = operator != Operator.LIKE ? segment.getIndex() : 0;
- Query query = null;
- switch (operator) {
- case EQUAL_TO:
- BooleanQuery booleanQuery = new BooleanQuery();
- booleanQuery.add(new TermQuery(new Term(PathIndex.LOCAL_NAME,
stringValue)), Occur.MUST);
- booleanQuery.add(NumericRangeQuery.newIntRange(PathIndex.SNS_INDEX,
snsIndex, snsIndex, true, false),
- Occur.MUST);
- return booleanQuery;
- case NOT_EQUAL_TO:
- booleanQuery = new BooleanQuery();
- booleanQuery.add(new TermQuery(new Term(PathIndex.LOCAL_NAME,
stringValue)), Occur.MUST);
- booleanQuery.add(NumericRangeQuery.newIntRange(PathIndex.SNS_INDEX,
snsIndex, snsIndex, true, false),
- Occur.MUST);
- return new NotQuery(booleanQuery);
- case GREATER_THAN:
- query =
CompareNameQuery.createQueryForNodesWithNameGreaterThan(segment,
-
PathIndex.LOCAL_NAME,
-
PathIndex.SNS_INDEX,
-
context.getValueFactories(),
-
caseSensitive);
- break;
- case GREATER_THAN_OR_EQUAL_TO:
- query =
CompareNameQuery.createQueryForNodesWithNameGreaterThanOrEqualTo(segment,
-
PathIndex.LOCAL_NAME,
-
PathIndex.SNS_INDEX,
-
context.getValueFactories(),
-
caseSensitive);
- break;
- case LESS_THAN:
- query =
CompareNameQuery.createQueryForNodesWithNameLessThan(segment,
-
PathIndex.LOCAL_NAME,
-
PathIndex.SNS_INDEX,
-
context.getValueFactories(),
-
caseSensitive);
- break;
- case LESS_THAN_OR_EQUAL_TO:
- query =
CompareNameQuery.createQueryForNodesWithNameLessThanOrEqualTo(segment,
-
PathIndex.LOCAL_NAME,
-
PathIndex.SNS_INDEX,
-
context.getValueFactories(),
-
caseSensitive);
- break;
- case LIKE:
- // See whether the like expression has brackets ...
- String likeExpression = stringValue;
- int openBracketIndex = likeExpression.indexOf('[');
- if (openBracketIndex != -1) {
- String localNameExpression = likeExpression.substring(0,
openBracketIndex);
- String snsIndexExpression =
likeExpression.substring(openBracketIndex);
- Query localNameQuery =
createLocalNameQuery(localNameExpression);
- Query snsQuery = createSnsIndexQuery(snsIndexExpression);
- if (localNameQuery == null) {
- if (snsQuery == null) {
- query = new MatchNoneQuery();
- } else {
- // There is just an SNS part ...
- query = snsQuery;
- }
- } else {
- // There is a local name part ...
- if (snsQuery == null) {
- query = localNameQuery;
- } else {
- // There is both a local name part and a SNS part ...
- booleanQuery = new BooleanQuery();
- booleanQuery.add(localNameQuery, Occur.MUST);
- booleanQuery.add(snsQuery, Occur.MUST);
- query = booleanQuery;
- }
- }
- } else {
- // There is no SNS expression ...
- query = createLocalNameQuery(likeExpression);
- }
- assert query != null;
- break;
- }
-
- // Now execute and collect the UUIDs ...
- UuidCollector uuidCollector = new UuidCollector();
- IndexSearcher searcher = getPathsSearcher();
- searcher.search(query, uuidCollector);
- return findAllNodesWithUuids(uuidCollector.getUuids());
- }
-
- protected Query findNodesWith( NodeLocalName nodeName,
- Operator operator,
- Object value,
- boolean caseSensitive ) throws IOException {
- String nameValue = stringFactory.create(value);
- Query query = null;
- switch (operator) {
- case LIKE:
- String likeExpression = stringFactory.create(value);
- return findNodesLike(PathIndex.LOCAL_NAME, likeExpression); //
already is a query with UUIDs
- case EQUAL_TO:
- query = new TermQuery(new Term(PathIndex.LOCAL_NAME, nameValue));
- break;
- case NOT_EQUAL_TO:
- query = new NotQuery(new TermQuery(new Term(PathIndex.LOCAL_NAME,
nameValue)));
- break;
- case GREATER_THAN:
- query =
CompareStringQuery.createQueryForNodesWithFieldGreaterThan(nameValue,
-
PathIndex.LOCAL_NAME,
-
context.getValueFactories(),
-
caseSensitive);
- break;
- case GREATER_THAN_OR_EQUAL_TO:
- query =
CompareStringQuery.createQueryForNodesWithFieldGreaterThanOrEqualTo(nameValue,
-
PathIndex.LOCAL_NAME,
-
context.getValueFactories(),
-
caseSensitive);
- break;
- case LESS_THAN:
- query =
CompareStringQuery.createQueryForNodesWithFieldLessThan(nameValue,
-
PathIndex.LOCAL_NAME,
-
context.getValueFactories(),
-
caseSensitive);
- break;
- case LESS_THAN_OR_EQUAL_TO:
- query =
CompareStringQuery.createQueryForNodesWithFieldLessThanOrEqualTo(nameValue,
-
PathIndex.LOCAL_NAME,
-
context.getValueFactories(),
-
caseSensitive);
- break;
- }
-
- // Now execute and collect the UUIDs ...
- UuidCollector uuidCollector = new UuidCollector();
- IndexSearcher searcher = getPathsSearcher();
- searcher.search(query, uuidCollector);
- return findAllNodesWithUuids(uuidCollector.getUuids());
- }
-
- protected Query findNodesWith( NodeDepth depthConstraint,
- Operator operator,
- Object value ) throws IOException {
- int depth =
context.getValueFactories().getLongFactory().create(value).intValue();
- Query query = null;
- switch (operator) {
- case EQUAL_TO:
- query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, depth, depth,
true, true);
- break;
- case NOT_EQUAL_TO:
- query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, depth, depth,
true, true);
- query = new NotQuery(query);
- break;
- case GREATER_THAN:
- query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, depth,
MAX_DEPTH, false, true);
- break;
- case GREATER_THAN_OR_EQUAL_TO:
- query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, depth,
MAX_DEPTH, true, true);
- break;
- case LESS_THAN:
- query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, MIN_DEPTH,
depth, true, false);
- break;
- case LESS_THAN_OR_EQUAL_TO:
- query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, MIN_DEPTH,
depth, true, true);
- break;
- case LIKE:
- // This is not allowed ...
- return null;
- }
-
- // Now execute and collect the UUIDs ...
- UuidCollector uuidCollector = new UuidCollector();
- IndexSearcher searcher = getPathsSearcher();
- searcher.search(query, uuidCollector);
- return findAllNodesWithUuids(uuidCollector.getUuids());
- }
-
- protected Query createLocalNameQuery( String likeExpression ) {
- if (likeExpression == null) return null;
- likeExpression = likeExpression.trim();
- if (likeExpression.length() == 0) return null;
- if (likeExpression.indexOf('?') != -1 ||
likeExpression.indexOf('*') != -1) {
- // The local name is a like ...
- return findNodesLike(PathIndex.LOCAL_NAME, likeExpression);
- }
- // The local name is an exact match ...
- return new TermQuery(new Term(PathIndex.LOCAL_NAME, likeExpression));
- }
-
- protected Query createSnsIndexQuery( String likeExpression ) {
- if (likeExpression == null) return null;
- likeExpression = likeExpression.trim();
- if (likeExpression.length() == 0) return null;
-
- // Remove the leading '[' ...
- assert likeExpression.charAt(0) == '[';
- likeExpression = likeExpression.substring(1);
-
- // Remove the trailing ']' if it exists ...
- int closeBracketIndex = likeExpression.indexOf(']');
- if (closeBracketIndex != -1) {
- likeExpression = likeExpression.substring(0, closeBracketIndex);
- }
- // If SNS expression contains '?' or '*' ...
- if (likeExpression.indexOf('?') != -1 ||
likeExpression.indexOf('*') != -1) {
- // There is a LIKE expression for the SNS ...
- return findNodesLike(PathIndex.SNS_INDEX, likeExpression);
- }
- // This is not a LIKE expression but an exact value specification and should
be a number ...
- try {
- // This SNS is just a number ...
- int sns = Integer.parseInt(likeExpression);
- return NumericRangeQuery.newIntRange(PathIndex.SNS_INDEX, sns, sns, true,
false);
- } catch (NumberFormatException e) {
- // It's not a number but it's in the SNS field, so there will be
no results ...
- return new MatchNoneQuery();
- }
- }
-
- }
-
- /**
- * Convert the JCR like expression to a Lucene wildcard expression. The JCR like
expression uses '%' to match 0 or more
- * characters, '_' to match any single character, '\x' to match the
'x' character, and all other characters to match
- * themselves.
- *
- * @param likeExpression the like expression; may not be null
- * @return the expression that can be used with a WildcardQuery; never null
- */
- protected static String toWildcardExpression( String likeExpression ) {
- assert likeExpression != null;
- assert likeExpression.length() > 0;
- return likeExpression.replace('%', '*').replace('_',
'?').replaceAll("\\\\(.)", "$1");
- }
-
- /**
- * Convert the JCR like expression to a regular expression. The JCR like expression
uses '%' to match 0 or more characters,
- * '_' to match any single character, '\x' to match the 'x'
character, and all other characters to match themselves. Note that
- * if any regex metacharacters appear in the like expression, they will be escaped
within the resulting regular expression.
- *
- * @param likeExpression the like expression; may not be null
- * @return the expression that can be used with a WildcardQuery; never null
- */
- protected static String toRegularExpression( String likeExpression ) {
- assert likeExpression != null;
- assert likeExpression.length() > 0;
- // Replace all '\x' with 'x' ...
- String result = likeExpression.replaceAll("\\\\(.)", "$1");
- // Escape characters used as metacharacters in regular expressions, including
- // '[', '^', '\', '$', '.', '|',
'?', '*', '+', '(', and ')'
- result = result.replaceAll("([[^\\\\$.|?*+()])", "\\$1");
- // Replace '%'->'[.]+' and '_'->'[.]
- result = likeExpression.replace("%",
"[.]+").replace("_", "[.]");
- return result;
- }
-
- protected static String pathAsString( Path path,
- ValueFactory<String> stringFactory ) {
- assert path != null;
- if (path.isRoot()) return "/";
- String pathStr = stringFactory.create(path);
- if (!pathStr.endsWith("]")) {
- pathStr = pathStr + '[' + Path.DEFAULT_INDEX + ']';
- }
- return pathStr;
- }
-
- /**
- * A {@link Collector} implementation that only captures the UUID of the documents
returned by a query. Score information is
- * not recorded. This is often used when querying the {@link PathIndex} to collect
the UUIDs of a set of nodes satisfying some
- * path constraint.
- *
- * @see DualIndexLayout.LuceneSession#findChildNodes(Path)
- */
- protected static class UuidCollector extends Collector {
- private final Set<UUID> uuids = new HashSet<UUID>();
- private String[] uuidsByDocId;
- private int baseDocId;
-
- protected UuidCollector() {
- }
-
- /**
- * Get the UUIDs that have been collected.
- *
- * @return the set of UUIDs; never null
- */
- public Set<UUID> getUuids() {
- return uuids;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.apache.lucene.search.Collector#acceptsDocsOutOfOrder()
- */
- @Override
- public boolean acceptsDocsOutOfOrder() {
- return true;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see
org.apache.lucene.search.Collector#setScorer(org.apache.lucene.search.Scorer)
- */
- @Override
- public void setScorer( Scorer scorer ) {
- // we don't care about scoring
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.apache.lucene.search.Collector#collect(int)
- */
- @Override
- public void collect( int doc ) {
- int index = doc - baseDocId;
- assert index >= 0;
- String uuidString = uuidsByDocId[index];
- assert uuidString != null;
- uuids.add(UUID.fromString(uuidString));
- }
-
- /**
- * {@inheritDoc}
- *
- * @see
org.apache.lucene.search.Collector#setNextReader(org.apache.lucene.index.IndexReader,
int)
- */
- @Override
- public void setNextReader( IndexReader reader,
- int docBase ) throws IOException {
- this.uuidsByDocId = FieldCache.DEFAULT.getStrings(reader, UUID_FIELD);
- this.baseDocId = docBase;
- }
- }
-}
Copied: trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexSearchProvider.java
(from rev 1328, trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexLayout.java)
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexSearchProvider.java
(rev 0)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexSearchProvider.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -0,0 +1,1578 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.search;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import net.jcip.annotations.ThreadSafe;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldSelector;
+import org.apache.lucene.document.FieldSelectorResult;
+import org.apache.lucene.document.NumericField;
+import org.apache.lucene.document.Field.Index;
+import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.IndexWriter.MaxFieldLength;
+import org.apache.lucene.queryParser.ParseException;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.NumericRangeQuery;
+import org.apache.lucene.search.PrefixQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.WildcardQuery;
+import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.search.regex.JavaUtilRegexCapabilities;
+import org.apache.lucene.search.regex.RegexQuery;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.Version;
+import org.jboss.dna.common.text.NoOpEncoder;
+import org.jboss.dna.common.text.TextEncoder;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.graph.DnaLexicon;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.JcrLexicon;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.Node;
+import org.jboss.dna.graph.property.Binary;
+import org.jboss.dna.graph.property.DateTime;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.PathFactory;
+import org.jboss.dna.graph.property.Property;
+import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.property.ValueFactories;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryEngine;
+import org.jboss.dna.graph.query.QueryResults;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.model.Length;
+import org.jboss.dna.graph.query.model.NodeDepth;
+import org.jboss.dna.graph.query.model.NodeLocalName;
+import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.NodePath;
+import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.optimize.Optimizer;
+import org.jboss.dna.graph.query.optimize.OptimizerRule;
+import org.jboss.dna.graph.query.optimize.RuleBasedOptimizer;
+import org.jboss.dna.graph.query.plan.CanonicalPlanner;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.Planner;
+import org.jboss.dna.graph.query.process.ProcessingComponent;
+import org.jboss.dna.graph.query.process.QueryProcessor;
+import org.jboss.dna.graph.request.ChangeRequest;
+import org.jboss.dna.graph.search.SearchException;
+import org.jboss.dna.graph.search.SearchProvider;
+import org.jboss.dna.search.IndexRules.Rule;
+import org.jboss.dna.search.query.CompareLengthQuery;
+import org.jboss.dna.search.query.CompareNameQuery;
+import org.jboss.dna.search.query.ComparePathQuery;
+import org.jboss.dna.search.query.CompareStringQuery;
+import org.jboss.dna.search.query.MatchNoneQuery;
+import org.jboss.dna.search.query.NotQuery;
+import org.jboss.dna.search.query.UuidsQuery;
+
+/**
+ * A simple {@link SearchProvider} implementation that relies upon two separate indexes:
one for the node content and a second one
+ * for paths and UUIDs.
+ */
+@ThreadSafe
+public class DualIndexSearchProvider implements SearchProvider {
+
+ /**
+ * The default set of {@link IndexRules} used by {@link DualIndexSearchProvider}
instances when no rules are provided. These
+ * rules default to index and analyze all properties, and to index the {@link
DnaLexicon#UUID dna:uuid} and
+ * {@link JcrLexicon#UUID jcr:uuid} properties to be indexed and stored only (not
analyzed and not included in full-text
+ * search. The rules also treat {@link JcrLexicon#CREATED jcr:created} and {@link
JcrLexicon#LAST_MODIFIED jcr:lastModified}
+ * properties as dates.
+ */
+ public static final IndexRules DEFAULT_RULES;
+
+ static {
+ IndexRules.Builder builder = IndexRules.createBuilder();
+ // Configure the default behavior ...
+ builder.defaultTo(IndexRules.INDEX | IndexRules.ANALYZE);
+ // Configure the UUID properties to be just indexed (not stored, not analyzed,
not included in full-text) ...
+ builder.store(JcrLexicon.UUID, DnaLexicon.UUID);
+ // Configure the properties that we'll treat as dates ...
+ builder.treatAsDates(JcrLexicon.CREATED, JcrLexicon.LAST_MODIFIED);
+ DEFAULT_RULES = builder.build();
+ }
+
+ protected static final long MIN_DATE = 0;
+ protected static final long MAX_DATE = Long.MAX_VALUE;
+ protected static final long MIN_LONG = Long.MIN_VALUE;
+ protected static final long MAX_LONG = Long.MAX_VALUE;
+ protected static final double MIN_DOUBLE = Double.MIN_VALUE;
+ protected static final double MAX_DOUBLE = Double.MAX_VALUE;
+ protected static final int MIN_DEPTH = 0;
+ protected static final int MAX_DEPTH = 100;
+
+ protected static final String PATHS_INDEX_NAME = "paths";
+ protected static final String CONTENT_INDEX_NAME = "content";
+
+ protected static final String UUID_FIELD = "uuid";
+ protected static final String FULL_TEXT_SUFFIX = "/fs"; // the slash
character is not allowed in a property name unescaped
+
+ static class PathIndex {
+ public static final String PATH = "path";
+ public static final String LOCAL_NAME = "name";
+ public static final String SNS_INDEX = "sns";
+ public static final String UUID = UUID_FIELD;
+ public static final String DEPTH = "depth";
+ }
+
+ static class ContentIndex {
+ public static final String UUID = UUID_FIELD;
+ public static final String FULL_TEXT = "fts";
+ }
+
+ /**
+ * The number of results that should be returned when performing queries while
deleting entire branches of content. The
+ * current value is {@value} .
+ */
+ protected static final int SIZE_OF_DELETE_BATCHES = 1000;
+
+ private ThreadLocal<DateFormat> dateFormatter = new
ThreadLocal<DateFormat>() {
+ @Override
+ protected DateFormat initialValue() {
+ return new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
+ }
+ };
+
+ /**
+ * Obtain an immutable {@link FieldSelector} instance that accesses the UUID field.
+ */
+ protected static final FieldSelector UUID_FIELD_SELECTOR = new FieldSelector() {
+ private static final long serialVersionUID = 1L;
+
+ public FieldSelectorResult accept( String fieldName ) {
+ return PathIndex.UUID.equals(fieldName) ? FieldSelectorResult.LOAD_AND_BREAK
: FieldSelectorResult.NO_LOAD;
+ }
+ };
+
+ private final IndexRules rules;
+ private final LuceneConfiguration directoryConfiguration;
+
+ public DualIndexSearchProvider( LuceneConfiguration directoryConfiguration,
+ IndexRules rules ) {
+ assert directoryConfiguration != null;
+ assert rules != null;
+ this.rules = rules;
+ this.directoryConfiguration = directoryConfiguration;
+ }
+
+ public DualIndexSearchProvider( LuceneConfiguration directoryConfiguration ) {
+ this(directoryConfiguration, DEFAULT_RULES);
+ }
+
+ /**
+ * Get the date formatter that can be reused safely within the current thread.
+ *
+ * @return the date formatter; never null
+ */
+ protected DateFormat dateFormatter() {
+ return dateFormatter.get();
+ }
+
+ /**
+ * Get the text encoder that should be used to encode namespaces in the search
index.
+ *
+ * @return the namespace text encoder; never null
+ */
+ protected TextEncoder getNamespaceEncoder() {
+ return new NoOpEncoder();
+ }
+
+ /**
+ * Create a Lucene {@link Analyzer} analyzer that should be used for indexing and
searching.
+ *
+ * @return the analyzer; never null
+ */
+ protected Analyzer createAnalyzer() {
+ return new StandardAnalyzer(Version.LUCENE_CURRENT);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.search.SearchProvider#createSession(org.jboss.dna.graph.ExecutionContext,
java.lang.String,
+ * java.lang.String, boolean, boolean)
+ */
+ public SearchProvider.Session createSession( ExecutionContext context,
+ String sourceName,
+ String workspaceName,
+ boolean overwrite,
+ boolean readOnly ) {
+ Directory pathIndexDirectory = directoryConfiguration.getDirectory(workspaceName,
PATHS_INDEX_NAME);
+ Directory contentIndexDirectory =
directoryConfiguration.getDirectory(workspaceName, CONTENT_INDEX_NAME);
+ assert pathIndexDirectory != null;
+ assert contentIndexDirectory != null;
+ Analyzer analyzer = createAnalyzer();
+ assert analyzer != null;
+ return new DualIndexSession(context, sourceName, workspaceName, rules,
pathIndexDirectory, contentIndexDirectory,
+ analyzer, overwrite, readOnly);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.search.SearchProvider#destroyIndexes(org.jboss.dna.graph.ExecutionContext,
java.lang.String,
+ * java.lang.String)
+ */
+ public boolean destroyIndexes( ExecutionContext context,
+ String sourceName,
+ String workspaceName ) {
+ directoryConfiguration.destroyDirectory(workspaceName, PATHS_INDEX_NAME);
+ directoryConfiguration.destroyDirectory(workspaceName, CONTENT_INDEX_NAME);
+ return true;
+ }
+
+ protected class DualIndexSession extends LuceneSession {
+ private final Directory pathsIndexDirectory;
+ private final Directory contentIndexDirectory;
+ private IndexReader pathsReader;
+ private IndexWriter pathsWriter;
+ private IndexSearcher pathsSearcher;
+ private IndexReader contentReader;
+ private IndexWriter contentWriter;
+ private IndexSearcher contentSearcher;
+
+ protected DualIndexSession( ExecutionContext context,
+ String sourceName,
+ String workspaceName,
+ IndexRules rules,
+ Directory pathsIndexDirectory,
+ Directory contentIndexDirectory,
+ Analyzer analyzer,
+ boolean overwrite,
+ boolean readOnly ) {
+ super(context, sourceName, workspaceName, rules, analyzer, overwrite,
readOnly);
+ this.pathsIndexDirectory = pathsIndexDirectory;
+ this.contentIndexDirectory = contentIndexDirectory;
+ assert this.pathsIndexDirectory != null;
+ assert this.contentIndexDirectory != null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.LuceneSession#fullTextFieldName(java.lang.String)
+ */
+ @Override
+ protected String fullTextFieldName( String propertyName ) {
+ return propertyName + FULL_TEXT_SUFFIX;
+ }
+
+ protected IndexReader getPathsReader() throws IOException {
+ if (pathsReader == null) {
+ pathsReader = IndexReader.open(pathsIndexDirectory, readOnly);
+ }
+ return pathsReader;
+ }
+
+ protected IndexReader getContentReader() throws IOException {
+ if (contentReader == null) {
+ contentReader = IndexReader.open(contentIndexDirectory, readOnly);
+ }
+ return contentReader;
+ }
+
+ protected IndexWriter getPathsWriter() throws IOException {
+ assert !readOnly;
+ if (pathsWriter == null) {
+ pathsWriter = new IndexWriter(pathsIndexDirectory, analyzer, overwrite,
MaxFieldLength.UNLIMITED);
+ }
+ return pathsWriter;
+ }
+
+ protected IndexWriter getContentWriter() throws IOException {
+ assert !readOnly;
+ if (contentWriter == null) {
+ contentWriter = new IndexWriter(contentIndexDirectory, analyzer,
overwrite, MaxFieldLength.UNLIMITED);
+ }
+ return contentWriter;
+ }
+
+ protected IndexSearcher getPathsSearcher() throws IOException {
+ if (pathsSearcher == null) {
+ pathsSearcher = new IndexSearcher(getPathsReader());
+ }
+ return pathsSearcher;
+ }
+
+ @Override
+ public IndexSearcher getContentSearcher() throws IOException {
+ if (contentSearcher == null) {
+ contentSearcher = new IndexSearcher(getContentReader());
+ }
+ return contentSearcher;
+ }
+
+ protected boolean hasWriters() {
+ return pathsWriter != null || contentWriter != null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.search.SearchProvider.Session#index(org.jboss.dna.graph.Node)
+ */
+ public void index( Node node ) {
+ assert !readOnly;
+ Location location = node.getLocation();
+ UUID uuid = location.getUuid();
+ if (uuid == null) uuid = UUID.randomUUID();
+ Path path = location.getPath();
+ String uuidStr = stringFactory.create(uuid);
+ String pathStr = pathAsString(path, stringFactory);
+ String nameStr = path.isRoot() ? "" :
stringFactory.create(path.getLastSegment().getName());
+ int sns = path.isRoot() ? 1 : path.getLastSegment().getIndex();
+
+ Logger logger = Logger.getLogger(getClass());
+ if (logger.isTraceEnabled()) {
+ logger.trace("indexing {0}", pathStr);
+ }
+
+ try {
+
+ // Create a separate document for the path, which makes it easier to
handle moves since the path can
+ // be changed without changing any other content fields ...
+ Document doc = new Document();
+ doc.add(new Field(PathIndex.PATH, pathStr, Field.Store.YES,
Field.Index.NOT_ANALYZED));
+ doc.add(new Field(PathIndex.LOCAL_NAME, nameStr, Field.Store.YES,
Field.Index.ANALYZED));
+ doc.add(new NumericField(PathIndex.LOCAL_NAME, Field.Store.YES,
true).setIntValue(sns));
+ doc.add(new Field(PathIndex.UUID, uuidStr, Field.Store.YES,
Field.Index.NOT_ANALYZED));
+ doc.add(new NumericField(PathIndex.DEPTH, Field.Store.YES,
true).setIntValue(path.size()));
+ getPathsWriter().addDocument(doc);
+
+ // Create the document for the content (properties) ...
+ doc = new Document();
+ doc.add(new Field(ContentIndex.UUID, uuidStr, Field.Store.YES,
Field.Index.NOT_ANALYZED));
+ String stringValue = null;
+ StringBuilder fullTextSearchValue = null;
+ for (Property property : node.getProperties()) {
+ Name name = property.getName();
+ Rule rule = rules.getRule(name);
+ if (!rule.isIncluded()) continue;
+ String nameString = stringFactory.create(name);
+ if (rule.isDate()) {
+ for (Object value : property) {
+ if (value == null) continue;
+ DateTime dateValue = dateFactory.create(value);
+ // Add a separate field for each property value ...
+ doc.add(new NumericField(nameString, rule.getStoreOption(),
true).setLongValue(dateValue.getMillisecondsInUtc()));
+ // Dates are not added to the full-text search field (since
this wouldn't make sense)
+ }
+ continue;
+ }
+ for (Object value : property) {
+ if (value == null) continue;
+ if (value instanceof Binary) {
+ // don't include binary values as individual fields but
do include them in the full-text search ...
+ // TODO : add to full-text search ...
+ continue;
+ }
+ stringValue = stringFactory.create(value);
+ // Add a separate field for each property value ...
+ doc.add(new Field(nameString, stringValue, rule.getStoreOption(),
rule.getIndexOption()));
+
+ if (rule.isFullText()) {
+ // Add this text to the full-text field ...
+ if (fullTextSearchValue == null) {
+ fullTextSearchValue = new StringBuilder();
+ } else {
+ fullTextSearchValue.append(' ');
+ }
+ fullTextSearchValue.append(stringValue);
+
+ // Also create a full-text-searchable field ...
+ String fullTextNameString = fullTextFieldName(nameString);
+ doc.add(new Field(fullTextNameString, stringValue, Store.NO,
Index.ANALYZED));
+ }
+ }
+ }
+ // Add the full-text-search field ...
+ if (fullTextSearchValue != null) {
+ doc.add(new Field(ContentIndex.FULL_TEXT,
fullTextSearchValue.toString(), Field.Store.NO,
+ Field.Index.ANALYZED));
+ }
+ getContentWriter().addDocument(doc);
+ } catch (IOException e) {
+ throw new LuceneException(e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.search.SearchProvider.Session#optimize()
+ */
+ public void optimize() {
+ try {
+ getContentWriter().optimize();
+ getPathsWriter().optimize();
+ } catch (IOException e) {
+ throw new LuceneException(e);
+
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.search.SearchProvider.Session#apply(java.lang.Iterable)
+ */
+ public int apply( Iterable<ChangeRequest> changes ) {
+ for (ChangeRequest change : changes) {
+ if (change != null) continue;
+ }
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Because this strategy uses multiple indexes, and since there's no
correlation between the documents in those indexes,
+ * we need to perform the delete in multiple steps. First, we need to perform a
query to find out which nodes exist below
+ * a certain path. Then, we need to delete those nodes from the paths index.
Finally, we need to delete the corresponding
+ * documents in the content index that represent those same nodes.
+ * </p>
+ * <p>
+ * Since we don't know how many documents there will be, we perform these
steps in batches, where each batch limits the
+ * number of results to a maximum number. We repeat batches as long as we find
more results. This approach has the
+ * advantage that we'll never bring in a large number of results, and it
allows us to delete the documents from the
+ * content node using a query.
+ * </p>
+ *
+ * @see
org.jboss.dna.graph.search.SearchProvider.Session#deleteBelow(org.jboss.dna.graph.property.Path)
+ */
+ public int deleteBelow( Path path ) {
+ assert !readOnly;
+ // Perform a query using the reader to find those nodes at/below the path
...
+ try {
+ IndexReader pathReader = getPathsReader();
+ IndexSearcher pathSearcher = new IndexSearcher(pathReader);
+ String pathStr = stringFactory.create(path) + "/";
+ PrefixQuery query = new PrefixQuery(new Term(PathIndex.PATH, pathStr));
+ int numberDeleted = 0;
+ while (true) {
+ // Execute the query and get the results ...
+ TopDocs results = pathSearcher.search(query,
SIZE_OF_DELETE_BATCHES);
+ int numResultsInBatch = results.scoreDocs.length;
+ // Walk the results, delete the doc, and add to the query that
we'll use against the content index ...
+ IndexReader contentReader = getContentReader();
+ for (ScoreDoc result : results.scoreDocs) {
+ int docId = result.doc;
+ // Find the UUID of the node ...
+ Document doc = pathReader.document(docId, UUID_FIELD_SELECTOR);
+ String uuid = doc.get(PathIndex.UUID);
+ // Delete the document from the paths index ...
+ pathReader.deleteDocument(docId);
+ // Delete the corresponding document from the content index ...
+ contentReader.deleteDocuments(new Term(ContentIndex.UUID,
uuid));
+ }
+ numberDeleted += numResultsInBatch;
+ if (numResultsInBatch < SIZE_OF_DELETE_BATCHES) break;
+ }
+ return numberDeleted;
+ } catch (FileNotFoundException e) {
+ // There are no index files yet, so nothing to delete ...
+ return 0;
+ } catch (IOException e) {
+ throw new LuceneException(e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.search.SearchProvider.Session#search(org.jboss.dna.graph.ExecutionContext,
java.lang.String,
+ * int, int, java.util.List)
+ */
+ public void search( ExecutionContext context,
+ String fullTextString,
+ int maxResults,
+ int offset,
+ List<Location> results ) {
+ assert fullTextString != null;
+ assert fullTextString.length() > 0;
+ assert offset >= 0;
+ assert maxResults > 0;
+ assert results != null;
+
+ try {
+ // Parse the full-text search and search against the 'fts' field
...
+ QueryParser parser = new QueryParser(ContentIndex.FULL_TEXT,
createAnalyzer());
+ Query query = parser.parse(fullTextString);
+ TopDocs docs = getContentSearcher().search(query, maxResults + offset);
+
+ // Collect the results ...
+ IndexReader contentReader = getContentReader();
+ IndexReader pathReader = getPathsReader();
+ IndexSearcher pathSearcher = getPathsSearcher();
+ ScoreDoc[] scoreDocs = docs.scoreDocs;
+ int numberOfResults = scoreDocs.length;
+ if (numberOfResults > offset) {
+ // There are enough results to satisfy the offset ...
+ PathFactory pathFactory =
context.getValueFactories().getPathFactory();
+ for (int i = offset, num = scoreDocs.length; i != num; ++i) {
+ ScoreDoc result = scoreDocs[i];
+ int docId = result.doc;
+ // Find the UUID of the node (this UUID might be artificial, so
we have to find the path) ...
+ Document doc = contentReader.document(docId,
UUID_FIELD_SELECTOR);
+ String uuid = doc.get(ContentIndex.UUID);
+ // Find the path for this node (is there a better way to do this
than one search per UUID?) ...
+ TopDocs pathDocs = pathSearcher.search(new TermQuery(new
Term(PathIndex.UUID, uuid)), 1);
+ if (pathDocs.scoreDocs.length < 1) {
+ // No path record found ...
+ continue;
+ }
+ Document pathDoc =
pathReader.document(pathDocs.scoreDocs[0].doc);
+ String pathString = pathDoc.get(PathIndex.PATH);
+ Path path = pathFactory.create(pathString);
+ // Now add the location ...
+ results.add(Location.create(path, UUID.fromString(uuid)));
+ }
+ }
+ } catch (ParseException e) {
+ String msg = SearchI18n.errorWhilePerformingSearch.text(workspaceName,
sourceName, fullTextString, e.getMessage());
+ throw new SearchException(fullTextString, msg, e);
+ } catch (IOException e) {
+ throw new LuceneException(e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.search.SearchProvider.Session#query(org.jboss.dna.graph.query.QueryContext,
+ * org.jboss.dna.graph.query.model.QueryCommand)
+ */
+ public QueryResults query( QueryContext queryContext,
+ QueryCommand query ) {
+ return queryEngine().execute(queryContext, query);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.search.SearchProvider.Session#commit()
+ */
+ public void commit() {
+ IOException ioError = null;
+ RuntimeException runtimeError = null;
+ if (pathsReader != null) {
+ try {
+ pathsReader.close();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ pathsReader = null;
+ }
+ }
+ if (contentReader != null) {
+ try {
+ contentReader.close();
+ } catch (IOException e) {
+ if (ioError == null) ioError = e;
+ } catch (RuntimeException e) {
+ if (runtimeError == null) runtimeError = e;
+ } finally {
+ contentReader = null;
+ }
+ }
+ if (pathsWriter != null) {
+ try {
+ pathsWriter.commit();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ try {
+ pathsWriter.close();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ pathsWriter = null;
+ }
+ }
+ }
+ if (contentWriter != null) {
+ try {
+ contentWriter.commit();
+ } catch (IOException e) {
+ if (ioError == null) ioError = e;
+ } catch (RuntimeException e) {
+ if (runtimeError == null) runtimeError = e;
+ } finally {
+ try {
+ contentWriter.close();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ contentWriter = null;
+ }
+ }
+ }
+ if (ioError != null) {
+ String msg =
SearchI18n.errorWhileCommittingIndexChanges.text(workspaceName, sourceName,
ioError.getMessage());
+ throw new LuceneException(msg, ioError);
+ }
+ if (runtimeError != null) throw runtimeError;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.search.SearchProvider.Session#rollback()
+ */
+ public void rollback() {
+ IOException ioError = null;
+ RuntimeException runtimeError = null;
+ if (pathsReader != null) {
+ try {
+ pathsReader.close();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ pathsReader = null;
+ }
+ }
+ if (contentReader != null) {
+ try {
+ contentReader.close();
+ } catch (IOException e) {
+ if (ioError == null) ioError = e;
+ } catch (RuntimeException e) {
+ if (runtimeError == null) runtimeError = e;
+ } finally {
+ contentReader = null;
+ }
+ }
+ if (pathsWriter != null) {
+ try {
+ pathsWriter.rollback();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ try {
+ pathsWriter.close();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ pathsWriter = null;
+ }
+ }
+ }
+ if (contentWriter != null) {
+ try {
+ contentWriter.rollback();
+ } catch (IOException e) {
+ if (ioError == null) ioError = e;
+ } catch (RuntimeException e) {
+ if (runtimeError == null) runtimeError = e;
+ } finally {
+ try {
+ contentWriter.close();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ contentWriter = null;
+ }
+ }
+ }
+ if (ioError != null) {
+ String msg =
SearchI18n.errorWhileRollingBackIndexChanges.text(workspaceName, sourceName,
ioError.getMessage());
+ throw new LuceneException(msg, ioError);
+ }
+ if (runtimeError != null) throw runtimeError;
+ }
+
+ protected QueryEngine createQueryProcessor() {
+ // Create the query engine ...
+ Planner planner = new CanonicalPlanner();
+ Optimizer optimizer = new RuleBasedOptimizer() {
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.optimize.RuleBasedOptimizer#populateRuleStack(java.util.LinkedList,
+ * org.jboss.dna.graph.query.plan.PlanHints)
+ */
+ @Override
+ protected void populateRuleStack( LinkedList<OptimizerRule>
ruleStack,
+ PlanHints hints ) {
+ super.populateRuleStack(ruleStack, hints);
+ // Add any custom rules here, either at the front of the stack or at
the end
+ }
+ };
+ QueryProcessor processor = new QueryProcessor() {
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.process.QueryProcessor#createAccessComponent(org.jboss.dna.graph.query.model.QueryCommand,
+ * org.jboss.dna.graph.query.QueryContext,
org.jboss.dna.graph.query.plan.PlanNode,
+ * org.jboss.dna.graph.query.QueryResults.Columns,
+ * org.jboss.dna.graph.query.process.SelectComponent.Analyzer)
+ */
+ @Override
+ protected ProcessingComponent createAccessComponent( QueryCommand
originalQuery,
+ QueryContext
context,
+ PlanNode
accessNode,
+ Columns
resultColumns,
+
org.jboss.dna.graph.query.process.SelectComponent.Analyzer analyzer ) {
+ return DualIndexSession.this.createAccessComponent(originalQuery,
+ context,
+ accessNode,
+ resultColumns,
+ analyzer);
+ }
+ };
+
+ return new QueryEngine(planner, optimizer, processor);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.search.LuceneSession#createAccessComponent(org.jboss.dna.graph.query.model.QueryCommand,
+ * org.jboss.dna.graph.query.QueryContext,
org.jboss.dna.graph.query.plan.PlanNode,
+ * org.jboss.dna.graph.query.QueryResults.Columns,
org.jboss.dna.graph.query.process.SelectComponent.Analyzer)
+ */
+ @Override
+ protected ProcessingComponent createAccessComponent( QueryCommand originalQuery,
+ QueryContext context,
+ PlanNode accessNode,
+ Columns resultColumns,
+
org.jboss.dna.graph.query.process.SelectComponent.Analyzer analyzer ) {
+ // Create a processing component for this access query ...
+ return new LuceneQueryComponent(this, originalQuery, context, resultColumns,
accessNode, analyzer, sourceName,
+ workspaceName);
+ }
+
+ /**
+ * Get the set of UUIDs for the children of the node at the given path.
+ *
+ * @param parentPath the path to the parent node; may not be null
+ * @return the UUIDs of the child nodes; never null but possibly empty
+ * @throws IOException if there is an error accessing the indexes
+ */
+ protected Set<UUID> getUuidsForChildrenOf( Path parentPath ) throws
IOException {
+ // Find the path of the parent ...
+ String stringifiedPath = pathAsString(parentPath, stringFactory);
+ // Append a '/' to the parent path, so we'll only get decendants
...
+ stringifiedPath = stringifiedPath + '/';
+
+ // Create a query to find all the nodes below the parent path ...
+ Query query = new PrefixQuery(new Term(PathIndex.PATH, stringifiedPath));
+ // Include only the children ...
+ int childrenDepth = parentPath.size() + 1;
+ Query depthQuery = NumericRangeQuery.newIntRange(PathIndex.DEPTH,
childrenDepth, childrenDepth, true, true);
+ // And combine ...
+ BooleanQuery combinedQuery = new BooleanQuery();
+ combinedQuery.add(query, Occur.MUST);
+ combinedQuery.add(depthQuery, Occur.MUST);
+ query = combinedQuery;
+
+ // Now execute and collect the UUIDs ...
+ UuidCollector uuidCollector = new UuidCollector();
+ IndexSearcher searcher = getPathsSearcher();
+ searcher.search(query, uuidCollector);
+ return uuidCollector.getUuids();
+ }
+
+ /**
+ * Get the set of UUIDs for the nodes that are descendants of the node at the
given path.
+ *
+ * @param parentPath the path to the parent node; may not be null and
<i>may not be the root node</i>
+ * @param includeParent true if the parent node should be included in the
results, or false if only the descendants should
+ * be included
+ * @return the UUIDs of the nodes; never null but possibly empty
+ * @throws IOException if there is an error accessing the indexes
+ */
+ protected Set<UUID> getUuidsForDescendantsOf( Path parentPath,
+ boolean includeParent ) throws
IOException {
+ assert !parentPath.isRoot();
+
+ // Find the path of the parent ...
+ String stringifiedPath = pathAsString(parentPath, stringFactory);
+ if (!includeParent) {
+ // Append a '/' to the parent path, and we'll only get
decendants ...
+ stringifiedPath = stringifiedPath + '/';
+ }
+
+ // Create a prefix query ...
+ Query query = new PrefixQuery(new Term(PathIndex.PATH, stringifiedPath));
+
+ // Now execute and collect the UUIDs ...
+ UuidCollector uuidCollector = new UuidCollector();
+ IndexSearcher searcher = getPathsSearcher();
+ searcher.search(query, uuidCollector);
+ return uuidCollector.getUuids();
+ }
+
+ /**
+ * Get the set containing the single UUID for the node at the given path.
+ *
+ * @param path the path to the node; may not be null
+ * @return the UUID of the supplied node; or null if the node cannot be found
+ * @throws IOException if there is an error accessing the indexes
+ */
+ protected UUID getUuidFor( Path path ) throws IOException {
+ // Create a query to find all the nodes below the parent path ...
+ IndexSearcher searcher = getPathsSearcher();
+ String stringifiedPath = pathAsString(path, stringFactory);
+ TermQuery query = new TermQuery(new Term(PathIndex.PATH, stringifiedPath));
+
+ // Now execute and collect the UUIDs ...
+ TopDocs topDocs = searcher.search(query, 1);
+ if (topDocs.totalHits == 0) return null;
+ Document pathDoc = getPathsReader().document(topDocs.scoreDocs[0].doc);
+ String uuidString = pathDoc.get(PathIndex.UUID);
+ return UUID.fromString(uuidString);
+ }
+
+ /**
+ * Utility method to create a query to find all of the documents representing
nodes with the supplied UUIDs.
+ *
+ * @param uuids the UUIDs of the nodes that are to be found; may not be null
+ * @return the query; never null
+ */
+ @Override
+ public Query findAllNodesWithUuids( Set<UUID> uuids ) {
+ if (uuids.isEmpty()) {
+ // There are no children, so return a null query ...
+ return new MatchNoneQuery();
+ }
+ if (uuids.size() == 1) {
+ UUID uuid = uuids.iterator().next();
+ if (uuid == null) return new MatchNoneQuery();
+ return new TermQuery(new Term(ContentIndex.UUID, uuid.toString()));
+ }
+ if (uuids.size() < 50) {
+ // Create an OR boolean query for all the UUIDs, since this is probably
more efficient ...
+ BooleanQuery query = new BooleanQuery();
+ for (UUID uuid : uuids) {
+ Query uuidQuery = new TermQuery(new Term(ContentIndex.UUID,
uuid.toString()));
+ query.add(uuidQuery, Occur.SHOULD);
+ }
+ return query;
+ }
+ // Returna query that will always find all of the UUIDs ...
+ return new UuidsQuery(ContentIndex.UUID, uuids,
getContext().getValueFactories().getUuidFactory());
+ }
+
+ @Override
+ public Query findAllNodesBelow( Path ancestorPath ) throws IOException {
+ if (ancestorPath.isRoot()) {
+ return new MatchAllDocsQuery();
+ }
+ Set<UUID> uuids = getUuidsForDescendantsOf(ancestorPath, false);
+ return findAllNodesWithUuids(uuids);
+ }
+
+ /**
+ * Return a query that can be used to find all of the documents that represent
nodes that are children of the node at the
+ * supplied path.
+ *
+ * @param parentPath the path of the parent node.
+ * @return the query; never null
+ * @throws IOException if there is an error finding the UUIDs of the child nodes
+ */
+ @Override
+ public Query findChildNodes( Path parentPath ) throws IOException {
+ if (parentPath.isRoot()) {
+ return new MatchAllDocsQuery();
+ }
+ Set<UUID> childUuids = getUuidsForChildrenOf(parentPath);
+ return findAllNodesWithUuids(childUuids);
+ }
+
+ /**
+ * Create a query that can be used to find the one document (or node) that exists
at the exact path supplied. This method
+ * first queries the {@link PathIndex path index} to find the UUID of the node at
the supplied path, and then returns a
+ * query that matches the UUID.
+ *
+ * @param path the path of the node
+ * @return the query; never null
+ * @throws IOException if there is an error finding the UUID for the supplied
path
+ */
+ @Override
+ public Query findNodeAt( Path path ) throws IOException {
+ UUID uuid = getUuidFor(path);
+ if (uuid == null) return null;
+ return new TermQuery(new Term(ContentIndex.UUID, uuid.toString()));
+ }
+
+ /**
+ * Create a query that can be used to find documents (or nodes) that have a field
value that satisfies the supplied LIKE
+ * expression.
+ *
+ * @param fieldName the name of the document field to search
+ * @param likeExpression the JCR like expression
+ * @return the query; never null
+ */
+ @Override
+ public Query findNodesLike( String fieldName,
+ String likeExpression ) {
+ assert likeExpression != null;
+ assert likeExpression.length() > 0;
+
+ // '%' matches 0 or more characters
+ // '_' matches any single character
+ // '\x' matches 'x'
+ // all other characters match themselves
+
+ // Wildcard queries are a better match, but they can be slow and should not
be used
+ // if the first character of the expression is a '%' or '_'
...
+ char firstChar = likeExpression.charAt(0);
+ if (firstChar != '%' && firstChar != '_') {
+ // Create a wildcard query ...
+ String expression = toWildcardExpression(likeExpression);
+ return new WildcardQuery(new Term(fieldName, expression));
+ }
+ // Create a regex query,
+ String regex = toRegularExpression(likeExpression);
+ RegexQuery query = new RegexQuery(new Term(fieldName, regex));
+ query.setRegexImplementation(new JavaUtilRegexCapabilities());
+ return query;
+ }
+
+ @Override
+ public Query findNodesWith( Length propertyLength,
+ Operator operator,
+ Object value ) {
+ assert propertyLength != null;
+ assert value != null;
+ PropertyValue propertyValue = propertyLength.getPropertyValue();
+ String field = stringFactory.create(propertyValue.getPropertyName());
+ ValueFactories factories = context.getValueFactories();
+ int length = factories.getLongFactory().create(value).intValue();
+ switch (operator) {
+ case EQUAL_TO:
+ return CompareLengthQuery.createQueryForNodesWithFieldEqualTo(length,
field, factories);
+ case NOT_EQUAL_TO:
+ return
CompareLengthQuery.createQueryForNodesWithFieldNotEqualTo(length, field, factories);
+ case GREATER_THAN:
+ return
CompareLengthQuery.createQueryForNodesWithFieldGreaterThan(length, field, factories);
+ case GREATER_THAN_OR_EQUAL_TO:
+ return
CompareLengthQuery.createQueryForNodesWithFieldGreaterThanOrEqualTo(length, field,
factories);
+ case LESS_THAN:
+ return
CompareLengthQuery.createQueryForNodesWithFieldLessThan(length, field, factories);
+ case LESS_THAN_OR_EQUAL_TO:
+ return
CompareLengthQuery.createQueryForNodesWithFieldLessThanOrEqualTo(length, field,
factories);
+ case LIKE:
+ // This is not allowed ...
+ assert false;
+ break;
+ }
+ return null;
+ }
+
+ @Override
+ public Query findNodesWith( PropertyValue propertyValue,
+ Operator operator,
+ Object value,
+ boolean caseSensitive ) {
+ String field = stringFactory.create(propertyValue.getPropertyName());
+ PropertyType valueType = PropertyType.discoverType(value);
+ ValueFactories factories = context.getValueFactories();
+ switch (valueType) {
+ case NAME:
+ case PATH:
+ case REFERENCE:
+ case URI:
+ case UUID:
+ case STRING:
+ String stringValue = stringFactory.create(value);
+ if (valueType == PropertyType.PATH) {
+ stringValue = pathAsString(pathFactory.create(value),
stringFactory);
+ }
+ if (!caseSensitive) stringValue = stringValue.toLowerCase();
+ switch (operator) {
+ case EQUAL_TO:
+ return new TermQuery(new Term(field, stringValue));
+ case NOT_EQUAL_TO:
+ Query query = new TermQuery(new Term(field, stringValue));
+ return new NotQuery(query);
+ case GREATER_THAN:
+ return
CompareStringQuery.createQueryForNodesWithFieldGreaterThan(stringValue,
+
field,
+
factories,
+
caseSensitive);
+ case GREATER_THAN_OR_EQUAL_TO:
+ return
CompareStringQuery.createQueryForNodesWithFieldGreaterThanOrEqualTo(stringValue,
+
field,
+
factories,
+
caseSensitive);
+ case LESS_THAN:
+ return
CompareStringQuery.createQueryForNodesWithFieldLessThan(stringValue,
+
field,
+
factories,
+
caseSensitive);
+ case LESS_THAN_OR_EQUAL_TO:
+ return
CompareStringQuery.createQueryForNodesWithFieldLessThanOrEqualTo(stringValue,
+
field,
+
factories,
+
caseSensitive);
+ case LIKE:
+ return findNodesLike(field, stringValue);
+ }
+ break;
+ case DATE:
+ long date = factories.getLongFactory().create(value);
+ switch (operator) {
+ case EQUAL_TO:
+ return NumericRangeQuery.newLongRange(field, date, date,
true, true);
+ case NOT_EQUAL_TO:
+ Query query = NumericRangeQuery.newLongRange(field, date,
date, true, true);
+ return new NotQuery(query);
+ case GREATER_THAN:
+ return NumericRangeQuery.newLongRange(field, date, MAX_DATE,
false, true);
+ case GREATER_THAN_OR_EQUAL_TO:
+ return NumericRangeQuery.newLongRange(field, date, MAX_DATE,
true, true);
+ case LESS_THAN:
+ return NumericRangeQuery.newLongRange(field, MIN_DATE, date,
true, false);
+ case LESS_THAN_OR_EQUAL_TO:
+ return NumericRangeQuery.newLongRange(field, MIN_DATE, date,
true, true);
+ case LIKE:
+ // This is not allowed ...
+ assert false;
+ return null;
+ }
+ break;
+ case LONG:
+ long longValue = factories.getLongFactory().create(value);
+ switch (operator) {
+ case EQUAL_TO:
+ return NumericRangeQuery.newLongRange(field, longValue,
longValue, true, true);
+ case NOT_EQUAL_TO:
+ Query query = NumericRangeQuery.newLongRange(field,
longValue, longValue, true, true);
+ return new NotQuery(query);
+ case GREATER_THAN:
+ return NumericRangeQuery.newLongRange(field, longValue,
MAX_LONG, false, true);
+ case GREATER_THAN_OR_EQUAL_TO:
+ return NumericRangeQuery.newLongRange(field, longValue,
MAX_LONG, true, true);
+ case LESS_THAN:
+ return NumericRangeQuery.newLongRange(field, MIN_LONG,
longValue, true, false);
+ case LESS_THAN_OR_EQUAL_TO:
+ return NumericRangeQuery.newLongRange(field, MIN_LONG,
longValue, true, true);
+ case LIKE:
+ // This is not allowed ...
+ assert false;
+ return null;
+ }
+ break;
+ case DECIMAL:
+ case DOUBLE:
+ double doubleValue = factories.getDoubleFactory().create(value);
+ switch (operator) {
+ case EQUAL_TO:
+ return NumericRangeQuery.newDoubleRange(field, doubleValue,
doubleValue, true, true);
+ case NOT_EQUAL_TO:
+ Query query = NumericRangeQuery.newDoubleRange(field,
doubleValue, doubleValue, true, true);
+ return new NotQuery(query);
+ case GREATER_THAN:
+ return NumericRangeQuery.newDoubleRange(field, doubleValue,
MAX_DOUBLE, false, true);
+ case GREATER_THAN_OR_EQUAL_TO:
+ return NumericRangeQuery.newDoubleRange(field, doubleValue,
MAX_DOUBLE, true, true);
+ case LESS_THAN:
+ return NumericRangeQuery.newDoubleRange(field, MIN_DOUBLE,
doubleValue, true, false);
+ case LESS_THAN_OR_EQUAL_TO:
+ return NumericRangeQuery.newDoubleRange(field, MIN_DOUBLE,
doubleValue, true, true);
+ case LIKE:
+ // This is not allowed ...
+ assert false;
+ return null;
+ }
+ break;
+ case BOOLEAN:
+ boolean booleanValue = factories.getBooleanFactory().create(value);
+ stringValue = stringFactory.create(value);
+ switch (operator) {
+ case EQUAL_TO:
+ return new TermQuery(new Term(field, stringValue));
+ case NOT_EQUAL_TO:
+ return new TermQuery(new Term(field,
stringFactory.create(!booleanValue)));
+ case GREATER_THAN:
+ if (!booleanValue) {
+ return new TermQuery(new Term(field,
stringFactory.create(true)));
+ }
+ // Can't be greater than 'true', per JCR spec
+ return new MatchNoneQuery();
+ case GREATER_THAN_OR_EQUAL_TO:
+ return new TermQuery(new Term(field,
stringFactory.create(true)));
+ case LESS_THAN:
+ if (booleanValue) {
+ return new TermQuery(new Term(field,
stringFactory.create(false)));
+ }
+ // Can't be less than 'false', per JCR spec
+ return new MatchNoneQuery();
+ case LESS_THAN_OR_EQUAL_TO:
+ return new TermQuery(new Term(field,
stringFactory.create(false)));
+ case LIKE:
+ // This is not allowed ...
+ assert false;
+ return null;
+ }
+ break;
+ case OBJECT:
+ case BINARY:
+ // This is not allowed ...
+ assert false;
+ return null;
+ }
+ return null;
+ }
+
+ @Override
+ public Query findNodesWithNumericRange( PropertyValue propertyValue,
+ Object lowerValue,
+ Object upperValue,
+ boolean includesLower,
+ boolean includesUpper ) {
+ String field = stringFactory.create(propertyValue.getPropertyName());
+ return findNodesWithNumericRange(field, lowerValue, upperValue,
includesLower, includesUpper);
+ }
+
+ @Override
+ public Query findNodesWithNumericRange( NodeDepth depth,
+ Object lowerValue,
+ Object upperValue,
+ boolean includesLower,
+ boolean includesUpper ) {
+ return findNodesWithNumericRange(PathIndex.DEPTH, lowerValue, upperValue,
includesLower, includesUpper);
+ }
+
+ protected Query findNodesWithNumericRange( String field,
+ Object lowerValue,
+ Object upperValue,
+ boolean includesLower,
+ boolean includesUpper ) {
+ PropertyType type = PropertyType.discoverType(lowerValue);
+ assert type == PropertyType.discoverType(upperValue);
+ ValueFactories factories = context.getValueFactories();
+ switch (type) {
+ case DATE:
+ long lowerDate = factories.getLongFactory().create(lowerValue);
+ long upperDate = factories.getLongFactory().create(upperValue);
+ return NumericRangeQuery.newLongRange(field, lowerDate, upperDate,
includesLower, includesUpper);
+ case LONG:
+ long lowerLong = factories.getLongFactory().create(lowerValue);
+ long upperLong = factories.getLongFactory().create(upperValue);
+ return NumericRangeQuery.newLongRange(field, lowerLong, upperLong,
includesLower, includesUpper);
+ case DECIMAL:
+ case DOUBLE:
+ double lowerDouble =
factories.getDoubleFactory().create(lowerValue);
+ double upperDouble =
factories.getDoubleFactory().create(upperValue);
+ return NumericRangeQuery.newDoubleRange(field, lowerDouble,
upperDouble, includesLower, includesUpper);
+ default:
+ // This is not allowed ...
+ assert false;
+ return null;
+ }
+ }
+
+ @Override
+ public Query findNodesWith( NodePath nodePath,
+ Operator operator,
+ Object value,
+ boolean caseSensitive ) throws IOException {
+ if (!caseSensitive) value = stringFactory.create(value).toLowerCase();
+ Path pathValue = operator != Operator.LIKE ? pathFactory.create(value) :
null;
+ Query query = null;
+ switch (operator) {
+ case EQUAL_TO:
+ return findNodeAt(pathValue);
+ case NOT_EQUAL_TO:
+ return new NotQuery(findNodeAt(pathValue));
+ case LIKE:
+ String likeExpression = stringFactory.create(value);
+ return findNodesLike(PathIndex.PATH, likeExpression);
+ case GREATER_THAN:
+ query =
ComparePathQuery.createQueryForNodesWithPathGreaterThan(pathValue,
+
PathIndex.PATH,
+
context.getValueFactories(),
+
caseSensitive);
+ break;
+ case GREATER_THAN_OR_EQUAL_TO:
+ query =
ComparePathQuery.createQueryForNodesWithPathGreaterThanOrEqualTo(pathValue,
+
PathIndex.PATH,
+
context.getValueFactories(),
+
caseSensitive);
+ break;
+ case LESS_THAN:
+ query =
ComparePathQuery.createQueryForNodesWithPathLessThan(pathValue,
+
PathIndex.PATH,
+
context.getValueFactories(),
+
caseSensitive);
+ break;
+ case LESS_THAN_OR_EQUAL_TO:
+ query =
ComparePathQuery.createQueryForNodesWithPathLessThanOrEqualTo(pathValue,
+
PathIndex.PATH,
+
context.getValueFactories(),
+
caseSensitive);
+ break;
+ }
+ // Now execute and collect the UUIDs ...
+ UuidCollector uuidCollector = new UuidCollector();
+ IndexSearcher searcher = getPathsSearcher();
+ searcher.search(query, uuidCollector);
+ return findAllNodesWithUuids(uuidCollector.getUuids());
+ }
+
+ @Override
+ public Query findNodesWith( NodeName nodeName,
+ Operator operator,
+ Object value,
+ boolean caseSensitive ) throws IOException {
+ String stringValue = stringFactory.create(value);
+ if (!caseSensitive) stringValue = stringValue.toLowerCase();
+ Path.Segment segment = operator != Operator.LIKE ?
pathFactory.createSegment(stringValue) : null;
+ int snsIndex = operator != Operator.LIKE ? segment.getIndex() : 0;
+ Query query = null;
+ switch (operator) {
+ case EQUAL_TO:
+ BooleanQuery booleanQuery = new BooleanQuery();
+ booleanQuery.add(new TermQuery(new Term(PathIndex.LOCAL_NAME,
stringValue)), Occur.MUST);
+ booleanQuery.add(NumericRangeQuery.newIntRange(PathIndex.SNS_INDEX,
snsIndex, snsIndex, true, false),
+ Occur.MUST);
+ return booleanQuery;
+ case NOT_EQUAL_TO:
+ booleanQuery = new BooleanQuery();
+ booleanQuery.add(new TermQuery(new Term(PathIndex.LOCAL_NAME,
stringValue)), Occur.MUST);
+ booleanQuery.add(NumericRangeQuery.newIntRange(PathIndex.SNS_INDEX,
snsIndex, snsIndex, true, false),
+ Occur.MUST);
+ return new NotQuery(booleanQuery);
+ case GREATER_THAN:
+ query =
CompareNameQuery.createQueryForNodesWithNameGreaterThan(segment,
+
PathIndex.LOCAL_NAME,
+
PathIndex.SNS_INDEX,
+
context.getValueFactories(),
+
caseSensitive);
+ break;
+ case GREATER_THAN_OR_EQUAL_TO:
+ query =
CompareNameQuery.createQueryForNodesWithNameGreaterThanOrEqualTo(segment,
+
PathIndex.LOCAL_NAME,
+
PathIndex.SNS_INDEX,
+
context.getValueFactories(),
+
caseSensitive);
+ break;
+ case LESS_THAN:
+ query =
CompareNameQuery.createQueryForNodesWithNameLessThan(segment,
+
PathIndex.LOCAL_NAME,
+
PathIndex.SNS_INDEX,
+
context.getValueFactories(),
+
caseSensitive);
+ break;
+ case LESS_THAN_OR_EQUAL_TO:
+ query =
CompareNameQuery.createQueryForNodesWithNameLessThanOrEqualTo(segment,
+
PathIndex.LOCAL_NAME,
+
PathIndex.SNS_INDEX,
+
context.getValueFactories(),
+
caseSensitive);
+ break;
+ case LIKE:
+ // See whether the like expression has brackets ...
+ String likeExpression = stringValue;
+ int openBracketIndex = likeExpression.indexOf('[');
+ if (openBracketIndex != -1) {
+ String localNameExpression = likeExpression.substring(0,
openBracketIndex);
+ String snsIndexExpression =
likeExpression.substring(openBracketIndex);
+ Query localNameQuery =
createLocalNameQuery(localNameExpression);
+ Query snsQuery = createSnsIndexQuery(snsIndexExpression);
+ if (localNameQuery == null) {
+ if (snsQuery == null) {
+ query = new MatchNoneQuery();
+ } else {
+ // There is just an SNS part ...
+ query = snsQuery;
+ }
+ } else {
+ // There is a local name part ...
+ if (snsQuery == null) {
+ query = localNameQuery;
+ } else {
+ // There is both a local name part and a SNS part ...
+ booleanQuery = new BooleanQuery();
+ booleanQuery.add(localNameQuery, Occur.MUST);
+ booleanQuery.add(snsQuery, Occur.MUST);
+ query = booleanQuery;
+ }
+ }
+ } else {
+ // There is no SNS expression ...
+ query = createLocalNameQuery(likeExpression);
+ }
+ assert query != null;
+ break;
+ }
+
+ // Now execute and collect the UUIDs ...
+ UuidCollector uuidCollector = new UuidCollector();
+ IndexSearcher searcher = getPathsSearcher();
+ searcher.search(query, uuidCollector);
+ return findAllNodesWithUuids(uuidCollector.getUuids());
+ }
+
+ @Override
+ public Query findNodesWith( NodeLocalName nodeName,
+ Operator operator,
+ Object value,
+ boolean caseSensitive ) throws IOException {
+ String nameValue = stringFactory.create(value);
+ Query query = null;
+ switch (operator) {
+ case LIKE:
+ String likeExpression = stringFactory.create(value);
+ return findNodesLike(PathIndex.LOCAL_NAME, likeExpression); //
already is a query with UUIDs
+ case EQUAL_TO:
+ query = new TermQuery(new Term(PathIndex.LOCAL_NAME, nameValue));
+ break;
+ case NOT_EQUAL_TO:
+ query = new NotQuery(new TermQuery(new Term(PathIndex.LOCAL_NAME,
nameValue)));
+ break;
+ case GREATER_THAN:
+ query =
CompareStringQuery.createQueryForNodesWithFieldGreaterThan(nameValue,
+
PathIndex.LOCAL_NAME,
+
context.getValueFactories(),
+
caseSensitive);
+ break;
+ case GREATER_THAN_OR_EQUAL_TO:
+ query =
CompareStringQuery.createQueryForNodesWithFieldGreaterThanOrEqualTo(nameValue,
+
PathIndex.LOCAL_NAME,
+
context.getValueFactories(),
+
caseSensitive);
+ break;
+ case LESS_THAN:
+ query =
CompareStringQuery.createQueryForNodesWithFieldLessThan(nameValue,
+
PathIndex.LOCAL_NAME,
+
context.getValueFactories(),
+
caseSensitive);
+ break;
+ case LESS_THAN_OR_EQUAL_TO:
+ query =
CompareStringQuery.createQueryForNodesWithFieldLessThanOrEqualTo(nameValue,
+
PathIndex.LOCAL_NAME,
+
context.getValueFactories(),
+
caseSensitive);
+ break;
+ }
+
+ // Now execute and collect the UUIDs ...
+ UuidCollector uuidCollector = new UuidCollector();
+ IndexSearcher searcher = getPathsSearcher();
+ searcher.search(query, uuidCollector);
+ return findAllNodesWithUuids(uuidCollector.getUuids());
+ }
+
+ @Override
+ public Query findNodesWith( NodeDepth depthConstraint,
+ Operator operator,
+ Object value ) throws IOException {
+ int depth =
context.getValueFactories().getLongFactory().create(value).intValue();
+ Query query = null;
+ switch (operator) {
+ case EQUAL_TO:
+ query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, depth, depth,
true, true);
+ break;
+ case NOT_EQUAL_TO:
+ query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, depth, depth,
true, true);
+ query = new NotQuery(query);
+ break;
+ case GREATER_THAN:
+ query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, depth,
MAX_DEPTH, false, true);
+ break;
+ case GREATER_THAN_OR_EQUAL_TO:
+ query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, depth,
MAX_DEPTH, true, true);
+ break;
+ case LESS_THAN:
+ query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, MIN_DEPTH,
depth, true, false);
+ break;
+ case LESS_THAN_OR_EQUAL_TO:
+ query = NumericRangeQuery.newIntRange(PathIndex.DEPTH, MIN_DEPTH,
depth, true, true);
+ break;
+ case LIKE:
+ // This is not allowed ...
+ return null;
+ }
+
+ // Now execute and collect the UUIDs ...
+ UuidCollector uuidCollector = new UuidCollector();
+ IndexSearcher searcher = getPathsSearcher();
+ searcher.search(query, uuidCollector);
+ return findAllNodesWithUuids(uuidCollector.getUuids());
+ }
+
+ protected Query createLocalNameQuery( String likeExpression ) {
+ if (likeExpression == null) return null;
+ likeExpression = likeExpression.trim();
+ if (likeExpression.length() == 0) return null;
+ if (likeExpression.indexOf('?') != -1 ||
likeExpression.indexOf('*') != -1) {
+ // The local name is a like ...
+ return findNodesLike(PathIndex.LOCAL_NAME, likeExpression);
+ }
+ // The local name is an exact match ...
+ return new TermQuery(new Term(PathIndex.LOCAL_NAME, likeExpression));
+ }
+
+ protected Query createSnsIndexQuery( String likeExpression ) {
+ if (likeExpression == null) return null;
+ likeExpression = likeExpression.trim();
+ if (likeExpression.length() == 0) return null;
+
+ // Remove the leading '[' ...
+ assert likeExpression.charAt(0) == '[';
+ likeExpression = likeExpression.substring(1);
+
+ // Remove the trailing ']' if it exists ...
+ int closeBracketIndex = likeExpression.indexOf(']');
+ if (closeBracketIndex != -1) {
+ likeExpression = likeExpression.substring(0, closeBracketIndex);
+ }
+ // If SNS expression contains '?' or '*' ...
+ if (likeExpression.indexOf('?') != -1 ||
likeExpression.indexOf('*') != -1) {
+ // There is a LIKE expression for the SNS ...
+ return findNodesLike(PathIndex.SNS_INDEX, likeExpression);
+ }
+ // This is not a LIKE expression but an exact value specification and should
be a number ...
+ try {
+ // This SNS is just a number ...
+ int sns = Integer.parseInt(likeExpression);
+ return NumericRangeQuery.newIntRange(PathIndex.SNS_INDEX, sns, sns, true,
false);
+ } catch (NumberFormatException e) {
+ // It's not a number but it's in the SNS field, so there will be
no results ...
+ return new MatchNoneQuery();
+ }
+ }
+
+ }
+
+ /**
+ * A {@link Collector} implementation that only captures the UUID of the documents
returned by a query. Score information is
+ * not recorded. This is often used when querying the {@link PathIndex} to collect
the UUIDs of a set of nodes satisfying some
+ * path constraint.
+ *
+ * @see DualIndexSearchProvider.DualIndexSession#findChildNodes(Path)
+ */
+ protected static class UuidCollector extends Collector {
+ private final Set<UUID> uuids = new HashSet<UUID>();
+ private String[] uuidsByDocId;
+ private int baseDocId;
+
+ protected UuidCollector() {
+ }
+
+ /**
+ * Get the UUIDs that have been collected.
+ *
+ * @return the set of UUIDs; never null
+ */
+ public Set<UUID> getUuids() {
+ return uuids;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.apache.lucene.search.Collector#acceptsDocsOutOfOrder()
+ */
+ @Override
+ public boolean acceptsDocsOutOfOrder() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.apache.lucene.search.Collector#setScorer(org.apache.lucene.search.Scorer)
+ */
+ @Override
+ public void setScorer( Scorer scorer ) {
+ // we don't care about scoring
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.apache.lucene.search.Collector#collect(int)
+ */
+ @Override
+ public void collect( int doc ) {
+ int index = doc - baseDocId;
+ assert index >= 0;
+ String uuidString = uuidsByDocId[index];
+ assert uuidString != null;
+ uuids.add(UUID.fromString(uuidString));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.apache.lucene.search.Collector#setNextReader(org.apache.lucene.index.IndexReader,
int)
+ */
+ @Override
+ public void setNextReader( IndexReader reader,
+ int docBase ) throws IOException {
+ this.uuidsByDocId = FieldCache.DEFAULT.getStrings(reader, UUID_FIELD);
+ this.baseDocId = docBase;
+ }
+ }
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexSearchProvider.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Deleted: trunk/dna-search/src/main/java/org/jboss/dna/search/IndexLayout.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/IndexLayout.java 2009-11-18
19:37:57 UTC (rev 1328)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/IndexLayout.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -1,66 +0,0 @@
-/*
- * JBoss DNA (
http://www.jboss.org/dna)
- * See the COPYRIGHT.txt file distributed with this work for information
- * regarding copyright ownership. Some portions may be licensed
- * to Red Hat, Inc. under one or more contributor license agreements.
- * See the AUTHORS.txt file in the distribution for a full listing of
- * individual contributors.
- *
- * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
- * is licensed to you under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * JBoss DNA is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.search;
-
-import java.io.IOException;
-import net.jcip.annotations.ThreadSafe;
-import org.jboss.dna.graph.ExecutionContext;
-
-/**
- * The representation of a single layout of one or more Lucene indexes.
- */
-@ThreadSafe
-public interface IndexLayout {
-
- /**
- * Create a new session to the indexes.
- *
- * @param context the execution context for which this session is to be established;
may not be null
- * @param sourceName the name of the source; may not be null
- * @param workspaceName the name of the workspace; may not be null
- * @param overwrite true if the existing indexes should be overwritten, or false if
they should be used
- * @param readOnly true if the resulting session can be optimized for use in
read-only situations, or false if the session
- * needs to allow calling the write methods
- * @return the session to the indexes; never null
- */
- IndexSession createSession( ExecutionContext context,
- String sourceName,
- String workspaceName,
- boolean overwrite,
- boolean readOnly );
-
- /**
- * Destroy the indexes for the workspace with the supplied name.
- *
- * @param context the execution context in which the destruction should be performed;
may not be null
- * @param sourceName the name of the source; may not be null
- * @param workspaceName the name of the workspace; may not be null
- * @return true if the indexes for the workspace were destroyed, or false if there
was no such workspace index
- * @throws IOException if there is a problem destroying the indexes
- */
- boolean destroyIndexes( ExecutionContext context,
- String sourceName,
- String workspaceName ) throws IOException;
-
-}
Modified: trunk/dna-search/src/main/java/org/jboss/dna/search/IndexRules.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/IndexRules.java 2009-11-18
19:37:57 UTC (rev 1328)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/IndexRules.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -56,10 +56,13 @@
*/
@Immutable
public static interface Rule {
+ /**
+ * Return whether this property should be included in the indexes.
+ *
+ * @return true if it is to be included, or false otherwise
+ */
boolean isIncluded();
- boolean isSkipped();
-
boolean isAnalyzed();
boolean isAnalyzedWithoutNorms();
@@ -131,15 +134,6 @@
/**
* {@inheritDoc}
*
- * @see org.jboss.dna.search.IndexRules.Rule#isSkipped()
- */
- public boolean isSkipped() {
- return true;
- }
-
- /**
- * {@inheritDoc}
- *
* @see org.jboss.dna.search.IndexRules.Rule#isStored()
*/
public boolean isStored() {
@@ -243,15 +237,6 @@
/**
* {@inheritDoc}
*
- * @see org.jboss.dna.search.IndexRules.Rule#isSkipped()
- */
- public boolean isSkipped() {
- return false;
- }
-
- /**
- * {@inheritDoc}
- *
* @see org.jboss.dna.search.IndexRules.Rule#isStored()
*/
public boolean isStored() {
Deleted: trunk/dna-search/src/main/java/org/jboss/dna/search/IndexSession.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/IndexSession.java 2009-11-18
19:37:57 UTC (rev 1328)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/IndexSession.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -1,162 +0,0 @@
-/*
- * JBoss DNA (
http://www.jboss.org/dna)
- * See the COPYRIGHT.txt file distributed with this work for information
- * regarding copyright ownership. Some portions may be licensed
- * to Red Hat, Inc. under one or more contributor license agreements.
- * See the AUTHORS.txt file in the distribution for a full listing of
- * individual contributors.
- *
- * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
- * is licensed to you under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * JBoss DNA is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.search;
-
-import java.io.IOException;
-import java.util.List;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.queryParser.ParseException;
-import org.apache.lucene.search.IndexSearcher;
-import org.jboss.dna.graph.ExecutionContext;
-import org.jboss.dna.graph.Location;
-import org.jboss.dna.graph.Node;
-import org.jboss.dna.graph.connector.RepositorySource;
-import org.jboss.dna.graph.property.Path;
-import org.jboss.dna.graph.query.QueryContext;
-import org.jboss.dna.graph.query.QueryResults;
-import org.jboss.dna.graph.query.model.QueryCommand;
-import org.jboss.dna.graph.query.validate.Schemata;
-import org.jboss.dna.graph.request.ChangeRequest;
-
-/**
- * A stateful session that maintains {@link IndexReader}, {@link IndexWriter} and {@link
IndexSearcher} resources to the indexes
- * of a particular source and workspace.
- */
-public interface IndexSession {
-
- /**
- * Get the name of the {@link RepositorySource repository source} for which this
session exists. A session instance will
- * always return the same name.
- *
- * @return the source name; never null
- */
- String getSourceName();
-
- /**
- * Get the name of the workspace for which this session exists. A session instance
will always return the same name.
- *
- * @return the workspace name; never null
- */
- String getWorkspaceName();
-
- /**
- * Get the execution context in which this session is operating.
- *
- * @return the execution context; never null
- */
- ExecutionContext getContext();
-
- /**
- * Return whether this session made changes to the indexed state.
- *
- * @return true if change were made, or false otherwise
- */
- boolean hasChanges();
-
- /**
- * Perform a full-text search given the supplied query.
- *
- * @param context the context in which the search should be executed; may not be
null
- * @param fullTextString the full-text query; never null or blank
- * @param maxResults the maximum number of results that are to be returned; always
positive
- * @param offset the number of initial results to skip, or 0 if the first results are
to be returned
- * @param results the list where the results should be accumulated; never null
- * @throws IOException if there is a problem indexing or using the indexes
- * @throws ParseException if there is a problem parsing the query
- */
- void search( ExecutionContext context,
- String fullTextString,
- int maxResults,
- int offset,
- List<Location> results ) throws IOException, ParseException;
-
- /**
- * Perform a query of the content. The {@link QueryCommand query} is supplied in the
form of the Abstract Query Model, with
- * the {@link Schemata} that defines the tables and views that are available to the
query, and the set of index readers (and
- * writers) that should be used.
- *
- * @param queryContext the context in which the query should be executed; may not be
null
- * @param query the query; never null
- * @return the results of the query; never null
- * @throws IOException if there is a problem indexing or using the indexes
- * @throws ParseException if there is a problem parsing the query
- */
- QueryResults query( QueryContext queryContext,
- QueryCommand query ) throws IOException, ParseException;
-
- /**
- * Index the node given the index writers. Note that implementors should simply just
use the writers to add documents to the
- * index(es), and should never call any of the writer lifecycle methods (e.g., {@link
IndexWriter#commit()},
- * {@link IndexWriter#rollback()}, etc.).
- *
- * @param node the node to be indexed; never null
- * @throws IOException if there is a problem indexing or using the writers
- */
- void index( Node node ) throws IOException;
-
- /**
- * Update the indexes to reflect the supplied changes to the graph content. Note that
implementors should simply just use the
- * writers to add documents to the index(es), and should never call any of the writer
lifecycle methods (e.g.,
- * {@link IndexWriter#commit()}, {@link IndexWriter#rollback()}, etc.).
- *
- * @param changes the set of changes to the content
- * @return the (approximate) number of nodes that were affected by the changes
- * @throws IOException if there is a problem indexing or using the writers
- */
- int apply( Iterable<ChangeRequest> changes ) throws IOException;
-
- /**
- * Remove from the index(es) all of the information pertaining to the nodes at or
below the supplied path. Note that
- * implementors should simply just use the writers to add documents to the index(es),
and should never call any of the writer
- * lifecycle methods (e.g., {@link IndexWriter#commit()}, {@link
IndexWriter#rollback()}, etc.).
- *
- * @param path the path identifying the graph content that is to be removed; never
null
- * @return the (approximate) number of nodes that were affected by the changes
- * @throws IOException if there is a problem indexing or using the writers
- */
- int deleteBelow( Path path ) throws IOException;
-
- /**
- * Optimize the indexes, if required.
- *
- * @throws IOException if there is a problem optimizing
- */
- void optimize() throws IOException;
-
- /**
- * Close this session by committing all of the changes. This session is no longer
usable after this method is called.
- *
- * @throws IOException if there is a problem committing
- */
- void commit() throws IOException;
-
- /**
- * Close this session by rolling back all of the changes that have been made. This
session is no longer usable after this
- * method is called.
- *
- * @throws IOException if there is a problem rolling back
- */
- void rollback() throws IOException;
-}
Deleted: trunk/dna-search/src/main/java/org/jboss/dna/search/KitchenSinkIndexLayout.java
===================================================================
---
trunk/dna-search/src/main/java/org/jboss/dna/search/KitchenSinkIndexLayout.java 2009-11-18
19:37:57 UTC (rev 1328)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/KitchenSinkIndexLayout.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -1,708 +0,0 @@
-/*
- * JBoss DNA (
http://www.jboss.org/dna)
- * See the COPYRIGHT.txt file distributed with this work for information
- * regarding copyright ownership. Some portions may be licensed
- * to Red Hat, Inc. under one or more contributor license agreements.
- * See the AUTHORS.txt file in the distribution for a full listing of
- * individual contributors.
- *
- * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
- * is licensed to you under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * JBoss DNA is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.search;
-
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-import java.util.UUID;
-import net.jcip.annotations.ThreadSafe;
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.FieldSelector;
-import org.apache.lucene.document.FieldSelectorResult;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.Term;
-import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.Collector;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.MatchAllDocsQuery;
-import org.apache.lucene.search.PhraseQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.Scorer;
-import org.apache.lucene.search.TermQuery;
-import org.apache.lucene.search.BooleanClause.Occur;
-import org.apache.lucene.store.Directory;
-import org.jboss.dna.common.i18n.I18n;
-import org.jboss.dna.graph.DnaLexicon;
-import org.jboss.dna.graph.ExecutionContext;
-import org.jboss.dna.graph.JcrLexicon;
-import org.jboss.dna.graph.Location;
-import org.jboss.dna.graph.property.Binary;
-import org.jboss.dna.graph.property.Name;
-import org.jboss.dna.graph.property.PropertyType;
-import org.jboss.dna.graph.property.ValueFactory;
-import org.jboss.dna.graph.query.QueryContext;
-import org.jboss.dna.graph.query.QueryResults.Columns;
-import org.jboss.dna.graph.query.model.And;
-import org.jboss.dna.graph.query.model.Between;
-import org.jboss.dna.graph.query.model.BindVariableName;
-import org.jboss.dna.graph.query.model.ChildNode;
-import org.jboss.dna.graph.query.model.Comparison;
-import org.jboss.dna.graph.query.model.Constraint;
-import org.jboss.dna.graph.query.model.DescendantNode;
-import org.jboss.dna.graph.query.model.DynamicOperand;
-import org.jboss.dna.graph.query.model.FullTextSearch;
-import org.jboss.dna.graph.query.model.FullTextSearchScore;
-import org.jboss.dna.graph.query.model.Length;
-import org.jboss.dna.graph.query.model.Literal;
-import org.jboss.dna.graph.query.model.LowerCase;
-import org.jboss.dna.graph.query.model.NodeDepth;
-import org.jboss.dna.graph.query.model.NodeLocalName;
-import org.jboss.dna.graph.query.model.NodeName;
-import org.jboss.dna.graph.query.model.NodePath;
-import org.jboss.dna.graph.query.model.Not;
-import org.jboss.dna.graph.query.model.Operator;
-import org.jboss.dna.graph.query.model.Or;
-import org.jboss.dna.graph.query.model.PropertyExistence;
-import org.jboss.dna.graph.query.model.PropertyValue;
-import org.jboss.dna.graph.query.model.QueryCommand;
-import org.jboss.dna.graph.query.model.SameNode;
-import org.jboss.dna.graph.query.model.SelectorName;
-import org.jboss.dna.graph.query.model.SetCriteria;
-import org.jboss.dna.graph.query.model.StaticOperand;
-import org.jboss.dna.graph.query.model.UpperCase;
-import org.jboss.dna.graph.query.model.Visitors;
-import org.jboss.dna.graph.query.model.FullTextSearch.NegationTerm;
-import org.jboss.dna.graph.query.plan.PlanNode;
-import org.jboss.dna.graph.query.process.AbstractAccessComponent;
-import org.jboss.dna.graph.query.process.ProcessingComponent;
-import org.jboss.dna.graph.query.process.SelectComponent;
-import org.jboss.dna.graph.query.process.SelectComponent.Analyzer;
-
-/**
- * An {@link IndexLayout} implementation that stores all content within a set of two
indexes: one for the node content and a
- * second one for paths and UUIDs.
- */
-@ThreadSafe
-public class KitchenSinkIndexLayout extends DualIndexLayout {
-
- /**
- * The default set of {@link IndexRules} used by {@link KitchenSinkIndexLayout}
instances when no rules are provided.
- */
- public static final IndexRules DEFAULT_RULES;
-
- static {
- IndexRules.Builder builder = IndexRules.createBuilder();
- // Configure the default behavior ...
- builder.defaultTo(IndexRules.INDEX | IndexRules.ANALYZE);
- // Configure the UUID properties to be just indexed (not stored, not analyzed,
not included in full-text) ...
- builder.index(JcrLexicon.UUID, DnaLexicon.UUID);
- // Configure the properties that we'll treat as dates ...
- builder.treatAsDates(JcrLexicon.CREATED, JcrLexicon.LAST_MODIFIED);
- DEFAULT_RULES = builder.build();
- }
-
- private final IndexRules rules;
- private final DirectoryConfiguration directoryConfiguration;
-
- public KitchenSinkIndexLayout( DirectoryConfiguration directoryConfiguration ) {
- this.rules = DEFAULT_RULES;
- this.directoryConfiguration = directoryConfiguration;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.search.IndexLayout#createSession(org.jboss.dna.graph.ExecutionContext,
java.lang.String,
- * java.lang.String, boolean, boolean)
- */
- public IndexSession createSession( ExecutionContext context,
- String sourceName,
- String workspaceName,
- boolean overwrite,
- boolean readOnly ) {
- Directory pathIndexDirectory = directoryConfiguration.getDirectory(workspaceName,
PATHS_INDEX_NAME);
- Directory contentIndexDirectory =
directoryConfiguration.getDirectory(workspaceName, CONTENT_INDEX_NAME);
- assert pathIndexDirectory != null;
- assert contentIndexDirectory != null;
- return new Session(context, sourceName, workspaceName, rules, pathIndexDirectory,
contentIndexDirectory, overwrite,
- readOnly);
- }
-
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.search.IndexLayout#destroyIndexes(org.jboss.dna.graph.ExecutionContext,
java.lang.String,
- * java.lang.String)
- */
- public boolean destroyIndexes( ExecutionContext context,
- String sourceName,
- String workspaceName ) {
- directoryConfiguration.destroyDirectory(workspaceName, PATHS_INDEX_NAME);
- directoryConfiguration.destroyDirectory(workspaceName, CONTENT_INDEX_NAME);
- return true;
- }
-
- protected class Session extends LuceneSession {
-
- protected Session( ExecutionContext context,
- String sourceName,
- String workspaceName,
- IndexRules rules,
- Directory pathsIndexDirectory,
- Directory contentIndexDirectory,
- boolean overwrite,
- boolean readOnly ) {
- super(context, sourceName, workspaceName, rules, pathsIndexDirectory,
contentIndexDirectory, overwrite, readOnly);
- }
-
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.search.DualIndexLayout.LuceneSession#createAccessComponent(org.jboss.dna.graph.query.model.QueryCommand,
- * org.jboss.dna.graph.query.QueryContext,
org.jboss.dna.graph.query.plan.PlanNode,
- * org.jboss.dna.graph.query.QueryResults.Columns,
org.jboss.dna.graph.query.process.SelectComponent.Analyzer)
- */
- @Override
- protected ProcessingComponent createAccessComponent( QueryCommand originalQuery,
- QueryContext context,
- PlanNode accessNode,
- Columns resultColumns,
- Analyzer analyzer ) {
- // Create a processing component for this access query ...
- return new LuceneQueryComponent(this, originalQuery, context, resultColumns,
accessNode, analyzer, sourceName,
- workspaceName);
- }
-
- }
-
- /**
- * The {@link ProcessingComponent} implementation that executes a single atomic
access query against the Lucene indexes.
- */
- protected static class LuceneQueryComponent extends AbstractAccessComponent {
- private final QueryCommand originalQuery;
- private final Session session;
- private final String sourceName;
- private final String workspaceName;
-
- protected LuceneQueryComponent( Session session,
- QueryCommand originalQuery,
- QueryContext context,
- Columns columns,
- PlanNode accessNode,
- Analyzer analyzer,
- String sourceName,
- String workspaceName ) {
- super(context, columns, accessNode);
- this.originalQuery = originalQuery;
- this.session = session;
- this.sourceName = sourceName;
- this.workspaceName = workspaceName;
- }
-
- protected String fieldNameFor( Name name ) {
- return session.stringFactory.create(name);
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
- */
- @Override
- public List<Object[]> execute() {
-
- // Some kinds of constraints are not easily pushed down to Lucene as are of a
Lucene Query, and
- // instead are applied by filtering the results. For example, a
FullTextSearchScore applies
- // to the score of the tuple, which cannot be (easily?) applied as a Query.
- //
- // Therefore, each of the AND-ed constraints of the query are evaluated
separately. After all,
- // each of the tuples returned by the planned query must satisfy all of the
AND-ed constraints.
- // Or, to put it another way, if a tuple does not satisfy one of the AND-ed
constraints, the
- // tuple should not be included in the query results.
- //
- // Logically, any AND-ed criteria that cannot be pushed down to Lucene can of
course be applied
- // as a filter on the results. Thus, each AND-ed constraint is processed to
first determine if
- // it can be represented as a Lucene query; all other AND-ed constraints must
be handled as
- // a results filter. Since most queries will likely use one or more simple
constraints AND-ed
- // together, this approach will likely work very well.
- //
- // The only hairy case is when any AND-ed constraint is actually an OR-ed
combination of multiple
- // constraints of which at least one cannot be pushed down to Lucene. In this
case, the entire
- // AND-ed constraint must be treated as a results filter (even if many of
those constraints that
- // make up the OR-ed constraint can be pushed down). Hopefully, this will not
be a common case
- // in actual queries.
-
- // For each of the AND-ed constraints ...
- Query pushDownQuery = null;
- Constraint postProcessConstraint = null;
- try {
- for (Constraint andedConstraint : this.andedConstraints) {
- // Determine if it can be represented as a Lucene query ...
- Query constraintQuery = createQuery(andedConstraint);
- if (constraintQuery != null) {
- // The AND-ed constraint _can_ be represented as a push-down
Lucene query ...
- if (pushDownQuery == null) {
- // This must be the first query ...
- pushDownQuery = constraintQuery;
- } else if (pushDownQuery instanceof BooleanQuery) {
- // We have to add the constraint query to the existing
boolean ...
- BooleanQuery booleanQuery = (BooleanQuery)pushDownQuery;
- booleanQuery.add(constraintQuery, Occur.MUST);
- } else {
- // This is the second push-down query, so create a
BooleanQuery ...
- BooleanQuery booleanQuery = new BooleanQuery();
- booleanQuery.add(pushDownQuery, Occur.MUST);
- booleanQuery.add(constraintQuery, Occur.MUST);
- pushDownQuery = booleanQuery;
- }
- } else {
- // The AND-ed constraint _cannot_ be represented as a push-down
Lucene query ...
- if (postProcessConstraint == null) {
- postProcessConstraint = andedConstraint;
- } else {
- postProcessConstraint = new And(postProcessConstraint,
andedConstraint);
- }
- }
- }
- } catch (IOException e) {
- // There was a error working with the constraints (such as a
ValueFormatException) ...
- QueryContext context = getContext();
- I18n msg = SearchI18n.errorWhilePerformingQuery;
- String origQueryString = Visitors.readable(originalQuery,
context.getExecutionContext());
- context.getProblems().addError(e, msg, origQueryString, workspaceName,
sourceName, e.getMessage());
- return emptyTuples();
- } catch (RuntimeException e) {
- // There was a error working with the constraints (such as a
ValueFormatException) ...
- QueryContext context = getContext();
- I18n msg = SearchI18n.errorWhilePerformingQuery;
- String origQueryString = Visitors.readable(originalQuery,
context.getExecutionContext());
- context.getProblems().addError(e, msg, origQueryString, workspaceName,
sourceName, e.getMessage());
- return emptyTuples();
- }
-
- if (pushDownQuery == null) {
- // There are no constraints that can be pushed down, so return _all_ the
nodes ...
- pushDownQuery = new MatchAllDocsQuery();
- }
-
- // Get the results from Lucene ...
- List<Object[]> tuples = null;
- final Columns columns = getColumns();
- final QueryContext context = getContext();
- final ExecutionContext execContext = context.getExecutionContext();
- try {
- // Execute the query against the content indexes ...
- IndexSearcher searcher = session.getContentSearcher();
- TupleCollector collector = new TupleCollector(columns,
execContext.getValueFactories().getUuidFactory());
- searcher.search(pushDownQuery, collector);
- tuples = collector.getTuples();
- } catch (IOException e) {
- // There was a problem executing the Lucene query ...
- I18n msg = SearchI18n.errorWhilePerformingLuceneQuery;
- String origQueryString = Visitors.readable(originalQuery, execContext);
- context.getProblems().addError(e, msg, pushDownQuery, origQueryString,
workspaceName, sourceName, e.getMessage());
- return emptyTuples();
- }
-
- if (postProcessConstraint != null && !tuples.isEmpty()) {
- // Create a delegate processing component that will return the tuples
we've already found ...
- final List<Object[]> allTuples = tuples;
- ProcessingComponent tuplesProcessor = new ProcessingComponent(context,
columns) {
- @Override
- public List<Object[]> execute() {
- return allTuples;
- }
- };
- // Create a processing component that will apply these constraints to the
tuples we already found ...
- return new SelectComponent(tuplesProcessor, postProcessConstraint,
context.getVariables()).execute();
- }
- return tuples;
- }
-
- protected Query createQuery( Constraint constraint ) throws IOException {
- if (constraint instanceof And) {
- And and = (And)constraint;
- Query leftQuery = createQuery(and.getLeft());
- Query rightQuery = createQuery(and.getRight());
- if (leftQuery == null || rightQuery == null) return null;
- BooleanQuery booleanQuery = new BooleanQuery();
- booleanQuery.add(createQuery(and.getLeft()), Occur.MUST);
- booleanQuery.add(createQuery(and.getRight()), Occur.MUST);
- return booleanQuery;
- }
- if (constraint instanceof Or) {
- Or or = (Or)constraint;
- Query leftQuery = createQuery(or.getLeft());
- Query rightQuery = createQuery(or.getRight());
- if (leftQuery == null) {
- return rightQuery != null ? rightQuery : null;
- } else if (rightQuery == null) {
- return leftQuery;
- }
- BooleanQuery booleanQuery = new BooleanQuery();
- booleanQuery.add(createQuery(or.getLeft()), Occur.SHOULD);
- booleanQuery.add(createQuery(or.getRight()), Occur.SHOULD);
- return booleanQuery;
- }
- if (constraint instanceof Not) {
- Not not = (Not)constraint;
- Query notted = createQuery(not.getConstraint());
- if (notted == null) return new MatchAllDocsQuery();
- }
- if (constraint instanceof SetCriteria) {
- SetCriteria setCriteria = (SetCriteria)constraint;
- DynamicOperand left = setCriteria.getLeftOperand();
- int numRightOperands = setCriteria.getRightOperands().size();
- assert numRightOperands > 0;
- if (numRightOperands == 1) {
- return createQuery(left, Operator.EQUAL_TO,
setCriteria.getRightOperands().iterator().next());
- }
- BooleanQuery setQuery = new BooleanQuery();
- for (StaticOperand right : setCriteria.getRightOperands()) {
- Query rightQuery = createQuery(left, Operator.EQUAL_TO, right);
- if (rightQuery == null) return null;
- setQuery.add(rightQuery, Occur.SHOULD);
- }
- return setQuery;
- }
- if (constraint instanceof PropertyExistence) {
- PropertyExistence existence = (PropertyExistence)constraint;
- return createQuery(existence.getSelectorName(),
existence.getPropertyName());
- }
- if (constraint instanceof Between) {
- Between between = (Between)constraint;
- return createQuery(between);
- }
- if (constraint instanceof Comparison) {
- Comparison comparison = (Comparison)constraint;
- return createQuery(comparison.getOperand1(), comparison.getOperator(),
comparison.getOperand2());
- }
- if (constraint instanceof FullTextSearch) {
- FullTextSearch search = (FullTextSearch)constraint;
- String fieldName = ContentIndex.FULL_TEXT;
- Name propertyName = search.getPropertyName();
- if (propertyName != null) {
- fieldName = session.fullTextFieldName(fieldNameFor(propertyName));
- }
- return createQuery(fieldName, search.getTerm());
- }
- try {
- if (constraint instanceof SameNode) {
- SameNode sameNode = (SameNode)constraint;
- return session.findNodeAt(sameNode.getPath());
- }
- if (constraint instanceof ChildNode) {
- ChildNode childNode = (ChildNode)constraint;
- return session.findChildNodes(childNode.getParentPath());
- }
- if (constraint instanceof DescendantNode) {
- DescendantNode descendantNode = (DescendantNode)constraint;
- return session.findAllNodesBelow(descendantNode.getAncestorPath());
- }
- } catch (IOException e) {
- I18n msg = SearchI18n.errorWhilePerformingQuery;
- getContext().getProblems().addError(e,
- msg,
- Visitors.readable(originalQuery),
- workspaceName,
- sourceName,
- e.getMessage());
- return null;
- }
- // Should not get here ...
- assert false;
- return null;
- }
-
- protected Query createQuery( DynamicOperand left,
- Operator operator,
- StaticOperand right ) throws IOException {
- return createQuery(left, operator, right, true);
- }
-
- protected Query createQuery( DynamicOperand left,
- Operator operator,
- StaticOperand right,
- boolean caseSensitive ) throws IOException {
- // Handle the static operand ...
- Object value = createOperand(right, caseSensitive);
- assert value != null;
-
- // Address the dynamic operand ...
- if (left instanceof FullTextSearchScore) {
- // This can only be represented as a filter ...
- return null;
- } else if (left instanceof PropertyValue) {
- return session.findNodesWith((PropertyValue)left, operator, value,
caseSensitive);
- } else if (left instanceof Length) {
- return session.findNodesWith((Length)left, operator, right);
- } else if (left instanceof LowerCase) {
- LowerCase lowercase = (LowerCase)left;
- return createQuery(lowercase.getOperand(), operator, right, false);
- } else if (left instanceof UpperCase) {
- UpperCase lowercase = (UpperCase)left;
- return createQuery(lowercase.getOperand(), operator, right, false);
- } else if (left instanceof NodeDepth) {
- assert operator != Operator.LIKE;
- // Could be represented as a result filter, but let's do this now
...
- return session.findNodesWith((NodeDepth)left, operator, value);
- } else if (left instanceof NodePath) {
- return session.findNodesWith((NodePath)left, operator, value,
caseSensitive);
- } else if (left instanceof NodeName) {
- return session.findNodesWith((NodeName)left, operator, value,
caseSensitive);
- } else if (left instanceof NodeLocalName) {
- return session.findNodesWith((NodeLocalName)left, operator, value,
caseSensitive);
- } else {
- assert false;
- return null;
- }
- }
-
- protected Object createOperand( StaticOperand operand,
- boolean caseSensitive ) {
- Object value = null;
- if (operand instanceof Literal) {
- Literal literal = (Literal)operand;
- value = literal.getValue();
- if (!caseSensitive) value = lowerCase(value);
- } else if (operand instanceof BindVariableName) {
- BindVariableName variable = (BindVariableName)operand;
- String variableName = variable.getVariableName();
- value = getContext().getVariables().get(variableName);
- if (!caseSensitive) value = lowerCase(value);
- } else {
- assert false;
- }
- return value;
- }
-
- protected Query createQuery( DynamicOperand left,
- StaticOperand lower,
- StaticOperand upper,
- boolean includesLower,
- boolean includesUpper,
- boolean caseSensitive ) throws IOException {
- // Handle the static operands ...
- Object lowerValue = createOperand(lower, caseSensitive);
- Object upperValue = createOperand(upper, caseSensitive);
- assert lowerValue != null;
- assert upperValue != null;
-
- // Only in the case of a PropertyValue and Depth will we need to do something
special ...
- if (left instanceof NodeDepth) {
- return session.findNodesWithNumericRange((NodeDepth)left, lowerValue,
upperValue, includesLower, includesUpper);
- } else if (left instanceof PropertyValue) {
- PropertyType lowerType = PropertyType.discoverType(lowerValue);
- PropertyType upperType = PropertyType.discoverType(upperValue);
- if (upperType == lowerType) {
- switch (upperType) {
- case DATE:
- case LONG:
- case DOUBLE:
- case DECIMAL:
- return
session.findNodesWithNumericRange((PropertyValue)left,
- lowerValue,
- upperValue,
- includesLower,
- includesUpper);
- default:
- // continue on and handle as boolean query ...
- }
- }
- }
-
- // Otherwise, just create a boolean query ...
- BooleanQuery query = new BooleanQuery();
- Operator lowerOp = includesLower ? Operator.GREATER_THAN_OR_EQUAL_TO :
Operator.GREATER_THAN;
- Operator upperOp = includesUpper ? Operator.LESS_THAN_OR_EQUAL_TO :
Operator.LESS_THAN;
- Query lowerQuery = createQuery(left, lowerOp, lower, caseSensitive);
- Query upperQuery = createQuery(left, upperOp, upper, caseSensitive);
- if (lowerQuery == null || upperQuery == null) return null;
- query.add(lowerQuery, Occur.MUST);
- query.add(upperQuery, Occur.MUST);
- return query;
- }
-
- protected Object lowerCase( Object value ) {
- if (value instanceof String) {
- return ((String)value).toLowerCase();
- }
- assert !(value instanceof Binary);
- ValueFactory<String> stringFactory =
getContext().getExecutionContext().getValueFactories().getStringFactory();
- ValueFactory<?> valueFactory =
getContext().getExecutionContext().getValueFactories().getValueFactory(value);
- return valueFactory.create(stringFactory.create(value).toLowerCase());
- }
-
- protected Query createQuery( SelectorName selectorName,
- Name propertyName ) {
- Term term = new Term(fieldNameFor(propertyName));
- return new TermQuery(term);
- }
-
- protected Query createQuery( String fieldName,
- FullTextSearch.Term term ) {
- if (term instanceof FullTextSearch.Conjunction) {
- FullTextSearch.Conjunction conjunction =
(FullTextSearch.Conjunction)term;
- BooleanQuery query = new BooleanQuery();
- for (FullTextSearch.Term nested : conjunction) {
- if (nested instanceof NegationTerm) {
- query.add(createQuery(fieldName,
((NegationTerm)nested).getNegatedTerm()), Occur.MUST_NOT);
- } else {
- query.add(createQuery(fieldName, nested), Occur.MUST);
- }
- }
- return query;
- }
- if (term instanceof FullTextSearch.Disjunction) {
- FullTextSearch.Disjunction disjunction =
(FullTextSearch.Disjunction)term;
- BooleanQuery query = new BooleanQuery();
- for (FullTextSearch.Term nested : disjunction) {
- if (nested instanceof NegationTerm) {
- query.add(createQuery(fieldName,
((NegationTerm)nested).getNegatedTerm()), Occur.MUST_NOT);
- } else {
- query.add(createQuery(fieldName, nested), Occur.SHOULD);
- }
- }
- return query;
- }
- if (term instanceof FullTextSearch.SimpleTerm) {
- FullTextSearch.SimpleTerm simple = (FullTextSearch.SimpleTerm)term;
- if (simple.isQuotingRequired()) {
- PhraseQuery query = new PhraseQuery();
- query.setSlop(0); // terms must be adjacent
- for (String value : simple.getValues()) {
- query.add(new Term(fieldName, value));
- }
- return query;
- }
- return new TermQuery(new Term(fieldName, simple.getValue()));
- }
- // Should not get here ...
- assert false;
- return null;
- }
- }
-
- /**
- * This collector is responsible for loading the value for each of the columns into
each tuple array.
- */
- protected static class TupleCollector extends Collector {
- private final LinkedList<Object[]> tuples = new
LinkedList<Object[]>();
- private final Columns columns;
- private final int numValues;
- private final boolean recordScore;
- private final int scoreIndex;
- private final FieldSelector fieldSelector;
- private final int locationIndex;
- private final ValueFactory<UUID> uuidFactory;
- private Scorer scorer;
- private IndexReader currentReader;
- private int docOffset;
-
- protected TupleCollector( Columns columns,
- ValueFactory<UUID> uuidFactory ) {
- this.columns = columns;
- this.uuidFactory = uuidFactory;
- assert this.columns != null;
- assert this.uuidFactory != null;
- this.numValues = this.columns.getTupleSize();
- assert this.numValues >= 0;
- assert this.columns.getSelectorNames().size() == 1;
- final String selectorName = this.columns.getSelectorNames().get(0);
- this.locationIndex = this.columns.getLocationIndex(selectorName);
- this.recordScore = this.columns.hasFullTextSearchScores();
- this.scoreIndex = this.recordScore ?
this.columns.getFullTextSearchScoreIndexFor(selectorName) : -1;
- final Set<String> columnNames = new
HashSet<String>(this.columns.getColumnNames());
- columnNames.add(ContentIndex.UUID); // add the UUID, which we'll put into
the Location ...
- this.fieldSelector = new FieldSelector() {
- private static final long serialVersionUID = 1L;
-
- public FieldSelectorResult accept( String fieldName ) {
- return columnNames.contains(fieldName) ? FieldSelectorResult.LOAD :
FieldSelectorResult.NO_LOAD;
- }
- };
- }
-
- /**
- * @return tuples
- */
- public LinkedList<Object[]> getTuples() {
- return tuples;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.apache.lucene.search.Collector#acceptsDocsOutOfOrder()
- */
- @Override
- public boolean acceptsDocsOutOfOrder() {
- return true;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see
org.apache.lucene.search.Collector#setNextReader(org.apache.lucene.index.IndexReader,
int)
- */
- @Override
- public void setNextReader( IndexReader reader,
- int docBase ) {
- this.currentReader = reader;
- this.docOffset = docBase;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see
org.apache.lucene.search.Collector#setScorer(org.apache.lucene.search.Scorer)
- */
- @Override
- public void setScorer( Scorer scorer ) {
- this.scorer = scorer;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.apache.lucene.search.Collector#collect(int)
- */
- @Override
- public void collect( int doc ) throws IOException {
- int docId = doc + docOffset;
- Object[] tuple = new Object[numValues];
- Document document = currentReader.document(docId, fieldSelector);
- for (String columnName : columns.getColumnNames()) {
- int index = columns.getColumnIndexForName(columnName);
- // We just need to retrieve the first value if there is more than one
...
- tuple[index] = document.get(columnName);
- }
-
- // Set the score column if required ...
- if (recordScore) {
- assert scorer != null;
- tuple[scoreIndex] = scorer.score();
- }
-
- // Load the UUID into a Location object ...
- UUID uuid = uuidFactory.create(document.get(ContentIndex.UUID));
- tuple[locationIndex] = Location.create(uuid);
- tuples.add(tuple);
- }
- }
-
-}
Copied: trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneConfiguration.java (from
rev 1328,
trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfiguration.java)
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneConfiguration.java
(rev 0)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneConfiguration.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -0,0 +1,60 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.search;
+
+import net.jcip.annotations.ThreadSafe;
+import org.apache.lucene.store.Directory;
+import org.jboss.dna.graph.search.SearchEngineException;
+
+/**
+ * Interface used to obtain the Lucene {@link Directory} instance that should be used for
a workspace given the name of the
+ * workspace. There are several implementations (see {@link LuceneConfigurations}), but
custom implementations can always be
+ * used.
+ */
+@ThreadSafe
+public interface LuceneConfiguration {
+ /**
+ * Get the {@link Directory} that should be used for the workspace with the supplied
name.
+ *
+ * @param workspaceName the workspace name
+ * @param indexName the name of the index to be created
+ * @return the directory; never null
+ * @throws IllegalArgumentException if the workspace name is null
+ * @throws SearchEngineException if there is a problem creating the directory
+ */
+ Directory getDirectory( String workspaceName,
+ String indexName ) throws SearchEngineException;
+
+ /**
+ * Destroy the {@link Directory} that is used for the workspace with the supplied
name.
+ *
+ * @param workspaceName the workspace name
+ * @param indexName the name of the index to be created
+ * @return true if the directory existed and was destroyed, or false if the directory
didn't exist
+ * @throws IllegalArgumentException if the workspace name is null
+ * @throws SearchEngineException if there is a problem creating the directory
+ */
+ boolean destroyDirectory( String workspaceName,
+ String indexName ) throws SearchEngineException;
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneConfiguration.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Copied: trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneConfigurations.java
(from rev 1328,
trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfigurations.java)
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneConfigurations.java
(rev 0)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneConfigurations.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -0,0 +1,427 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.search;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+import net.jcip.annotations.Immutable;
+import net.jcip.annotations.ThreadSafe;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.store.LockFactory;
+import org.apache.lucene.store.RAMDirectory;
+import org.jboss.dna.common.i18n.I18n;
+import org.jboss.dna.common.text.NoOpEncoder;
+import org.jboss.dna.common.text.TextEncoder;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.FileUtil;
+import org.jboss.dna.common.util.HashCode;
+import org.jboss.dna.graph.search.SearchEngineException;
+
+/**
+ * A family of {@link LuceneConfiguration} implementations.
+ */
+public class LuceneConfigurations {
+
+ /**
+ * Return a new {@link LuceneConfiguration} that creates in-memory directories.
+ *
+ * @return the new directory configuration; never null
+ */
+ public static final LuceneConfiguration inMemory() {
+ return new RamDirectoryFactory();
+ }
+
+ /**
+ * Return a new {@link LuceneConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note that
this has ramifications on the allowable
+ * workspace names.
+ *
+ * @param parent the parent folder
+ * @return the new directory configuration; never null
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ public static final LuceneConfiguration using( File parent ) {
+ return new FileSystemDirectoryFromNameFactory(parent);
+ }
+
+ /**
+ * Return a new {@link LuceneConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note that
this has ramifications on the allowable
+ * workspace names.
+ *
+ * @param parent the parent folder
+ * @param lockFactory the lock factory; may be null
+ * @return the new directory configuration; never null
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ public static final LuceneConfiguration using( File parent,
+ LockFactory lockFactory ) {
+ return new FileSystemDirectoryFromNameFactory(parent, lockFactory);
+ }
+
+ /**
+ * Return a new {@link LuceneConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note that
this has ramifications on the allowable
+ * workspace names.
+ *
+ * @param parent the parent folder
+ * @param workspaceNameEncoder the encoder that should be used for encoding the
workspace name into a directory name
+ * @param indexNameEncoder the encoder that should be used for encoding the index
name into a directory name
+ * @return the new directory configuration; never null
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ public static final LuceneConfiguration using( File parent,
+ TextEncoder workspaceNameEncoder,
+ TextEncoder indexNameEncoder ) {
+ return new FileSystemDirectoryFromNameFactory(parent, workspaceNameEncoder,
indexNameEncoder);
+ }
+
+ /**
+ * Return a new {@link LuceneConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note that
this has ramifications on the allowable
+ * workspace names.
+ *
+ * @param parent the parent folder
+ * @param lockFactory the lock factory; may be null
+ * @param workspaceNameEncoder the encoder that should be used for encoding the
workspace name into a directory name
+ * @param indexNameEncoder the encoder that should be used for encoding the index
name into a directory name
+ * @return the new directory configuration; never null
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ public static final LuceneConfiguration using( File parent,
+ LockFactory lockFactory,
+ TextEncoder workspaceNameEncoder,
+ TextEncoder indexNameEncoder ) {
+ return new FileSystemDirectoryFromNameFactory(parent, lockFactory,
workspaceNameEncoder, indexNameEncoder);
+ }
+
+ /**
+ * A {@link LuceneConfiguration} implementation that creates {@link Directory}
instances of the supplied type for each
+ * workspace and pools the results, ensuring that the same {@link Directory} instance
is always returned for the same
+ * workspace name.
+ *
+ * @param <DirectoryType> the concrete type of the directory
+ */
+ @ThreadSafe
+ protected static abstract class PoolingDirectoryFactory<DirectoryType extends
Directory> implements LuceneConfiguration {
+ private final ConcurrentHashMap<IndexId, DirectoryType> directories = new
ConcurrentHashMap<IndexId, DirectoryType>();
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.LuceneConfiguration#getDirectory(java.lang.String,
java.lang.String)
+ */
+ public Directory getDirectory( String workspaceName,
+ String indexName ) throws SearchEngineException {
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ IndexId id = new IndexId(workspaceName, indexName);
+ DirectoryType result = directories.get(id);
+ if (result == null) {
+ DirectoryType newDirectory = createDirectory(workspaceName, indexName);
+ result = directories.putIfAbsent(id, newDirectory);
+ if (result == null) result = newDirectory;
+ }
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.search.LuceneConfiguration#destroyDirectory(java.lang.String,
java.lang.String)
+ */
+ public boolean destroyDirectory( String workspaceName,
+ String indexName ) throws SearchEngineException
{
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ IndexId id = new IndexId(workspaceName, indexName);
+ DirectoryType result = directories.remove(id);
+ return result != null ? doDestroy(result) : false;
+ }
+
+ /**
+ * Method implemented by subclasses to create a new Directory implementation.
+ *
+ * @param workspaceName the name of the workspace for which the {@link Directory}
is to be created; never null
+ * @param indexName the name of the index to be created
+ * @return the new directory; may not be null
+ * @throws SearchEngineException if there is a problem creating the directory
+ */
+ protected abstract DirectoryType createDirectory( String workspaceName,
+ String indexName ) throws
SearchEngineException;
+
+ protected abstract boolean doDestroy( DirectoryType directory ) throws
SearchEngineException;
+ }
+
+ /**
+ * A {@link LuceneConfiguration} implementation that creates {@link RAMDirectory}
instances for each workspace and index
+ * name. Each factory instance maintains a pool of {@link RAMDirectory} instances,
ensuring that the same {@link RAMDirectory}
+ * is always returned for the same workspace name.
+ */
+ @ThreadSafe
+ public static class RamDirectoryFactory extends
PoolingDirectoryFactory<RAMDirectory> {
+ protected RamDirectoryFactory() {
+ }
+
+ @Override
+ protected RAMDirectory createDirectory( String workspaceName,
+ String indexName ) {
+ return new RAMDirectory();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.search.LuceneConfigurations.PoolingDirectoryFactory#doDestroy(org.apache.lucene.store.Directory)
+ */
+ @Override
+ protected boolean doDestroy( RAMDirectory directory ) throws
SearchEngineException {
+ return directory != null;
+ }
+ }
+
+ /**
+ * A {@link LuceneConfiguration} implementation that creates {@link FSDirectory}
instances for each workspace and index
+ * name. This factory is created with a parent directory under which all workspace
and index directories are created.
+ * <p>
+ * This uses the supplied encoders to translate the workspace and index names into
valid directory names. By default, no
+ * encoding is performed, meaning that the workspace and index names are used
explicitly as directory names. This default
+ * behavior, then, means that not all values of workspace names or index names will
work. If you want to be sure that all
+ * workspace names work, supply an encoder for the workspace names. (Index names are
currently such that they will always be
+ * valid directory names, but you can always supply an encoder if you'd like.)
+ * </p>
+ */
+ public static class FileSystemDirectoryFromNameFactory extends
PoolingDirectoryFactory<FSDirectory> {
+ private final File parentFile;
+ private final LockFactory lockFactory;
+ private final TextEncoder workspaceNameEncoder;
+ private final TextEncoder indexNameEncoder;
+
+ /**
+ * Create a new {@link LuceneConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note
that this has ramifications on the
+ * allowable workspace names.
+ *
+ * @param parent the parent folder
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ protected FileSystemDirectoryFromNameFactory( File parent ) {
+ this(parent, null, null, null);
+ }
+
+ /**
+ * Create a new {@link LuceneConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note
that this has ramifications on the
+ * allowable workspace names.
+ *
+ * @param parent the parent folder
+ * @param lockFactory the lock factory; may be null
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ protected FileSystemDirectoryFromNameFactory( File parent,
+ LockFactory lockFactory ) {
+ this(parent, lockFactory, null, null);
+ }
+
+ /**
+ * Create a new {@link LuceneConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note
that this has ramifications on the
+ * allowable workspace names.
+ *
+ * @param parent the parent folder
+ * @param workspaceNameEncoder the encoder that should be used for encoding the
workspace name into a directory name
+ * @param indexNameEncoder the encoder that should be used for encoding the index
name into a directory name
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ protected FileSystemDirectoryFromNameFactory( File parent,
+ TextEncoder workspaceNameEncoder,
+ TextEncoder indexNameEncoder ) {
+ this(parent, null, workspaceNameEncoder, indexNameEncoder);
+ }
+
+ /**
+ * Create a new {@link LuceneConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note
that this has ramifications on the
+ * allowable workspace names.
+ *
+ * @param parent the parent folder
+ * @param lockFactory the lock factory; may be null
+ * @param workspaceNameEncoder the encoder that should be used for encoding the
workspace name into a directory name
+ * @param indexNameEncoder the encoder that should be used for encoding the index
name into a directory name
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ protected FileSystemDirectoryFromNameFactory( File parent,
+ LockFactory lockFactory,
+ TextEncoder workspaceNameEncoder,
+ TextEncoder indexNameEncoder ) {
+ CheckArg.isNotNull(parent, "parent");
+ this.parentFile = parent;
+ this.lockFactory = lockFactory;
+ this.workspaceNameEncoder = workspaceNameEncoder != null ?
workspaceNameEncoder : new NoOpEncoder();
+ this.indexNameEncoder = indexNameEncoder != null ? indexNameEncoder : new
NoOpEncoder();
+ }
+
+ @Override
+ protected FSDirectory createDirectory( String workspaceName,
+ String indexName ) {
+ File workspaceFile = new File(parentFile,
workspaceNameEncoder.encode(workspaceName));
+ if (!workspaceFile.exists()) {
+ workspaceFile.mkdirs();
+ } else {
+ if (!workspaceFile.isDirectory()) {
+ I18n msg = SearchI18n.locationForIndexesIsNotDirectory;
+ throw new
SearchEngineException(msg.text(workspaceFile.getAbsolutePath(), workspaceName));
+ }
+ if (!workspaceFile.canRead()) {
+ I18n msg = SearchI18n.locationForIndexesCannotBeRead;
+ throw new
SearchEngineException(msg.text(workspaceFile.getAbsolutePath(), workspaceName));
+ }
+ if (!workspaceFile.canWrite()) {
+ I18n msg = SearchI18n.locationForIndexesCannotBeWritten;
+ throw new
SearchEngineException(msg.text(workspaceFile.getAbsolutePath(), workspaceName));
+ }
+ }
+ File directory = workspaceFile;
+ if (indexName != null) {
+ File indexFile = new File(workspaceFile,
indexNameEncoder.encode(indexName));
+ if (!indexFile.exists()) {
+ indexFile.mkdirs();
+ } else {
+ if (!indexFile.isDirectory()) {
+ I18n msg = SearchI18n.locationForIndexesIsNotDirectory;
+ throw new
SearchEngineException(msg.text(indexFile.getAbsolutePath(), workspaceName));
+ }
+ if (!indexFile.canRead()) {
+ I18n msg = SearchI18n.locationForIndexesCannotBeRead;
+ throw new
SearchEngineException(msg.text(indexFile.getAbsolutePath(), workspaceName));
+ }
+ if (!indexFile.canWrite()) {
+ I18n msg = SearchI18n.locationForIndexesCannotBeWritten;
+ throw new
SearchEngineException(msg.text(indexFile.getAbsolutePath(), workspaceName));
+ }
+ }
+ directory = indexFile;
+ }
+ try {
+ return create(directory, lockFactory);
+ } catch (IOException e) {
+ throw new SearchEngineException(e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.search.LuceneConfigurations.PoolingDirectoryFactory#doDestroy(org.apache.lucene.store.Directory)
+ */
+ @Override
+ protected boolean doDestroy( FSDirectory directory ) throws SearchEngineException
{
+ File file = directory.getFile();
+ if (file.exists()) {
+ return FileUtil.delete(file);
+ }
+ return false;
+ }
+
+ /**
+ * Override this method to define which subclass of {@link FSDirectory} should be
created.
+ *
+ * @param directory the file system directory; never null
+ * @param lockFactory the lock factory; may be null
+ * @return the {@link FSDirectory} instance
+ * @throws IOException if there is a problem creating the FSDirectory instance
+ */
+ protected FSDirectory create( File directory,
+ LockFactory lockFactory ) throws IOException {
+ return FSDirectory.open(directory, lockFactory);
+ }
+ }
+
+ @Immutable
+ protected static final class IndexId {
+ private final String workspaceName;
+ private final String indexName;
+ private final int hc;
+
+ protected IndexId( String workspaceName,
+ String indexName ) {
+ assert workspaceName != null;
+ this.workspaceName = workspaceName;
+ this.indexName = indexName;
+ this.hc = HashCode.compute(this.workspaceName, this.indexName);
+ }
+
+ /**
+ * @return indexName
+ */
+ public String getIndexName() {
+ return indexName;
+ }
+
+ /**
+ * @return workspaceName
+ */
+ public String getWorkspaceName() {
+ return workspaceName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return hc;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof IndexId) {
+ IndexId that = (IndexId)obj;
+ if (this.hashCode() != that.hashCode()) return false;
+ if (!this.workspaceName.equals(that.workspaceName)) return false;
+ if (!this.indexName.equals(that.indexName)) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return indexName != null ? workspaceName + "/" + this.indexName :
this.workspaceName;
+ }
+ }
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneConfigurations.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Copied: trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneException.java (from rev
1328, trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngineException.java)
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneException.java
(rev 0)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneException.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -0,0 +1,74 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.jboss.dna.search;
+
+/**
+ * A {@link RuntimeException runtime exception} representing a problem operating against
Lucene.
+ */
+public class LuceneException extends RuntimeException {
+
+ /**
+ */
+ private static final long serialVersionUID = 8281373010920861138L;
+
+ /**
+ * Construct a system failure exception with no message.
+ */
+ public LuceneException() {
+ }
+
+ /**
+ * Construct a system failure exception with a single message.
+ *
+ * @param message the message describing the failure
+ */
+ public LuceneException( String message ) {
+ super(message);
+
+ }
+
+ /**
+ * Construct a system failure exception with another exception that is the cause of
the failure.
+ *
+ * @param cause the original cause of the failure
+ */
+ public LuceneException( Throwable cause ) {
+ super(cause);
+
+ }
+
+ /**
+ * Construct a system failure exception with a single message and another exception
that is the cause of the failure.
+ *
+ * @param message the message describing the failure
+ * @param cause the original cause of the failure
+ */
+ public LuceneException( String message,
+ Throwable cause ) {
+ super(message, cause);
+
+ }
+
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneException.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryComponent.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryComponent.java
(rev 0)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryComponent.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -0,0 +1,609 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.search;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.FieldSelector;
+import org.apache.lucene.document.FieldSelectorResult;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.PhraseQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.BooleanClause.Occur;
+import org.jboss.dna.common.i18n.I18n;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.property.Binary;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.Between;
+import org.jboss.dna.graph.query.model.BindVariableName;
+import org.jboss.dna.graph.query.model.ChildNode;
+import org.jboss.dna.graph.query.model.Comparison;
+import org.jboss.dna.graph.query.model.Constraint;
+import org.jboss.dna.graph.query.model.DescendantNode;
+import org.jboss.dna.graph.query.model.DynamicOperand;
+import org.jboss.dna.graph.query.model.FullTextSearch;
+import org.jboss.dna.graph.query.model.FullTextSearchScore;
+import org.jboss.dna.graph.query.model.Length;
+import org.jboss.dna.graph.query.model.Literal;
+import org.jboss.dna.graph.query.model.LowerCase;
+import org.jboss.dna.graph.query.model.NodeDepth;
+import org.jboss.dna.graph.query.model.NodeLocalName;
+import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.NodePath;
+import org.jboss.dna.graph.query.model.Not;
+import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.graph.query.model.Or;
+import org.jboss.dna.graph.query.model.PropertyExistence;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.model.SameNode;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.SetCriteria;
+import org.jboss.dna.graph.query.model.StaticOperand;
+import org.jboss.dna.graph.query.model.UpperCase;
+import org.jboss.dna.graph.query.model.Visitors;
+import org.jboss.dna.graph.query.model.FullTextSearch.NegationTerm;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.process.AbstractAccessComponent;
+import org.jboss.dna.graph.query.process.ProcessingComponent;
+import org.jboss.dna.graph.query.process.SelectComponent;
+import org.jboss.dna.graph.query.process.SelectComponent.Analyzer;
+import org.jboss.dna.search.DualIndexSearchProvider.ContentIndex;
+
+/**
+ *
+ */
+/**
+ * The {@link ProcessingComponent} implementation that executes a single atomic access
query against the Lucene indexes.
+ */
+public class LuceneQueryComponent extends AbstractAccessComponent {
+ private final QueryCommand originalQuery;
+ private final LuceneSession session;
+ private final String sourceName;
+ private final String workspaceName;
+
+ protected LuceneQueryComponent( LuceneSession session,
+ QueryCommand originalQuery,
+ QueryContext context,
+ Columns columns,
+ PlanNode accessNode,
+ Analyzer analyzer,
+ String sourceName,
+ String workspaceName ) {
+ super(context, columns, accessNode);
+ this.originalQuery = originalQuery;
+ this.session = session;
+ this.sourceName = sourceName;
+ this.workspaceName = workspaceName;
+ }
+
+ protected String fieldNameFor( Name name ) {
+ return session.stringFactory.create(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List<Object[]> execute() {
+
+ // Some kinds of constraints are not easily pushed down to Lucene as are of a
Lucene Query, and
+ // instead are applied by filtering the results. For example, a
FullTextSearchScore applies
+ // to the score of the tuple, which cannot be (easily?) applied as a Query.
+ //
+ // Therefore, each of the AND-ed constraints of the query are evaluated
separately. After all,
+ // each of the tuples returned by the planned query must satisfy all of the
AND-ed constraints.
+ // Or, to put it another way, if a tuple does not satisfy one of the AND-ed
constraints, the
+ // tuple should not be included in the query results.
+ //
+ // Logically, any AND-ed criteria that cannot be pushed down to Lucene can of
course be applied
+ // as a filter on the results. Thus, each AND-ed constraint is processed to first
determine if
+ // it can be represented as a Lucene query; all other AND-ed constraints must be
handled as
+ // a results filter. Since most queries will likely use one or more simple
constraints AND-ed
+ // together, this approach will likely work very well.
+ //
+ // The only hairy case is when any AND-ed constraint is actually an OR-ed
combination of multiple
+ // constraints of which at least one cannot be pushed down to Lucene. In this
case, the entire
+ // AND-ed constraint must be treated as a results filter (even if many of those
constraints that
+ // make up the OR-ed constraint can be pushed down). Hopefully, this will not be
a common case
+ // in actual queries.
+
+ // For each of the AND-ed constraints ...
+ Query pushDownQuery = null;
+ Constraint postProcessConstraint = null;
+ try {
+ for (Constraint andedConstraint : this.andedConstraints) {
+ // Determine if it can be represented as a Lucene query ...
+ Query constraintQuery = createQuery(andedConstraint);
+ if (constraintQuery != null) {
+ // The AND-ed constraint _can_ be represented as a push-down Lucene
query ...
+ if (pushDownQuery == null) {
+ // This must be the first query ...
+ pushDownQuery = constraintQuery;
+ } else if (pushDownQuery instanceof BooleanQuery) {
+ // We have to add the constraint query to the existing boolean
...
+ BooleanQuery booleanQuery = (BooleanQuery)pushDownQuery;
+ booleanQuery.add(constraintQuery, Occur.MUST);
+ } else {
+ // This is the second push-down query, so create a BooleanQuery
...
+ BooleanQuery booleanQuery = new BooleanQuery();
+ booleanQuery.add(pushDownQuery, Occur.MUST);
+ booleanQuery.add(constraintQuery, Occur.MUST);
+ pushDownQuery = booleanQuery;
+ }
+ } else {
+ // The AND-ed constraint _cannot_ be represented as a push-down
Lucene query ...
+ if (postProcessConstraint == null) {
+ postProcessConstraint = andedConstraint;
+ } else {
+ postProcessConstraint = new And(postProcessConstraint,
andedConstraint);
+ }
+ }
+ }
+ } catch (IOException e) {
+ // There was a error working with the constraints (such as a
ValueFormatException) ...
+ QueryContext context = getContext();
+ I18n msg = SearchI18n.errorWhilePerformingQuery;
+ String origQueryString = Visitors.readable(originalQuery,
context.getExecutionContext());
+ context.getProblems().addError(e, msg, origQueryString, workspaceName,
sourceName, e.getMessage());
+ return emptyTuples();
+ } catch (RuntimeException e) {
+ // There was a error working with the constraints (such as a
ValueFormatException) ...
+ QueryContext context = getContext();
+ I18n msg = SearchI18n.errorWhilePerformingQuery;
+ String origQueryString = Visitors.readable(originalQuery,
context.getExecutionContext());
+ context.getProblems().addError(e, msg, origQueryString, workspaceName,
sourceName, e.getMessage());
+ return emptyTuples();
+ }
+
+ if (pushDownQuery == null) {
+ // There are no constraints that can be pushed down, so return _all_ the
nodes ...
+ pushDownQuery = new MatchAllDocsQuery();
+ }
+
+ // Get the results from Lucene ...
+ List<Object[]> tuples = null;
+ final Columns columns = getColumns();
+ final QueryContext context = getContext();
+ final ExecutionContext execContext = context.getExecutionContext();
+ try {
+ // Execute the query against the content indexes ...
+ IndexSearcher searcher = session.getContentSearcher();
+ TupleCollector collector = new TupleCollector(columns,
execContext.getValueFactories().getUuidFactory());
+ searcher.search(pushDownQuery, collector);
+ tuples = collector.getTuples();
+ } catch (IOException e) {
+ // There was a problem executing the Lucene query ...
+ I18n msg = SearchI18n.errorWhilePerformingLuceneQuery;
+ String origQueryString = Visitors.readable(originalQuery, execContext);
+ context.getProblems().addError(e, msg, pushDownQuery, origQueryString,
workspaceName, sourceName, e.getMessage());
+ return emptyTuples();
+ }
+
+ if (postProcessConstraint != null && !tuples.isEmpty()) {
+ // Create a delegate processing component that will return the tuples
we've already found ...
+ final List<Object[]> allTuples = tuples;
+ ProcessingComponent tuplesProcessor = new ProcessingComponent(context,
columns) {
+ @Override
+ public List<Object[]> execute() {
+ return allTuples;
+ }
+ };
+ // Create a processing component that will apply these constraints to the
tuples we already found ...
+ return new SelectComponent(tuplesProcessor, postProcessConstraint,
context.getVariables()).execute();
+ }
+ return tuples;
+ }
+
+ protected Query createQuery( Constraint constraint ) throws IOException {
+ if (constraint instanceof And) {
+ And and = (And)constraint;
+ Query leftQuery = createQuery(and.getLeft());
+ Query rightQuery = createQuery(and.getRight());
+ if (leftQuery == null || rightQuery == null) return null;
+ BooleanQuery booleanQuery = new BooleanQuery();
+ booleanQuery.add(createQuery(and.getLeft()), Occur.MUST);
+ booleanQuery.add(createQuery(and.getRight()), Occur.MUST);
+ return booleanQuery;
+ }
+ if (constraint instanceof Or) {
+ Or or = (Or)constraint;
+ Query leftQuery = createQuery(or.getLeft());
+ Query rightQuery = createQuery(or.getRight());
+ if (leftQuery == null) {
+ return rightQuery != null ? rightQuery : null;
+ } else if (rightQuery == null) {
+ return leftQuery;
+ }
+ BooleanQuery booleanQuery = new BooleanQuery();
+ booleanQuery.add(createQuery(or.getLeft()), Occur.SHOULD);
+ booleanQuery.add(createQuery(or.getRight()), Occur.SHOULD);
+ return booleanQuery;
+ }
+ if (constraint instanceof Not) {
+ Not not = (Not)constraint;
+ Query notted = createQuery(not.getConstraint());
+ if (notted == null) return new MatchAllDocsQuery();
+ }
+ if (constraint instanceof SetCriteria) {
+ SetCriteria setCriteria = (SetCriteria)constraint;
+ DynamicOperand left = setCriteria.getLeftOperand();
+ int numRightOperands = setCriteria.getRightOperands().size();
+ assert numRightOperands > 0;
+ if (numRightOperands == 1) {
+ return createQuery(left, Operator.EQUAL_TO,
setCriteria.getRightOperands().iterator().next());
+ }
+ BooleanQuery setQuery = new BooleanQuery();
+ for (StaticOperand right : setCriteria.getRightOperands()) {
+ Query rightQuery = createQuery(left, Operator.EQUAL_TO, right);
+ if (rightQuery == null) return null;
+ setQuery.add(rightQuery, Occur.SHOULD);
+ }
+ return setQuery;
+ }
+ if (constraint instanceof PropertyExistence) {
+ PropertyExistence existence = (PropertyExistence)constraint;
+ return createQuery(existence.getSelectorName(),
existence.getPropertyName());
+ }
+ if (constraint instanceof Between) {
+ Between between = (Between)constraint;
+ return createQuery(between);
+ }
+ if (constraint instanceof Comparison) {
+ Comparison comparison = (Comparison)constraint;
+ return createQuery(comparison.getOperand1(), comparison.getOperator(),
comparison.getOperand2());
+ }
+ if (constraint instanceof FullTextSearch) {
+ FullTextSearch search = (FullTextSearch)constraint;
+ String fieldName = ContentIndex.FULL_TEXT;
+ Name propertyName = search.getPropertyName();
+ if (propertyName != null) {
+ fieldName = session.fullTextFieldName(fieldNameFor(propertyName));
+ }
+ return createQuery(fieldName, search.getTerm());
+ }
+ try {
+ if (constraint instanceof SameNode) {
+ SameNode sameNode = (SameNode)constraint;
+ return session.findNodeAt(sameNode.getPath());
+ }
+ if (constraint instanceof ChildNode) {
+ ChildNode childNode = (ChildNode)constraint;
+ return session.findChildNodes(childNode.getParentPath());
+ }
+ if (constraint instanceof DescendantNode) {
+ DescendantNode descendantNode = (DescendantNode)constraint;
+ return session.findAllNodesBelow(descendantNode.getAncestorPath());
+ }
+ } catch (IOException e) {
+ I18n msg = SearchI18n.errorWhilePerformingQuery;
+ getContext().getProblems().addError(e,
+ msg,
+ Visitors.readable(originalQuery),
+ workspaceName,
+ sourceName,
+ e.getMessage());
+ return null;
+ }
+ // Should not get here ...
+ assert false;
+ return null;
+ }
+
+ protected Query createQuery( DynamicOperand left,
+ Operator operator,
+ StaticOperand right ) throws IOException {
+ return createQuery(left, operator, right, true);
+ }
+
+ protected Query createQuery( DynamicOperand left,
+ Operator operator,
+ StaticOperand right,
+ boolean caseSensitive ) throws IOException {
+ // Handle the static operand ...
+ Object value = createOperand(right, caseSensitive);
+ assert value != null;
+
+ // Address the dynamic operand ...
+ if (left instanceof FullTextSearchScore) {
+ // This can only be represented as a filter ...
+ return null;
+ } else if (left instanceof PropertyValue) {
+ return session.findNodesWith((PropertyValue)left, operator, value,
caseSensitive);
+ } else if (left instanceof Length) {
+ return session.findNodesWith((Length)left, operator, right);
+ } else if (left instanceof LowerCase) {
+ LowerCase lowercase = (LowerCase)left;
+ return createQuery(lowercase.getOperand(), operator, right, false);
+ } else if (left instanceof UpperCase) {
+ UpperCase lowercase = (UpperCase)left;
+ return createQuery(lowercase.getOperand(), operator, right, false);
+ } else if (left instanceof NodeDepth) {
+ assert operator != Operator.LIKE;
+ // Could be represented as a result filter, but let's do this now ...
+ return session.findNodesWith((NodeDepth)left, operator, value);
+ } else if (left instanceof NodePath) {
+ return session.findNodesWith((NodePath)left, operator, value,
caseSensitive);
+ } else if (left instanceof NodeName) {
+ return session.findNodesWith((NodeName)left, operator, value,
caseSensitive);
+ } else if (left instanceof NodeLocalName) {
+ return session.findNodesWith((NodeLocalName)left, operator, value,
caseSensitive);
+ } else {
+ assert false;
+ return null;
+ }
+ }
+
+ protected Object createOperand( StaticOperand operand,
+ boolean caseSensitive ) {
+ Object value = null;
+ if (operand instanceof Literal) {
+ Literal literal = (Literal)operand;
+ value = literal.getValue();
+ if (!caseSensitive) value = lowerCase(value);
+ } else if (operand instanceof BindVariableName) {
+ BindVariableName variable = (BindVariableName)operand;
+ String variableName = variable.getVariableName();
+ value = getContext().getVariables().get(variableName);
+ if (!caseSensitive) value = lowerCase(value);
+ } else {
+ assert false;
+ }
+ return value;
+ }
+
+ protected Query createQuery( DynamicOperand left,
+ StaticOperand lower,
+ StaticOperand upper,
+ boolean includesLower,
+ boolean includesUpper,
+ boolean caseSensitive ) throws IOException {
+ // Handle the static operands ...
+ Object lowerValue = createOperand(lower, caseSensitive);
+ Object upperValue = createOperand(upper, caseSensitive);
+ assert lowerValue != null;
+ assert upperValue != null;
+
+ // Only in the case of a PropertyValue and Depth will we need to do something
special ...
+ if (left instanceof NodeDepth) {
+ return session.findNodesWithNumericRange((NodeDepth)left, lowerValue,
upperValue, includesLower, includesUpper);
+ } else if (left instanceof PropertyValue) {
+ PropertyType lowerType = PropertyType.discoverType(lowerValue);
+ PropertyType upperType = PropertyType.discoverType(upperValue);
+ if (upperType == lowerType) {
+ switch (upperType) {
+ case DATE:
+ case LONG:
+ case DOUBLE:
+ case DECIMAL:
+ return session.findNodesWithNumericRange((PropertyValue)left,
+ lowerValue,
+ upperValue,
+ includesLower,
+ includesUpper);
+ default:
+ // continue on and handle as boolean query ...
+ }
+ }
+ }
+
+ // Otherwise, just create a boolean query ...
+ BooleanQuery query = new BooleanQuery();
+ Operator lowerOp = includesLower ? Operator.GREATER_THAN_OR_EQUAL_TO :
Operator.GREATER_THAN;
+ Operator upperOp = includesUpper ? Operator.LESS_THAN_OR_EQUAL_TO :
Operator.LESS_THAN;
+ Query lowerQuery = createQuery(left, lowerOp, lower, caseSensitive);
+ Query upperQuery = createQuery(left, upperOp, upper, caseSensitive);
+ if (lowerQuery == null || upperQuery == null) return null;
+ query.add(lowerQuery, Occur.MUST);
+ query.add(upperQuery, Occur.MUST);
+ return query;
+ }
+
+ protected Object lowerCase( Object value ) {
+ if (value instanceof String) {
+ return ((String)value).toLowerCase();
+ }
+ assert !(value instanceof Binary);
+ ValueFactory<String> stringFactory =
getContext().getExecutionContext().getValueFactories().getStringFactory();
+ ValueFactory<?> valueFactory =
getContext().getExecutionContext().getValueFactories().getValueFactory(value);
+ return valueFactory.create(stringFactory.create(value).toLowerCase());
+ }
+
+ protected Query createQuery( SelectorName selectorName,
+ Name propertyName ) {
+ Term term = new Term(fieldNameFor(propertyName));
+ return new TermQuery(term);
+ }
+
+ protected Query createQuery( String fieldName,
+ FullTextSearch.Term term ) {
+ if (term instanceof FullTextSearch.Conjunction) {
+ FullTextSearch.Conjunction conjunction = (FullTextSearch.Conjunction)term;
+ BooleanQuery query = new BooleanQuery();
+ for (FullTextSearch.Term nested : conjunction) {
+ if (nested instanceof NegationTerm) {
+ query.add(createQuery(fieldName,
((NegationTerm)nested).getNegatedTerm()), Occur.MUST_NOT);
+ } else {
+ query.add(createQuery(fieldName, nested), Occur.MUST);
+ }
+ }
+ return query;
+ }
+ if (term instanceof FullTextSearch.Disjunction) {
+ FullTextSearch.Disjunction disjunction = (FullTextSearch.Disjunction)term;
+ BooleanQuery query = new BooleanQuery();
+ for (FullTextSearch.Term nested : disjunction) {
+ if (nested instanceof NegationTerm) {
+ query.add(createQuery(fieldName,
((NegationTerm)nested).getNegatedTerm()), Occur.MUST_NOT);
+ } else {
+ query.add(createQuery(fieldName, nested), Occur.SHOULD);
+ }
+ }
+ return query;
+ }
+ if (term instanceof FullTextSearch.SimpleTerm) {
+ FullTextSearch.SimpleTerm simple = (FullTextSearch.SimpleTerm)term;
+ if (simple.isQuotingRequired()) {
+ PhraseQuery query = new PhraseQuery();
+ query.setSlop(0); // terms must be adjacent
+ for (String value : simple.getValues()) {
+ query.add(new Term(fieldName, value));
+ }
+ return query;
+ }
+ return new TermQuery(new Term(fieldName, simple.getValue()));
+ }
+ // Should not get here ...
+ assert false;
+ return null;
+ }
+
+ /**
+ * This collector is responsible for loading the value for each of the columns into
each tuple array.
+ */
+ protected static class TupleCollector extends Collector {
+ private final LinkedList<Object[]> tuples = new
LinkedList<Object[]>();
+ private final Columns columns;
+ private final int numValues;
+ private final boolean recordScore;
+ private final int scoreIndex;
+ private final FieldSelector fieldSelector;
+ private final int locationIndex;
+ private final ValueFactory<UUID> uuidFactory;
+ private Scorer scorer;
+ private IndexReader currentReader;
+ private int docOffset;
+
+ protected TupleCollector( Columns columns,
+ ValueFactory<UUID> uuidFactory ) {
+ this.columns = columns;
+ this.uuidFactory = uuidFactory;
+ assert this.columns != null;
+ assert this.uuidFactory != null;
+ this.numValues = this.columns.getTupleSize();
+ assert this.numValues >= 0;
+ assert this.columns.getSelectorNames().size() == 1;
+ final String selectorName = this.columns.getSelectorNames().get(0);
+ this.locationIndex = this.columns.getLocationIndex(selectorName);
+ this.recordScore = this.columns.hasFullTextSearchScores();
+ this.scoreIndex = this.recordScore ?
this.columns.getFullTextSearchScoreIndexFor(selectorName) : -1;
+ final Set<String> columnNames = new
HashSet<String>(this.columns.getColumnNames());
+ columnNames.add(ContentIndex.UUID); // add the UUID, which we'll put into
the Location ...
+ this.fieldSelector = new FieldSelector() {
+ private static final long serialVersionUID = 1L;
+
+ public FieldSelectorResult accept( String fieldName ) {
+ return columnNames.contains(fieldName) ? FieldSelectorResult.LOAD :
FieldSelectorResult.NO_LOAD;
+ }
+ };
+ }
+
+ /**
+ * @return tuples
+ */
+ public LinkedList<Object[]> getTuples() {
+ return tuples;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.apache.lucene.search.Collector#acceptsDocsOutOfOrder()
+ */
+ @Override
+ public boolean acceptsDocsOutOfOrder() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.apache.lucene.search.Collector#setNextReader(org.apache.lucene.index.IndexReader,
int)
+ */
+ @Override
+ public void setNextReader( IndexReader reader,
+ int docBase ) {
+ this.currentReader = reader;
+ this.docOffset = docBase;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.apache.lucene.search.Collector#setScorer(org.apache.lucene.search.Scorer)
+ */
+ @Override
+ public void setScorer( Scorer scorer ) {
+ this.scorer = scorer;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.apache.lucene.search.Collector#collect(int)
+ */
+ @Override
+ public void collect( int doc ) throws IOException {
+ int docId = doc + docOffset;
+ Object[] tuple = new Object[numValues];
+ Document document = currentReader.document(docId, fieldSelector);
+ for (String columnName : columns.getColumnNames()) {
+ int index = columns.getColumnIndexForName(columnName);
+ // We just need to retrieve the first value if there is more than one
...
+ tuple[index] = document.get(columnName);
+ }
+
+ // Set the score column if required ...
+ if (recordScore) {
+ assert scorer != null;
+ tuple[scoreIndex] = scorer.score();
+ }
+
+ // Load the UUID into a Location object ...
+ UUID uuid = uuidFactory.create(document.get(ContentIndex.UUID));
+ tuple[locationIndex] = Location.create(uuid);
+ tuples.add(tuple);
+ }
+ }
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneSession.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneSession.java
(rev 0)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneSession.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -0,0 +1,368 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.search;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.Set;
+import java.util.UUID;
+import net.jcip.annotations.NotThreadSafe;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.jboss.dna.common.i18n.I18n;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.DateTimeFactory;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.PathFactory;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryEngine;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.model.Length;
+import org.jboss.dna.graph.query.model.NodeDepth;
+import org.jboss.dna.graph.query.model.NodeLocalName;
+import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.NodePath;
+import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.model.Visitors;
+import org.jboss.dna.graph.query.optimize.Optimizer;
+import org.jboss.dna.graph.query.optimize.OptimizerRule;
+import org.jboss.dna.graph.query.optimize.RuleBasedOptimizer;
+import org.jboss.dna.graph.query.plan.CanonicalPlanner;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.Planner;
+import org.jboss.dna.graph.query.process.ProcessingComponent;
+import org.jboss.dna.graph.query.process.QueryProcessor;
+import org.jboss.dna.graph.search.SearchProvider;
+import org.jboss.dna.search.DualIndexSearchProvider.PathIndex;
+
+/**
+ *
+ */
+@NotThreadSafe
+public abstract class LuceneSession implements SearchProvider.Session {
+ protected final ExecutionContext context;
+ protected final String sourceName;
+ protected final String workspaceName;
+ protected final IndexRules rules;
+ protected final Analyzer analyzer;
+ protected final boolean overwrite;
+ protected final boolean readOnly;
+ protected final ValueFactory<String> stringFactory;
+ protected final DateTimeFactory dateFactory;
+ protected final PathFactory pathFactory;
+ private int changeCount;
+ private QueryEngine queryEngine;
+
+ protected LuceneSession( ExecutionContext context,
+ String sourceName,
+ String workspaceName,
+ IndexRules rules,
+ Analyzer analyzer,
+ boolean overwrite,
+ boolean readOnly ) {
+ this.context = context;
+ this.sourceName = sourceName;
+ this.workspaceName = workspaceName;
+ this.rules = rules;
+ this.overwrite = overwrite;
+ this.readOnly = readOnly;
+ this.analyzer = analyzer;
+ this.stringFactory = context.getValueFactories().getStringFactory();
+ this.dateFactory = context.getValueFactories().getDateFactory();
+ this.pathFactory = context.getValueFactories().getPathFactory();
+ assert this.context != null;
+ assert this.sourceName != null;
+ assert this.workspaceName != null;
+ assert this.rules != null;
+ assert this.analyzer != null;
+ assert this.stringFactory != null;
+ assert this.dateFactory != null;
+ }
+
+ /**
+ * Create the field name that will be used to store the full-text searchable property
values.
+ *
+ * @param propertyName the name of the property; may not be null
+ * @return the field name for the full-text searchable property values; never null
+ */
+ protected abstract String fullTextFieldName( String propertyName );
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.search.SearchProvider.Session#getContext()
+ */
+ public final ExecutionContext getContext() {
+ return context;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.search.SearchProvider.Session#getSourceName()
+ */
+ public final String getSourceName() {
+ return sourceName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.search.SearchProvider.Session#getWorkspaceName()
+ */
+ public String getWorkspaceName() {
+ return workspaceName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.search.SearchProvider.Session#hasChanges()
+ */
+ public boolean hasChanges() {
+ return changeCount > 0;
+ }
+
+ /**
+ * Get the Lucene index searcher that should be used to execute queries.
+ *
+ * @return the searcher; never null
+ * @throws IOException if there is an error obtaining the index searcher
+ */
+ public abstract IndexSearcher getContentSearcher() throws IOException;
+
+ /**
+ * Get the query engine for this session.
+ *
+ * @return the query engine; never null
+ */
+ protected QueryEngine queryEngine() {
+ if (queryEngine == null) {
+ // Create the query engine ...
+ Planner planner = new CanonicalPlanner();
+ Optimizer optimizer = new RuleBasedOptimizer() {
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.optimize.RuleBasedOptimizer#populateRuleStack(java.util.LinkedList,
+ * org.jboss.dna.graph.query.plan.PlanHints)
+ */
+ @Override
+ protected void populateRuleStack( LinkedList<OptimizerRule>
ruleStack,
+ PlanHints hints ) {
+ super.populateRuleStack(ruleStack, hints);
+ // Add any custom rules here, either at the front of the stack or at
the end
+ }
+ };
+ QueryProcessor processor = new QueryProcessor() {
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.process.QueryProcessor#createAccessComponent(org.jboss.dna.graph.query.model.QueryCommand,
+ * org.jboss.dna.graph.query.QueryContext,
org.jboss.dna.graph.query.plan.PlanNode,
+ * org.jboss.dna.graph.query.QueryResults.Columns,
+ * org.jboss.dna.graph.query.process.SelectComponent.Analyzer)
+ */
+ @Override
+ protected ProcessingComponent createAccessComponent( QueryCommand
originalQuery,
+ QueryContext
context,
+ PlanNode
accessNode,
+ Columns
resultColumns,
+
org.jboss.dna.graph.query.process.SelectComponent.Analyzer analyzer ) {
+ try {
+ return LuceneSession.this.createAccessComponent(originalQuery,
+ context,
+ accessNode,
+ resultColumns,
+ analyzer);
+ } catch (IOException e) {
+ I18n msg = SearchI18n.errorWhilePerformingQuery;
+ context.getProblems().addError(e,
+ msg,
+ Visitors.readable(originalQuery),
+ getWorkspaceName(),
+ getSourceName(),
+ e.getMessage());
+ return null;
+ }
+ }
+ };
+
+ queryEngine = new QueryEngine(planner, optimizer, processor);
+ }
+ return queryEngine;
+ }
+
+ protected abstract ProcessingComponent createAccessComponent( QueryCommand
originalQuery,
+ QueryContext context,
+ PlanNode accessNode,
+ Columns resultColumns,
+
org.jboss.dna.graph.query.process.SelectComponent.Analyzer analyzer )
+ throws IOException;
+
+ /**
+ * Utility method to create a query to find all of the documents representing nodes
with the supplied UUIDs.
+ *
+ * @param uuids the UUIDs of the nodes that are to be found; may not be null
+ * @return the query; never null
+ * @throws IOException if there is a problem creating this query
+ */
+ public abstract Query findAllNodesWithUuids( Set<UUID> uuids ) throws
IOException;
+
+ public abstract Query findAllNodesBelow( Path ancestorPath ) throws IOException;
+
+ /**
+ * Return a query that can be used to find all of the documents that represent nodes
that are children of the node at the
+ * supplied path.
+ *
+ * @param parentPath the path of the parent node.
+ * @return the query; never null
+ * @throws IOException if there is an error creating the query
+ */
+ public abstract Query findChildNodes( Path parentPath ) throws IOException;
+
+ /**
+ * Create a query that can be used to find the one document (or node) that exists at
the exact path supplied. This method
+ * first queries the {@link PathIndex path index} to find the UUID of the node at the
supplied path, and then returns a query
+ * that matches the UUID.
+ *
+ * @param path the path of the node
+ * @return the query; never null
+ * @throws IOException if there is an error creating the query
+ */
+ public abstract Query findNodeAt( Path path ) throws IOException;
+
+ /**
+ * Create a query that can be used to find documents (or nodes) that have a field
value that satisfies the supplied LIKE
+ * expression.
+ *
+ * @param fieldName the name of the document field to search
+ * @param likeExpression the JCR like expression
+ * @return the query; never null
+ * @throws IOException if there is an error creating the query
+ */
+ public abstract Query findNodesLike( String fieldName,
+ String likeExpression ) throws IOException;
+
+ public abstract Query findNodesWith( Length propertyLength,
+ Operator operator,
+ Object value ) throws IOException;
+
+ public abstract Query findNodesWith( PropertyValue propertyValue,
+ Operator operator,
+ Object value,
+ boolean caseSensitive ) throws IOException;
+
+ public abstract Query findNodesWithNumericRange( PropertyValue propertyValue,
+ Object lowerValue,
+ Object upperValue,
+ boolean includesLower,
+ boolean includesUpper ) throws
IOException;
+
+ public abstract Query findNodesWithNumericRange( NodeDepth depth,
+ Object lowerValue,
+ Object upperValue,
+ boolean includesLower,
+ boolean includesUpper ) throws
IOException;
+
+ // public abstract Query findNodesWithNumericRange( String field,
+ // Object lowerValue,
+ // Object upperValue,
+ // boolean includesLower,
+ // boolean includesUpper ) throws IOException;
+
+ public abstract Query findNodesWith( NodePath nodePath,
+ Operator operator,
+ Object value,
+ boolean caseSensitive ) throws IOException;
+
+ public abstract Query findNodesWith( NodeName nodeName,
+ Operator operator,
+ Object value,
+ boolean caseSensitive ) throws IOException;
+
+ public abstract Query findNodesWith( NodeLocalName nodeName,
+ Operator operator,
+ Object value,
+ boolean caseSensitive ) throws IOException;
+
+ public abstract Query findNodesWith( NodeDepth depthConstraint,
+ Operator operator,
+ Object value ) throws IOException;
+
+ // public abstract Query createLocalNameQuery( String likeExpression ) throws
IOException;
+
+ // public abstract Query createSnsIndexQuery( String likeExpression ) throws
IOException;
+
+ /**
+ * Convert the JCR like expression to a Lucene wildcard expression. The JCR like
expression uses '%' to match 0 or more
+ * characters, '_' to match any single character, '\x' to match the
'x' character, and all other characters to match
+ * themselves.
+ *
+ * @param likeExpression the like expression; may not be null
+ * @return the expression that can be used with a WildcardQuery; never null
+ */
+ public String toWildcardExpression( String likeExpression ) {
+ assert likeExpression != null;
+ assert likeExpression.length() > 0;
+ return likeExpression.replace('%', '*').replace('_',
'?').replaceAll("\\\\(.)", "$1");
+ }
+
+ /**
+ * Convert the JCR like expression to a regular expression. The JCR like expression
uses '%' to match 0 or more characters,
+ * '_' to match any single character, '\x' to match the 'x'
character, and all other characters to match themselves. Note that
+ * if any regex metacharacters appear in the like expression, they will be escaped
within the resulting regular expression.
+ *
+ * @param likeExpression the like expression; may not be null
+ * @return the expression that can be used with a WildcardQuery; never null
+ */
+ public String toRegularExpression( String likeExpression ) {
+ assert likeExpression != null;
+ assert likeExpression.length() > 0;
+ // Replace all '\x' with 'x' ...
+ String result = likeExpression.replaceAll("\\\\(.)", "$1");
+ // Escape characters used as metacharacters in regular expressions, including
+ // '[', '^', '\', '$', '.', '|',
'?', '*', '+', '(', and ')'
+ result = result.replaceAll("([[^\\\\$.|?*+()])", "\\$1");
+ // Replace '%'->'[.]+' and '_'->'[.]
+ result = likeExpression.replace("%",
"[.]+").replace("_", "[.]");
+ return result;
+ }
+
+ public String pathAsString( Path path,
+ ValueFactory<String> stringFactory ) {
+ assert path != null;
+ if (path.isRoot()) return "/";
+ String pathStr = stringFactory.create(path);
+ if (!pathStr.endsWith("]")) {
+ pathStr = pathStr + '[' + Path.DEFAULT_INDEX + ']';
+ }
+ return pathStr;
+ }
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneSession.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Deleted: trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngine.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngine.java 2009-11-18
19:37:57 UTC (rev 1328)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngine.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -1,810 +0,0 @@
-/*
- * JBoss DNA (
http://www.jboss.org/dna)
- * See the COPYRIGHT.txt file distributed with this work for information
- * regarding copyright ownership. Some portions may be licensed
- * to Red Hat, Inc. under one or more contributor license agreements.
- * See the AUTHORS.txt file in the distribution for a full listing of
- * individual contributors.
- *
- * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
- * is licensed to you under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * JBoss DNA is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.search;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import net.jcip.annotations.GuardedBy;
-import net.jcip.annotations.ThreadSafe;
-import org.apache.lucene.queryParser.ParseException;
-import org.jboss.dna.common.i18n.I18n;
-import org.jboss.dna.common.util.CheckArg;
-import org.jboss.dna.graph.ExecutionContext;
-import org.jboss.dna.graph.Graph;
-import org.jboss.dna.graph.GraphI18n;
-import org.jboss.dna.graph.Location;
-import org.jboss.dna.graph.Subgraph;
-import org.jboss.dna.graph.SubgraphNode;
-import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
-import org.jboss.dna.graph.connector.RepositorySource;
-import org.jboss.dna.graph.connector.RepositorySourceException;
-import org.jboss.dna.graph.property.Path;
-import org.jboss.dna.graph.query.QueryContext;
-import org.jboss.dna.graph.query.QueryResults;
-import org.jboss.dna.graph.query.model.QueryCommand;
-import org.jboss.dna.graph.query.validate.Schemata;
-import org.jboss.dna.graph.request.ChangeRequest;
-import org.jboss.dna.graph.request.InvalidWorkspaceException;
-
-/**
- * A component that acts as a search engine for the content within a single {@link
RepositorySource}. This engine manages a set of
- * indexes and provides search functionality for each of the workspaces within the
source, and provides various methods to
- * (re)index the content contained with source's workspaces and keep the indexes
up-to-date via changes.
- */
-@ThreadSafe
-public class SearchEngine {
-
- /**
- * The default maximum number of changes that can be made to an index before the
indexes are automatically optimized is * * *
- * * {@value}
- */
- public static final int DEFAULT_MAX_CHANGES_BEFORE_AUTOMATIC_OPTIMIZATION = 0;
-
- protected final ExecutionContext context;
- private final String sourceName;
- private final RepositoryConnectionFactory connectionFactory;
- protected final IndexLayout indexLayout;
- private final int maxChangesBeforeAutomaticOptimization;
- @GuardedBy( "workspacesLock" )
- private final Map<String, Workspace> workspacesByName = new HashMap<String,
Workspace>();
- private final ReadWriteLock workspacesLock = new ReentrantReadWriteLock();
-
- /**
- * Create a search engine instance given the supplied {@link ExecutionContext
execution context}, name of the
- * {@link RepositorySource}, the {@link RepositoryConnectionFactory factory for
RepositorySource connections}, and the
- * {@link DirectoryConfiguration directory factory} that defines where each
workspace's indexes should be placed.
- *
- * @param context the execution context for indexing and optimization operations
- * @param sourceName the name of the {@link RepositorySource}
- * @param connectionFactory the connection factory
- * @param indexLayout the specification of the Lucene index layout
- * @param maxChangesBeforeAutomaticOptimization the number of changes that can be
made to the index before the indexes are
- * automatically optimized; may be 0 or a negative number if no automatic
optimization should be done
- * @throws IllegalArgumentException if any of the parameters (other than indexing
strategy) are null
- */
- public SearchEngine( ExecutionContext context,
- String sourceName,
- RepositoryConnectionFactory connectionFactory,
- IndexLayout indexLayout,
- int maxChangesBeforeAutomaticOptimization ) {
- CheckArg.isNotNull(context, "context");
- CheckArg.isNotNull(sourceName, "sourceName");
- CheckArg.isNotNull(connectionFactory, "connectionFactory");
- this.sourceName = sourceName;
- this.connectionFactory = connectionFactory;
- this.indexLayout = indexLayout;
- this.context = context;
- this.maxChangesBeforeAutomaticOptimization =
maxChangesBeforeAutomaticOptimization < 0 ? 0 : maxChangesBeforeAutomaticOptimization;
- }
-
- /**
- * Create a search engine instance given the supplied {@link ExecutionContext
execution context}, name of the
- * {@link RepositorySource}, the {@link RepositoryConnectionFactory factory for
RepositorySource connections}, and the
- * {@link DirectoryConfiguration directory factory} that defines where each
workspace's indexes should be placed.
- *
- * @param context the execution context for indexing and optimization operations
- * @param sourceName the name of the {@link RepositorySource}
- * @param connectionFactory the connection factory
- * @param indexLayout the specification of the Lucene index layout
- * @throws IllegalArgumentException if any of the parameters (other than indexing
strategy) are null
- */
- public SearchEngine( ExecutionContext context,
- String sourceName,
- RepositoryConnectionFactory connectionFactory,
- IndexLayout indexLayout ) {
- this(context, sourceName, connectionFactory, indexLayout,
DEFAULT_MAX_CHANGES_BEFORE_AUTOMATIC_OPTIMIZATION);
- }
-
- /**
- * Get the name of the RepositorySource that this engine is to use.
- *
- * @return the source name; never null
- */
- public String getSourceName() {
- return sourceName;
- }
-
- /**
- * Get the context in which all indexing operations execute.
- *
- * @return the execution context; never null
- */
- public ExecutionContext getContext() {
- return context;
- }
-
- /**
- * @return maxChangesBeforeAutomaticOptimization
- */
- public int getMaxChangesBeforeAutomaticOptimization() {
- return maxChangesBeforeAutomaticOptimization;
- }
-
- /**
- * Utility to create a Graph for the source.
- *
- * @return the graph instance; never null
- */
- final Graph graph() {
- return Graph.create(sourceName, connectionFactory, context);
- }
-
- /**
- * Utility to obtain the root path.
- *
- * @return the root path; never null
- */
- final Path rootPath() {
- return context.getValueFactories().getPathFactory().createRootPath();
- }
-
- final String readable( Path path ) {
- return context.getValueFactories().getStringFactory().create(path);
- }
-
- /**
- * Index all of the content at or below the supplied path in the named workspace
within the {@link #getSourceName() source}.
- * If the starting point is the root node, then this method will drop the existing
index(es) and rebuild from the content in
- * the workspace and source.
- * <p>
- * This method operates synchronously and returns when the requested indexing is
completed.
- * </p>
- *
- * @param workspaceName the name of the workspace
- * @param startingPoint the location that represents the content to be indexed; must
have a path
- * @param depthPerRead the depth of each subgraph read operation
- * @throws IllegalArgumentException if the workspace name or location are null
- * @throws RepositorySourceException if there is a problem accessing the content
- * @throws SearchEngineException if there is a problem updating the indexes
- * @throws InvalidWorkspaceException if the workspace does not exist
- */
- public void index( String workspaceName,
- Location startingPoint,
- int depthPerRead ) throws RepositorySourceException,
SearchEngineException {
- CheckArg.isNotNull(workspaceName, "workspaceName");
- CheckArg.isNotNull(startingPoint, "startingPoint");
- assert startingPoint.hasPath();
-
- Workspace workspace = getWorkspace(workspaceName);
- if (startingPoint.getPath().isRoot()) {
- // More efficient to just start over with a new index ...
- workspace.execute(true, addContent(startingPoint, depthPerRead));
- } else {
- // Have to first remove the content below the starting point, then add it
again ...
- workspace.execute(false, removeContent(startingPoint),
addContent(startingPoint, depthPerRead));
- }
- }
-
- /**
- * Index all of the content at or below the supplied path in the named workspace
within the {@link #getSourceName() source}.
- * If the starting point is the root node, then this method will drop the existing
index(es) and rebuild from the content in
- * the workspace and source.
- * <p>
- * This method operates synchronously and returns when the requested indexing is
completed.
- * </p>
- *
- * @param workspaceName the name of the workspace
- * @param startingPoint the path that represents the content to be indexed
- * @param depthPerRead the depth of each subgraph read operation
- * @throws IllegalArgumentException if the workspace name or path are null
- * @throws RepositorySourceException if there is a problem accessing the content
- * @throws SearchEngineException if there is a problem updating the indexes
- * @throws InvalidWorkspaceException if the workspace does not exist
- */
- public void index( String workspaceName,
- Path startingPoint,
- int depthPerRead ) throws RepositorySourceException,
SearchEngineException {
- CheckArg.isNotNull(workspaceName, "workspaceName");
- CheckArg.isNotNull(startingPoint, "startingPoint");
- index(workspaceName, Location.create(startingPoint), depthPerRead);
- }
-
- /**
- * Index all of the content in the named workspace within the {@link #getSourceName()
source}. This method operates
- * synchronously and returns when the requested indexing is completed.
- *
- * @param workspaceName the name of the workspace
- * @param depthPerRead the depth of each subgraph read operation
- * @throws IllegalArgumentException if the workspace name is null
- * @throws RepositorySourceException if there is a problem accessing the content
- * @throws SearchEngineException if there is a problem updating the indexes
- * @throws InvalidWorkspaceException if the workspace does not exist
- */
- public void index( String workspaceName,
- int depthPerRead ) throws RepositorySourceException,
SearchEngineException {
- CheckArg.isNotNull(workspaceName, "workspaceName");
- index(workspaceName, rootPath(), depthPerRead);
- }
-
- /**
- * Index (or re-index) all of the content in all of the workspaces within the source.
This method operates synchronously and
- * returns when the requested indexing is completed.
- *
- * @param depthPerRead the depth of each subgraph read operation
- * @throws RepositorySourceException if there is a problem accessing the content
- * @throws SearchEngineException if there is a problem updating the indexes
- */
- public void index( int depthPerRead ) throws RepositorySourceException,
SearchEngineException {
- Path rootPath = rootPath();
- for (String workspaceName : graph().getWorkspaces()) {
- index(workspaceName, rootPath, depthPerRead);
- }
- }
-
- /**
- * Update the indexes with the supplied set of changes to the content.
- *
- * @param changes the set of changes to the content
- * @throws IllegalArgumentException if the path is null
- * @throws RepositorySourceException if there is a problem accessing the content
- * @throws SearchEngineException if there is a problem updating the indexes
- */
- public void index( final Iterable<ChangeRequest> changes ) throws
SearchEngineException {
- // First break up all the changes into different collections, one collection per
workspace ...
- Map<String, Collection<ChangeRequest>> changesByWorkspace = new
HashMap<String, Collection<ChangeRequest>>();
- for (ChangeRequest request : changes) {
- String workspaceName = request.changedWorkspace();
- Collection<ChangeRequest> changesForWorkspace =
changesByWorkspace.get(workspaceName);
- if (changesForWorkspace == null) {
- changesForWorkspace = new LinkedList<ChangeRequest>();
- changesByWorkspace.put(workspaceName, changesForWorkspace);
- }
- changesForWorkspace.add(request);
- }
- // Now update the indexes for each workspace (serially). This minimizes the time
that each workspace
- // locks its indexes for writing.
- for (Map.Entry<String, Collection<ChangeRequest>> entry :
changesByWorkspace.entrySet()) {
- String workspaceName = entry.getKey();
- Collection<ChangeRequest> changesForWorkspace = entry.getValue();
- getWorkspace(workspaceName).execute(false,
updateContent(changesForWorkspace));
- }
- }
-
- /**
- * Invoke the engine's garbage collection on all indexes used by all workspaces
in the source. This method reclaims space and
- * optimizes the index. This should be done on a periodic basis after changes are
made to the engine's indexes.
- *
- * @throws SearchEngineException if there is a problem during optimization
- */
- public void optimize() throws SearchEngineException {
- for (String workspaceName : graph().getWorkspaces()) {
- getWorkspace(workspaceName).execute(false, optimizeContent());
- }
- }
-
- /**
- * Invoke the engine's garbage collection for the indexes associated with the
specified workspace. This method reclaims space
- * and optimizes the index. This should be done on a periodic basis after changes are
made to the engine's indexes.
- *
- * @param workspaceName the name of the workspace
- * @throws IllegalArgumentException if the workspace name is null
- * @throws SearchEngineException if there is a problem during optimization
- * @throws InvalidWorkspaceException if the workspace does not exist
- */
- public void optimize( String workspaceName ) throws SearchEngineException {
- CheckArg.isNotNull(workspaceName, "workspaceName");
- getWorkspace(workspaceName).execute(false, optimizeContent());
- }
-
- /**
- * Perform a full-text search of the content in the named workspace, given the
maximum number of results and the offset
- * defining the first result the caller is interested in.
- *
- * @param context the execution context in which the search is to take place; may not
be null
- * @param workspaceName the name of the workspace
- * @param fullTextSearch the full-text search to be performed; may not be null
- * @param maxResults the maximum number of results that are to be returned; always
positive
- * @param offset the number of initial results to skip, or 0 if the first results are
to be returned
- * @return the activity that will perform the work
- * @throws IllegalArgumentException if the execution context or workspace name are
null
- * @throws SearchEngineException if there is a problem during optimization
- * @throws InvalidWorkspaceException if the workspace does not exist
- */
- public List<Location> fullTextSearch( ExecutionContext context,
- String workspaceName,
- String fullTextSearch,
- int maxResults,
- int offset ) {
- CheckArg.isNotNull(context, "context");
- CheckArg.isNotNull(workspaceName, "workspaceName");
- Search searchActivity = searchContent(context, fullTextSearch, maxResults,
offset);
- getWorkspace(workspaceName).execute(false, searchActivity);
- return searchActivity.getResults();
- }
-
- /**
- * Perform a query of the content in the named workspace, given the Abstract Query
Model representation of the query.
- *
- * @param context the execution context in which the search is to take place; may not
be null
- * @param workspaceName the name of the workspace
- * @param query the query that is to be executed, in the form of the Abstract Query
Model
- * @param schemata the definition of the tables and views that can be used in the
query; may not be null
- * @return the query results; never null
- * @throws IllegalArgumentException if the context, query, or schemata references are
null
- */
- public QueryResults query( ExecutionContext context,
- String workspaceName,
- QueryCommand query,
- Schemata schemata ) {
- CheckArg.isNotNull(context, "context");
- CheckArg.isNotNull(workspaceName, "workspaceName");
- CheckArg.isNotNull(query, "query");
- CheckArg.isNotNull(schemata, "schemata");
- QueryContext queryContext = new QueryContext(context, schemata);
- Query queryActivity = queryContent(queryContext, query);
- getWorkspace(workspaceName).execute(false, queryActivity);
- return queryActivity.getResults();
- }
-
- /**
- * Remove the supplied index from the search engine. This is typically done when the
workspace has been deleted from the
- * source, or when
- *
- * @param workspaceName the name of the workspace
- * @throws IllegalArgumentException if the workspace name is null
- * @throws SearchEngineException if there is a problem removing the workspace
- */
- public void removeWorkspace( String workspaceName ) throws SearchEngineException {
- CheckArg.isNotNull(workspaceName, "workspaceName");
- try {
- workspacesLock.writeLock().lock();
- // Check whether another thread got in and created the engine while we waited
...
- Workspace workspace = workspacesByName.remove(workspaceName);
- if (workspace != null) {
- indexLayout.destroyIndexes(context, getSourceName(), workspaceName);
- }
- } catch (IOException e) {
- String message =
SearchI18n.errorWhileRemovingIndexesForWorkspace.text(sourceName, workspaceName,
e.getMessage());
- throw new SearchEngineException(message, e);
- } finally {
- workspacesLock.writeLock().unlock();
- }
- }
-
- /**
- * Remove from the search engine all workspace-related indexes, thereby cleaning up
any resources used by this search engine.
- *
- * @throws SearchEngineException if there is a problem removing any of the workspace
- */
- public void removeWorkspaces() throws SearchEngineException {
- try {
- workspacesLock.writeLock().lock();
- for (String workspaceName : new
HashSet<String>(workspacesByName.keySet())) {
- removeWorkspace(workspaceName);
- }
- } finally {
- workspacesLock.writeLock().unlock();
- }
- }
-
- /**
- * Get the search engine for the workspace with the supplied name.
- *
- * @param workspaceName the name of the workspace
- * @return the workspace's search engine
- * @throws InvalidWorkspaceException if the workspace does not exist
- */
- protected Workspace getWorkspace( String workspaceName ) {
- Workspace workspace = null;
- try {
- workspacesLock.readLock().lock();
- workspace = workspacesByName.get(workspaceName);
- } finally {
- workspacesLock.readLock().unlock();
- }
-
- if (workspace == null) {
- // Verify the workspace does exist ...
- if (!graph().getWorkspaces().contains(workspaceName)) {
- String msg =
GraphI18n.workspaceDoesNotExistInRepository.text(workspaceName, getSourceName());
- throw new InvalidWorkspaceException(msg);
- }
- try {
- workspacesLock.writeLock().lock();
- // Check whether another thread got in and created the engine while we
waited ...
- workspace = workspacesByName.get(workspaceName);
- if (workspace == null) {
- // Create the engine and register it ...
- workspace = new Workspace(workspaceName);
- workspacesByName.put(workspaceName, workspace);
- }
- } finally {
- workspacesLock.writeLock().unlock();
- }
- }
- return workspace;
- }
-
- protected class Workspace {
- private final String sourceName;
- private final String workspaceName;
- protected final AtomicInteger modifiedNodesSinceLastOptimize = new
AtomicInteger(0);
-
- protected Workspace( String workspaceName ) {
- this.workspaceName = workspaceName;
- this.sourceName = getSourceName();
- }
-
- /**
- * Get the workspace name.
- *
- * @return the workspace name; never null
- */
- public String getWorkspaceName() {
- return workspaceName;
- }
-
- /**
- * Execute the supplied activities against the indexes.
- *
- * @param overwrite true if the existing indexes should be overwritten, or false
if they should be used
- * @param activities the activities to execute
- * @throws SearchEngineException if there is a problem performing the activities
- */
- protected final void execute( boolean overwrite,
- Activity... activities ) throws
SearchEngineException {
- // Determine if the activities are readonly ...
- boolean readOnly = true;
- for (Activity activity : activities) {
- if (!(activity instanceof ReadOnlyActivity)) {
- readOnly = false;
- break;
- }
- }
-
- // Create a session ...
- IndexSession session = indexLayout.createSession(context, sourceName,
workspaceName, overwrite, readOnly);
- assert session != null;
-
- // Execute the various activities ...
- Throwable error = null;
- try {
- int numChanges = 0;
- for (Activity activity : activities) {
- try {
- numChanges += activity.execute(session);
- } catch (IOException e) {
- error = e;
- throw new SearchEngineException(activity.messageFor(e,
sourceName, workspaceName), e);
- } catch (ParseException e) {
- error = e;
- throw new SearchEngineException(activity.messageFor(e,
sourceName, workspaceName), e);
- } catch (RuntimeException e) {
- error = e;
- throw e;
- }
- }
- if (numChanges > 0) {
- numChanges =
this.modifiedNodesSinceLastOptimize.addAndGet(numChanges);
- // Determine if there have been enough changes made to run the
optimizer ...
- int maxChanges = getMaxChangesBeforeAutomaticOptimization();
- if (maxChanges > 0 && numChanges >= maxChanges) {
- Activity optimizer = optimizeContent();
- try {
- optimizer.execute(session);
- } catch (ParseException e) {
- error = e;
- throw new SearchEngineException(optimizer.messageFor(e,
sourceName, workspaceName), e);
- } catch (IOException e) {
- error = e;
- throw new SearchEngineException(optimizer.messageFor(e,
sourceName, workspaceName), e);
- } catch (RuntimeException e) {
- error = e;
- throw e;
- }
- }
- }
- } finally {
- try {
- if (error == null) {
- session.commit();
- } else {
- session.rollback();
- }
- } catch (IOException e2) {
- // We don't want to lose the existing error, if there is one ...
- if (error == null) {
- I18n msg = SearchI18n.errorWhileCommittingIndexChanges;
- throw new SearchEngineException(msg.text(workspaceName,
sourceName, e2.getMessage()), e2);
- }
- }
- }
- }
- }
-
- /**
- * Create an activity that will optimize the indexes.
- *
- * @return the activity that will perform the work
- */
- protected Activity optimizeContent() {
- return new Activity() {
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.search.SearchEngine.Activity#execute(org.jboss.dna.search.IndexSession)
- */
- public int execute( IndexSession indexSession ) throws IOException {
- indexSession.optimize();
- return 0; // no lines changed
- }
-
- public String messageFor( Throwable error,
- String sourceName,
- String workspaceName ) {
- return SearchI18n.errorWhileOptimizingIndexes.text(sourceName,
workspaceName, error.getMessage());
- }
- };
- }
-
- /**
- * Create an activity that will read from the source the content at the supplied
location and add the content to the search
- * index.
- *
- * @param location the location of the content to read; may not be null
- * @param depthPerRead the depth of each read operation; always positive
- * @return the activity that will perform the work
- */
- protected Activity addContent( final Location location,
- final int depthPerRead ) {
- return new Activity() {
- public int execute( IndexSession indexSession ) throws IOException {
-
- // Create a queue that we'll use to walk the content ...
- LinkedList<Location> locationsToRead = new
LinkedList<Location>();
- locationsToRead.add(location);
- int count = 0;
-
- // Now read and index the content ...
- Graph graph = graph();
- graph.useWorkspace(indexSession.getWorkspaceName());
- while (!locationsToRead.isEmpty()) {
- Location location = locationsToRead.poll();
- if (location == null) continue;
- Subgraph subgraph =
graph.getSubgraphOfDepth(depthPerRead).at(location);
- // Index all of the nodes within this subgraph ...
- for (SubgraphNode node : subgraph) {
- // Index the node ...
- indexSession.index(node);
- ++count;
-
- // Process the children ...
- for (Location child : node.getChildren()) {
- if (!subgraph.includes(child)) {
- // Record this location as needing to be read ...
- locationsToRead.add(child);
- }
- }
- }
- }
- return count;
- }
-
- public String messageFor( Throwable error,
- String sourceName,
- String workspaceName ) {
- String path = readable(location.getPath());
- return SearchI18n.errorWhileIndexingContentAtPath.text(path,
workspaceName, sourceName, error.getMessage());
- }
- };
- }
-
- /**
- * Create an activity that will remove from the indexes all documents that represent
content at or below the specified
- * location.
- *
- * @param location the location of the content to removed; may not be null
- * @return the activity that will perform the work
- */
- protected Activity removeContent( final Location location ) {
- return new Activity() {
-
- public int execute( IndexSession indexSession ) throws IOException {
- // Delete the content at/below the path ...
- return indexSession.deleteBelow(location.getPath());
- }
-
- public String messageFor( Throwable error,
- String sourceName,
- String workspaceName ) {
- String path = readable(location.getPath());
- return SearchI18n.errorWhileRemovingContentAtPath.text(path,
workspaceName, sourceName, error.getMessage());
- }
- };
- }
-
- /**
- * Create an activity that will update the indexes with changes that were already
made to the content.
- *
- * @param changes the changes that have been made to the content; may not be null
- * @return the activity that will perform the work
- */
- protected Activity updateContent( final Iterable<ChangeRequest> changes ) {
- return new Activity() {
-
- public int execute( IndexSession indexSession ) throws IOException {
- return indexSession.apply(changes);
- }
-
- public String messageFor( Throwable error,
- String sourceName,
- String workspaceName ) {
- return SearchI18n.errorWhileUpdatingContent.text(workspaceName,
sourceName, error.getMessage());
- }
- };
- }
-
- /**
- * Create an activity that will perform a full-text search given the supplied query.
- *
- * @param context the context in which the search is to be performed; may not be
null
- * @param fullTextSearch the full-text search to be performed; may not be null
- * @param maxResults the maximum number of results that are to be returned; always
positive
- * @param offset the number of initial results to skip, or 0 if the first results are
to be returned
- * @return the activity that will perform the work; never null
- */
- protected Search searchContent( final ExecutionContext context,
- final String fullTextSearch,
- final int maxResults,
- final int offset ) {
- final List<Location> results = new ArrayList<Location>(maxResults);
- return new Search() {
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.search.SearchEngine.Activity#execute(org.jboss.dna.search.IndexSession)
- */
- public int execute( IndexSession session ) throws IOException, ParseException
{
- session.search(context, fullTextSearch, maxResults, offset, results);
- return 0;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.search.SearchEngine.Activity#messageFor(java.lang.Throwable,
java.lang.String,
- * java.lang.String)
- */
- public String messageFor( Throwable error,
- String sourceName,
- String workspaceName ) {
- return SearchI18n.errorWhilePerformingSearch.text(fullTextSearch,
workspaceName, sourceName, error.getMessage());
- }
-
- public List<Location> getResults() {
- return results;
- }
- };
- }
-
- /**
- * Create an activity that will perform a query against the index.
- *
- * @param context the context in which the search is to be performed; may not be
null
- * @param query the query to be performed; may not be null
- * @return the activity that will perform the query; never null
- */
- protected Query queryContent( final QueryContext context,
- final QueryCommand query ) {
- return new Query() {
- private QueryResults results = null;
-
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.search.SearchEngine.Activity#execute(org.jboss.dna.search.IndexSession)
- */
- public int execute( IndexSession session ) throws IOException, ParseException
{
- results = session.query(context, query);
- return 0;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see
org.jboss.dna.search.SearchEngine.Activity#messageFor(java.lang.Throwable,
java.lang.String,
- * java.lang.String)
- */
- public String messageFor( Throwable error,
- String sourceName,
- String workspaceName ) {
- return SearchI18n.errorWhilePerformingQuery.text(query, workspaceName,
sourceName, error.getMessage());
- }
-
- public QueryResults getResults() {
- return results;
- }
- };
- }
-
- /**
- * Interface for activities that will be executed against a workspace. These
activities don't have to commit or roll back the
- * writer, nor do they have to translate the exceptions, since this is done by the
- * {@link Workspace#execute(boolean, Activity...)} method.
- */
- protected interface Activity {
-
- /**
- * Perform the activity by using the index writer.
- *
- * @param indexSession the index session that should be used by the activity;
never null
- * @return the number of changes that were made by this activity
- * @throws IOException if there is an error using the writer
- * @throws ParseException if there is an error due to parsing
- */
- int execute( IndexSession indexSession ) throws IOException, ParseException;
-
- /**
- * Translate an exception obtained during {@link #execute(IndexSession)
execution} into a single message.
- *
- * @param t the exception
- * @param sourceName the name of the source
- * @param workspaceName the name of the workspace
- * @return the error message
- */
- String messageFor( Throwable t,
- String sourceName,
- String workspaceName );
- }
-
- /**
- * A read-only activity.
- */
- protected interface ReadOnlyActivity extends Activity {
- }
-
- /**
- * A search activity.
- */
- protected interface Search extends ReadOnlyActivity {
- /**
- * Get the results of the search.
- *
- * @return the list of {@link Location} objects for each node satisfying the
results; never null
- */
- List<Location> getResults();
- }
-
- /**
- * A query activity.
- */
- protected interface Query extends ReadOnlyActivity {
- /**
- * Get the results of the query.
- *
- * @return the results of a query; never null
- */
- QueryResults getResults();
- }
-
-}
Deleted: trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngineException.java
===================================================================
---
trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngineException.java 2009-11-18
19:37:57 UTC (rev 1328)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngineException.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -1,67 +0,0 @@
-/*
- * JBoss DNA (
http://www.jboss.org/dna)
- * See the COPYRIGHT.txt file distributed with this work for information
- * regarding copyright ownership. Some portions may be licensed
- * to Red Hat, Inc. under one or more contributor license agreements.
- * See the AUTHORS.txt file in the distribution for a full listing of
- * individual contributors.
- *
- * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
- * is licensed to you under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * JBoss DNA is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.search;
-
-/**
- * An exception that represents a problem within a search engine.
- */
-public class SearchEngineException extends RuntimeException {
-
- /**
- */
- private static final long serialVersionUID = 1L;
-
- /**
- *
- */
- public SearchEngineException() {
- }
-
- /**
- * @param message
- */
- public SearchEngineException( String message ) {
- super(message);
-
- }
-
- /**
- * @param cause
- */
- public SearchEngineException( Throwable cause ) {
- super(cause);
-
- }
-
- /**
- * @param message
- * @param cause
- */
- public SearchEngineException( String message,
- Throwable cause ) {
- super(message, cause);
-
- }
-
-}
Modified: trunk/dna-search/src/main/java/org/jboss/dna/search/SearchI18n.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/SearchI18n.java 2009-11-18
19:37:57 UTC (rev 1328)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/SearchI18n.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -28,25 +28,15 @@
import org.jboss.dna.common.CommonI18n;
import org.jboss.dna.common.i18n.I18n;
-/**
- *
- */
public class SearchI18n {
public static I18n locationForIndexesIsNotDirectory;
public static I18n locationForIndexesCannotBeRead;
public static I18n locationForIndexesCannotBeWritten;
- public static I18n errorWhileIndexingContentAtPath;
- public static I18n errorWhileRemovingContentAtPath;
- public static I18n errorWhileUpdatingContent;
public static I18n errorWhileCommittingIndexChanges;
public static I18n errorWhileRollingBackIndexChanges;
- public static I18n errorCreatingIndexWriter;
- public static I18n errorWhileOptimizingIndexes;
public static I18n errorWhilePerformingSearch;
public static I18n errorWhilePerformingQuery;
- public static I18n errorWhileInitializingSearchEngine;
- public static I18n errorWhileRemovingIndexesForWorkspace;
public static I18n errorWhilePerformingLuceneQuery;
static {
Deleted: trunk/dna-search/src/main/java/org/jboss/dna/search/filters/ResultFilter.java
===================================================================
---
trunk/dna-search/src/main/java/org/jboss/dna/search/filters/ResultFilter.java 2009-11-18
19:37:57 UTC (rev 1328)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/filters/ResultFilter.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -1,36 +0,0 @@
-/*
- * JBoss DNA (
http://www.jboss.org/dna)
- * See the COPYRIGHT.txt file distributed with this work for information
- * regarding copyright ownership. Some portions may be licensed
- * to Red Hat, Inc. under one or more contributor license agreements.
- * See the AUTHORS.txt file in the distribution for a full listing of
- * individual contributors.
- *
- * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
- * is licensed to you under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * JBoss DNA is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.search.filters;
-
-import org.jboss.dna.graph.query.QueryResults.Columns;
-
-/**
- *
- */
-public interface ResultFilter {
-
- boolean allows( Object[] tuple,
- Columns columns );
-
-}
Modified: trunk/dna-search/src/main/resources/org/jboss/dna/search/SearchI18n.properties
===================================================================
---
trunk/dna-search/src/main/resources/org/jboss/dna/search/SearchI18n.properties 2009-11-18
19:37:57 UTC (rev 1328)
+++
trunk/dna-search/src/main/resources/org/jboss/dna/search/SearchI18n.properties 2009-11-18
19:39:05 UTC (rev 1329)
@@ -26,15 +26,8 @@
locationForIndexesCannotBeRead = Location "{0}" cannot be used for search
indexes for workspace "{1}" because it cannot be read
locationForIndexesCannotBeWritten = Location "{0}" cannot be used for search
indexes for workspace "{1}" because its contents cannot be written or updated
-errorWhileIndexingContentAtPath = Error while indexing the content at "{0}" in
the "{1}" workspace of the "{2}" source: {3}
-errorWhileRemovingContentAtPath = Error while removing the content at/below
"{0}" in the "{1}" workspace of the "{2}" source: {3}
-errorWhileUpdatingContent = Error while updating content in the "{0}" workspace
of the "{1}" source: {2}
errorWhileCommittingIndexChanges = Error while committing changes to the indexes for the
"{0}" workspace of the "{1}" source: {2}
errorWhileRollingBackIndexChanges = Error while rolling back changes to the indexes for
the "{0}" workspace of the "{1}" source: {2}
-errorCreatingIndexWriter = Error attempting to create an index writer for the
"{0}" index for the "{1}" workspace of the "{2}" source:
{3}
-errorWhileOptimizingIndexes = Error while optimizing the indexes for the "{0}"
workspace of the "{1}" source: {2}
-errorWhilePerformingSearch = Error while searching for "{0}" in the
"{1}" workspace of the "{2}" source: {3}
+errorWhilePerformingSearch = Error while searching the "{0}" workspace in the
"{1}" source for "{2}": {3}
errorWhilePerformingQuery = Error while performing the query "{0}" against the
content in the "{1}" workspace of the "{2}" source: {3}
-errorWhileInitializingSearchEngine = Error while initializing the search engine for the
"{0}" workspace of the "{1}" source: {2}
-errorWhileRemovingIndexesForWorkspace = Error while removing the indexes for the
"{0}" workspace of the "{1}" source: {2}
errorWhilePerformingLuceneQuery = Error while performing the Lucene query "{0}"
as part of the "{1}" query against the "{2}" workspace of the
"{3}" source: {4}
\ No newline at end of file
Deleted: trunk/dna-search/src/test/java/org/jboss/dna/search/SearchEngineTest.java
===================================================================
--- trunk/dna-search/src/test/java/org/jboss/dna/search/SearchEngineTest.java 2009-11-18
19:37:57 UTC (rev 1328)
+++ trunk/dna-search/src/test/java/org/jboss/dna/search/SearchEngineTest.java 2009-11-18
19:39:05 UTC (rev 1329)
@@ -1,353 +0,0 @@
-/*
- * JBoss DNA (
http://www.jboss.org/dna)
- * See the COPYRIGHT.txt file distributed with this work for information
- * regarding copyright ownership. Some portions may be licensed
- * to Red Hat, Inc. under one or more contributor license agreements.
- * See the AUTHORS.txt file in the distribution for a full listing of
- * individual contributors.
- *
- * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
- * is licensed to you under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * JBoss DNA is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.search;
-
-import static org.hamcrest.core.Is.is;
-import static org.hamcrest.core.IsNull.notNullValue;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.stub;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import java.util.List;
-import org.jboss.dna.graph.ExecutionContext;
-import org.jboss.dna.graph.Graph;
-import org.jboss.dna.graph.Location;
-import org.jboss.dna.graph.Node;
-import org.jboss.dna.graph.connector.RepositoryConnection;
-import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
-import org.jboss.dna.graph.connector.RepositorySourceException;
-import org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource;
-import org.jboss.dna.graph.property.Path;
-import org.jboss.dna.graph.query.QueryContext;
-import org.jboss.dna.graph.query.model.Query;
-import org.jboss.dna.graph.query.validate.Schemata;
-import org.jboss.dna.graph.request.InvalidWorkspaceException;
-import org.jboss.dna.search.IndexLayout;
-import org.jboss.dna.search.IndexSession;
-import org.jboss.dna.search.SearchEngine;
-import org.junit.Before;
-import org.junit.Test;
-
-public class SearchEngineTest {
-
- private SearchEngine engine;
- private ExecutionContext context;
- private String sourceName;
- private String workspaceName1;
- private String workspaceName2;
- private InMemoryRepositorySource source;
- private RepositoryConnectionFactory connectionFactory;
- private IndexLayout layout;
- private IndexSession sessionWs1;
- private IndexSession sessionWs2;
- private IndexSession sessionDefault;
- private Graph content;
-
- @Before
- public void beforeEach() throws Exception {
- context = new ExecutionContext();
- sourceName = "sourceA";
- workspaceName1 = "workspace1";
- workspaceName2 = "workspace2";
-
- // Set up the source and graph instance ...
- source = new InMemoryRepositorySource();
- source.setName(sourceName);
- content = Graph.create(source, context);
-
- // Create the workspaces ...
- content.createWorkspace().named(workspaceName1);
- content.createWorkspace().named(workspaceName2);
-
- // Set up the connection factory ...
- connectionFactory = new RepositoryConnectionFactory() {
- @SuppressWarnings( "synthetic-access" )
- public RepositoryConnection createConnection( String sourceName ) throws
RepositorySourceException {
- return source.getConnection();
- }
- };
-
- // Set up the index layout ...
- layout = mock(IndexLayout.class);
- sessionWs1 = mockSession(layout, workspaceName1);
- sessionWs2 = mockSession(layout, workspaceName2);
- sessionDefault = mockSession(layout, "");
-
- // Now set up the search engine ...
- engine = new SearchEngine(context, sourceName, connectionFactory, layout);
- }
-
- protected IndexSession mockSession( IndexLayout mockLayout,
- String workspaceName ) {
- IndexSession session = mock(IndexSession.class);
- stub(layout.createSession(context, sourceName, workspaceName, false,
false)).toReturn(session);
- stub(layout.createSession(context, sourceName, workspaceName, false,
true)).toReturn(session);
- stub(layout.createSession(context, sourceName, workspaceName, true,
false)).toReturn(session);
- stub(layout.createSession(context, sourceName, workspaceName, true,
true)).toReturn(session);
- stub(session.getWorkspaceName()).toReturn(workspaceName);
- stub(session.getSourceName()).toReturn(sourceName);
- return session;
- }
-
- protected Path path( String path ) {
- return context.getValueFactories().getPathFactory().create(path);
- }
-
- protected void loadContent() throws Exception {
- // Load some content ...
- content.useWorkspace(workspaceName1);
-
content.importXmlFrom(getClass().getClassLoader().getResourceAsStream("cars.xml")).into("/");
- content.useWorkspace(workspaceName2);
-
content.importXmlFrom(getClass().getClassLoader().getResourceAsStream("aircraft.xml")).into("/");
- }
-
- @Test
- public void shouldReturnSearchWorkspaceForExistingWorkspaceInSource() {
- SearchEngine.Workspace workspace = engine.getWorkspace(workspaceName1);
- assertThat(workspace, is(notNullValue()));
- assertThat(workspace.modifiedNodesSinceLastOptimize.get(), is(0));
- assertThat(workspace.getWorkspaceName(), is(workspaceName1));
- }
-
- @Test( expected = InvalidWorkspaceException.class )
- public void shouldFailToReturnSearchWorkspaceForNonExistantWorkspaceInSource() {
- engine.getWorkspace(workspaceName1 + "foobar");
- }
-
- @Test
- public void shouldDoNothingDuringRemoveWorkspaceIfWorkspaceHasNotBeenLoaded() throws
Exception {
- engine.removeWorkspace(workspaceName1);
- verifyZeroInteractions(layout);
- }
-
- @Test
- public void shouldForwardRemoveWorkspaceToIndexLayout() throws Exception {
- engine.getWorkspace(workspaceName1);
- engine.removeWorkspace(workspaceName1);
- verify(layout).destroyIndexes(context, sourceName, workspaceName1);
- verifyNoMoreInteractions(layout);
- }
-
- @Test
- public void shouldForwardRemoveWorkspaceToIndexLayoutForEachWorkspaceThatWasLoaded()
throws Exception {
- engine.getWorkspace(workspaceName1);
- engine.removeWorkspaces();
- verify(layout).destroyIndexes(context, sourceName, workspaceName1);
- verifyZeroInteractions(layout);
- }
-
- @Test
- public void shouldForwardRemoveWorkspaceToIndexLayoutForAllWorkspacesThatWereLoaded()
throws Exception {
- engine.getWorkspace(workspaceName1);
- engine.getWorkspace(workspaceName2);
- engine.removeWorkspaces();
- verify(layout).destroyIndexes(context, sourceName, workspaceName1);
- verify(layout).destroyIndexes(context, sourceName, workspaceName2);
- verifyNoMoreInteractions(layout);
- }
-
- @Test( expected = IllegalArgumentException.class )
- public void shouldFailIfNullWorkspaceNamePassedToRemoveWorkspace() throws Exception
{
- engine.removeWorkspace(null);
- }
-
- @Test
- public void shouldForwardOptimizeOfWorkspaceToIndexSession() throws Exception {
- engine.optimize(workspaceName1);
- verify(sessionWs1).optimize();
- verify(sessionWs1).commit();
- verifyNoMoreInteractions(sessionWs1);
- }
-
- @Test
- public void shouldForwardOptimizeOfAllWorkspacesToEachIndexSession() throws Exception
{
- engine.optimize(); // will find all three workspaces
- verify(sessionWs1).optimize();
- verify(sessionWs1).commit();
- verifyNoMoreInteractions(sessionWs1);
- verify(sessionWs2).optimize();
- verify(sessionWs2).commit();
- verifyNoMoreInteractions(sessionWs2);
- verify(sessionDefault).optimize();
- verify(sessionDefault).commit();
- verifyNoMoreInteractions(sessionDefault);
- }
-
- @Test
- public void shouldForwardIndexOfWorkspaceToIndexSession() throws Exception {
- loadContent();
- engine.index(workspaceName1, 3);
- verify(sessionWs1, times(18)).index((Node)anyObject());
- verify(sessionWs1).commit();
- }
-
- @Test
- public void shouldForwardIndexOfSubgraphInWorkspaceToIndexSession() throws Exception
{
- loadContent();
- engine.index(workspaceName1, path("/Cars"), 3);
- verify(sessionWs1).deleteBelow(path("/Cars"));
- verify(sessionWs1, times(17)).index((Node)anyObject());
- verify(sessionWs1).commit();
- }
-
- @Test
- public void shouldForwardIndexEntireWorkspaceToIndexSession() throws Exception {
- loadContent();
- engine.index(workspaceName1, path("/"), 3);
- verify(sessionWs1, times(18)).index((Node)anyObject());
- verify(sessionWs1).commit();
- }
-
- @Test
- public void shouldForwardIndexOfAllWorkspacesToEachIndexSession() throws Exception {
- loadContent();
- engine.index(3); // will find all three workspaces
- verify(sessionWs1, times(18)).index((Node)anyObject());
- verify(sessionWs1).commit();
- verify(sessionWs2, times(24)).index((Node)anyObject());
- verify(sessionWs2).commit();
- verify(sessionDefault, times(1)).index((Node)anyObject());
- verify(sessionDefault).commit();
- }
-
- @SuppressWarnings( "unchecked" )
- @Test
- public void shouldForwardSearchToIndexSession() throws Exception {
- String query = "term1 term2";
- engine.fullTextSearch(context, workspaceName1, query, 3, 0);
- verify(sessionWs1).search(eq(context), eq(query), eq(3), eq(0),
(List<Location>)anyObject());
- verify(sessionWs1).commit();
- }
-
- @Test
- public void shouldForwardQueryToIndexSession() throws Exception {
- Query query = mock(Query.class);
- Schemata schemata = mock(Schemata.class);
- engine.query(context, workspaceName1, query, schemata);
- verify(sessionWs1).query(eq(new QueryContext(context, schemata)), eq(query));
- verify(sessionWs1).commit();
- }
-
- // These tests expect there to be some real IndexLayout ...
-
- // @Test
- // public void shouldIndexAllContentInRepositorySource() throws Exception {
- // loadContent();
- // engine.index(3);
- // }
- //
- // @Test
- // public void shouldIndexAllContentInWorkspace() throws Exception {
- // loadContent();
- // engine.index(workspaceName1, 3);
- // engine.index(workspaceName2, 5);
- // }
- //
- // @Test
- // public void shouldIndexAllContentInWorkspaceBelowPath() throws Exception {
- // loadContent();
- // engine.index(workspaceName1, path("/Cars/Hybrid"), 3);
- // engine.index(workspaceName2, path("/Aircraft/Commercial"), 5);
- // }
- //
- // @Test
- // public void shouldReIndexAllContentInWorkspaceBelowPath() throws Exception {
- // loadContent();
- // for (int i = 0; i != 0; i++) {
- // engine.index(workspaceName1, path("/Cars/Hybrid"), 3);
- // engine.index(workspaceName2, path("/Aircraft/Commercial"), 5);
- // }
- // }
- //
- // @Test
- // public void shouldHaveLoadedTestContentIntoRepositorySource() {
- // assertThat(content.getNodeAt("/Cars/Hybrid/Toyota
Prius").getProperty("msrp").getFirstValue(),
is((Object)"$21,500"));
- // }
- //
- // @Test
- // public void shouldIndexRepositoryContentStartingAtRootAndUsingDepthOfOne() {
- // engine.index(workspaceName1, path("/"), 1);
- // }
- //
- // @Test
- // public void shouldIndexRepositoryContentStartingAtRootAndUsingDepthOfTwo() {
- // engine.index(workspaceName1, path("/"), 2);
- // }
- //
- // @Test
- // public void shouldIndexRepositoryContentStartingAtRootAndUsingDepthOfThree() {
- // engine.index(workspaceName1, path("/"), 3);
- // }
- //
- // @Test
- // public void shouldIndexRepositoryContentStartingAtRootAndUsingDepthOfFour() {
- // engine.index(workspaceName1, path("/"), 4);
- // }
- //
- // @Test
- // public void shouldIndexRepositoryContentStartingAtRootAndUsingDepthOfTen() {
- // engine.index(workspaceName1, path("/"), 10);
- // }
- //
- // @Test
- // public void shouldIndexRepositoryContentStartingAtNonRootNode() {
- // engine.index(workspaceName1, path("/Cars"), 10);
- // }
- //
- // @Test
- // public void shouldReIndexRepositoryContentStartingAtNonRootNode() {
- // for (int i = 0; i != 3; ++i) {
- // engine.index(workspaceName1, path("/Cars"), 10);
- // }
- // }
- //
- // @Test
- // public void shouldFindNodesByFullTextSearch() {
- // engine.index(workspaceName1, path("/"), 100);
- // List<Location> results = engine.fullTextSearch(context, workspaceName1,
"Toyota Prius", 10, 0);
- // assertThat(results, is(notNullValue()));
- // assertThat(results.size(), is(2));
- // assertThat(results.get(0).getPath(), is(path("/Cars/Hybrid/Toyota
Prius")));
- // assertThat(results.get(1).getPath(), is(path("/Cars/Hybrid/Toyota
Highlander")));
- // }
- //
- // @Test
- // public void shouldFindNodesByFullTextSearchWithOffset() {
- // engine.index(workspaceName1, path("/"), 100);
- // List<Location> results = engine.fullTextSearch(context, workspaceName1,
"toyota prius", 1, 0);
- // assertThat(results, is(notNullValue()));
- // assertThat(results.size(), is(1));
- // assertThat(results.get(0).getPath(), is(path("/Cars/Hybrid/Toyota
Prius")));
- //
- // results = engine.fullTextSearch(context, workspaceName1, "+Toyota", 1,
1);
- // assertThat(results, is(notNullValue()));
- // assertThat(results.size(), is(1));
- // assertThat(results.get(0).getPath(), is(path("/Cars/Hybrid/Toyota
Highlander")));
- // }
-}