View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors.
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   * 
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.graph.search;
25  
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.HashMap;
29  import java.util.Map;
30  import java.util.concurrent.locks.ReadWriteLock;
31  import java.util.concurrent.locks.ReentrantReadWriteLock;
32  import net.jcip.annotations.GuardedBy;
33  import net.jcip.annotations.ThreadSafe;
34  import org.modeshape.common.util.CheckArg;
35  import org.modeshape.graph.ExecutionContext;
36  import org.modeshape.graph.Graph;
37  import org.modeshape.graph.GraphI18n;
38  import org.modeshape.graph.connector.RepositoryConnectionFactory;
39  import org.modeshape.graph.connector.RepositorySource;
40  import org.modeshape.graph.connector.RepositorySourceException;
41  import org.modeshape.graph.observe.Observer;
42  import org.modeshape.graph.request.InvalidWorkspaceException;
43  
44  /**
45   * A component that acts as a search engine for the content within a single {@link RepositorySource}. This engine manages a set of
46   * indexes and provides search functionality for each of the workspaces within the source, and provides various methods to
47   * (re)index the content contained with source's workspaces and keep the indexes up-to-date via changes.
48   * 
49   * @param <WorkspaceType> the workspace type
50   * @param <ProcessorType> the processor type
51   */
52  @ThreadSafe
53  public abstract class AbstractSearchEngine<WorkspaceType extends SearchEngineWorkspace, ProcessorType extends SearchEngineProcessor>
54      implements SearchEngine {
55  
56      public static final boolean DEFAULT_VERIFY_WORKSPACE_IN_SOURCE = false;
57  
58      private final boolean verifyWorkspaceInSource;
59      private final RepositoryConnectionFactory connectionFactory;
60      private final String sourceName;
61      private volatile Workspaces<WorkspaceType> workspaces;
62  
63      /**
64       * Create a new provider instance that can be used to manage the indexes for the workspaces in a single source.
65       * 
66       * @param sourceName the name of the source that can be searched; never null
67       * @param connectionFactory the connection factory; may be null if the engine can operate without connecting to the source
68       */
69      protected AbstractSearchEngine( String sourceName,
70                                      RepositoryConnectionFactory connectionFactory ) {
71          this(sourceName, connectionFactory, DEFAULT_VERIFY_WORKSPACE_IN_SOURCE);
72      }
73  
74      /**
75       * Create a new provider instance that can be used to manage the indexes for the workspaces in a single source.
76       * 
77       * @param sourceName the name of the source that can be searched; never null
78       * @param connectionFactory the connection factory; may be null if the engine can operate without connecting to the source
79       * @param verifyWorkspaceInSource true if the workspaces are to be verified by checking the original source
80       * @throws IllegalArgumentException if any of the parameters are null
81       */
82      protected AbstractSearchEngine( String sourceName,
83                                      RepositoryConnectionFactory connectionFactory,
84                                      boolean verifyWorkspaceInSource ) {
85          CheckArg.isNotNull(sourceName, "sourceName");
86          CheckArg.isNotNull(connectionFactory, "connectionFactory");
87          this.sourceName = sourceName;
88          this.connectionFactory = connectionFactory;
89          this.verifyWorkspaceInSource = verifyWorkspaceInSource;
90          this.workspaces = new SearchWorkspaces(connectionFactory);
91      }
92  
93      /**
94       * @return connectionFactory
95       */
96      protected RepositoryConnectionFactory getConnectionFactory() {
97          return connectionFactory;
98      }
99  
100     /**
101      * {@inheritDoc}
102      * 
103      * @see org.modeshape.graph.search.SearchEngine#getSourceName()
104      */
105     public String getSourceName() {
106         return sourceName;
107     }
108 
109     /**
110      * Determine whether the workspaces should be verified with the original source before creating indexes for them.
111      * 
112      * @return true if verification should be performed, or false otherwise
113      */
114     public boolean isVerifyWorkspaceInSource() {
115         return verifyWorkspaceInSource;
116     }
117 
118     /**
119      * Obtain a graph to the source for which this engine exists.
120      * 
121      * @param context the context in which the graph operations should be performed; never null
122      * @return the graph; never null
123      * @throws RepositorySourceException if a connection to the source cannot be established
124      */
125     protected Graph graph( ExecutionContext context ) {
126         assert context != null;
127         return Graph.create(sourceName, connectionFactory, context);
128     }
129 
130     /**
131      * Create the index(es) required for the named workspace.
132      * 
133      * @param context the context in which the operation is to be performed; may not be null
134      * @param workspaceName the name of the workspace; may not be null
135      * @return the workspace; never null
136      * @throws SearchEngineException if there is a problem creating the workspace.
137      */
138     protected abstract WorkspaceType createWorkspace( ExecutionContext context,
139                                                       String workspaceName ) throws SearchEngineException;
140 
141     /**
142      * Create the {@link SearchEngineProcessor} implementation that can be used to operate against the
143      * {@link SearchEngineWorkspace} instances.
144      * <p>
145      * Note that the resulting processor must be {@link SearchEngineProcessor#close() closed} by the caller when completed.
146      * </p>
147      * 
148      * @param context the context in which the processor is to be used; never null
149      * @param workspaces the set of existing search workspaces; never null
150      * @param observer the observer of any events created by the processor; may be null
151      * @param readOnly true if the processor will only be reading or searching, or false if the processor will be used to update
152      *        the workspaces
153      * @return the processor; may not be null
154      */
155     protected abstract ProcessorType createProcessor( ExecutionContext context,
156                                                       Workspaces<WorkspaceType> workspaces,
157                                                       Observer observer,
158                                                       boolean readOnly );
159 
160     /**
161      * {@inheritDoc}
162      * 
163      * @see org.modeshape.graph.search.SearchEngine#createProcessor(org.modeshape.graph.ExecutionContext,
164      *      org.modeshape.graph.observe.Observer, boolean)
165      */
166     public SearchEngineProcessor createProcessor( ExecutionContext context,
167                                                   Observer observer,
168                                                   boolean readOnly ) {
169         return createProcessor(context, workspaces, observer, readOnly);
170     }
171 
172     public interface Workspaces<WorkspaceType extends SearchEngineWorkspace> {
173         /**
174          * Get the connection factory for repository sources.
175          * 
176          * @return the connection factory; never null
177          */
178         RepositoryConnectionFactory getRepositoryConnectionFactory();
179 
180         /**
181          * Get the search engine for the workspace with the supplied name.
182          * 
183          * @param context the execution context; never null
184          * @param workspaceName the name of the workspace; never null
185          * @param createIfMissing true if the workspace should be created if missing, or false otherwise
186          * @return the workspace's search engine
187          * @throws InvalidWorkspaceException if the workspace does not exist
188          */
189         WorkspaceType getWorkspace( ExecutionContext context,
190                                     String workspaceName,
191                                     boolean createIfMissing );
192 
193         /**
194          * Get the existing workspaces.
195          * 
196          * @return the workspaces
197          */
198         Collection<WorkspaceType> getWorkspaces();
199 
200         /**
201          * Remove the supplied workspace from the search engine. This is typically done when the workspace is being deleted. Note
202          * that the resulting Workspace needs to then be cleaned up by the caller.
203          * 
204          * @param workspaceName the name of the workspace
205          * @return the workspace that was removed, or null if there was workspace with the supplied name
206          */
207         WorkspaceType removeWorkspace( String workspaceName );
208 
209         /**
210          * Remove from the search engine all workspace-related indexes, thereby cleaning up any resources used by this search
211          * engine.
212          * 
213          * @return the mutable map containing the {@link SearchEngineWorkspace} objects keyed by their name; never null but
214          *         possibly empty
215          */
216         Map<String, WorkspaceType> removeAllWorkspaces();
217     }
218 
219     protected class SearchWorkspaces implements Workspaces<WorkspaceType> {
220         private final ReadWriteLock workspacesLock = new ReentrantReadWriteLock();
221         @GuardedBy( "workspacesLock" )
222         private final Map<String, WorkspaceType> workspacesByName = new HashMap<String, WorkspaceType>();
223         private final RepositoryConnectionFactory connectionFactory;
224 
225         protected SearchWorkspaces( RepositoryConnectionFactory connectionFactory ) {
226             this.connectionFactory = connectionFactory;
227         }
228 
229         /**
230          * {@inheritDoc}
231          * 
232          * @see AbstractSearchEngine.Workspaces#getRepositoryConnectionFactory()
233          */
234         public RepositoryConnectionFactory getRepositoryConnectionFactory() {
235             return connectionFactory;
236         }
237 
238         /**
239          * {@inheritDoc}
240          * 
241          * @see AbstractSearchEngine.Workspaces#getWorkspace(org.modeshape.graph.ExecutionContext, java.lang.String, boolean)
242          */
243         public WorkspaceType getWorkspace( ExecutionContext context,
244                                            String workspaceName,
245                                            boolean createIfMissing ) {
246             assert context != null;
247             assert workspaceName != null;
248             WorkspaceType workspace = null;
249             try {
250                 workspacesLock.readLock().lock();
251                 workspace = workspacesByName.get(workspaceName);
252             } finally {
253                 workspacesLock.readLock().unlock();
254             }
255 
256             if (workspace == null) {
257                 // Verify the workspace does exist ...
258                 if (isVerifyWorkspaceInSource() && connectionFactory != null
259                     && !graph(context).getWorkspaces().contains(workspaceName)) {
260                     String msg = GraphI18n.workspaceDoesNotExistInRepository.text(workspaceName, getSourceName());
261                     throw new InvalidWorkspaceException(msg);
262                 }
263                 try {
264                     workspacesLock.writeLock().lock();
265                     // Check whether another thread got in and created the engine while we waited ...
266                     workspace = workspacesByName.get(workspaceName);
267                     if (workspace == null) {
268                         // Create the engine and register it ...
269                         workspace = createWorkspace(context, workspaceName);
270                         workspacesByName.put(workspaceName, workspace);
271                     }
272                 } finally {
273                     workspacesLock.writeLock().unlock();
274                 }
275             }
276             return workspace;
277         }
278 
279         /**
280          * {@inheritDoc}
281          * 
282          * @see AbstractSearchEngine.Workspaces#getWorkspaces()
283          */
284         public Collection<WorkspaceType> getWorkspaces() {
285             try {
286                 workspacesLock.writeLock().lock();
287                 return new ArrayList<WorkspaceType>(workspacesByName.values());
288             } finally {
289                 workspacesByName.clear();
290                 workspacesLock.writeLock().unlock();
291             }
292         }
293 
294         /**
295          * {@inheritDoc}
296          * 
297          * @see AbstractSearchEngine.Workspaces#removeWorkspace(java.lang.String)
298          */
299         public WorkspaceType removeWorkspace( String workspaceName ) {
300             CheckArg.isNotNull(workspaceName, "workspaceName");
301             try {
302                 workspacesLock.writeLock().lock();
303                 // Check whether another thread got in and created the engine while we waited ...
304                 return workspacesByName.remove(workspaceName);
305             } finally {
306                 workspacesLock.writeLock().unlock();
307             }
308         }
309 
310         /**
311          * {@inheritDoc}
312          * 
313          * @see AbstractSearchEngine.Workspaces#removeAllWorkspaces()
314          */
315         public Map<String, WorkspaceType> removeAllWorkspaces() {
316             try {
317                 workspacesLock.writeLock().lock();
318                 return new HashMap<String, WorkspaceType>(workspacesByName);
319             } finally {
320                 workspacesByName.clear();
321                 workspacesLock.writeLock().unlock();
322             }
323         }
324     }
325 }