View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors.
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   * 
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.search.lucene;
25  
26  import java.io.File;
27  import java.io.IOException;
28  import java.util.concurrent.ConcurrentHashMap;
29  import net.jcip.annotations.Immutable;
30  import net.jcip.annotations.ThreadSafe;
31  import org.apache.lucene.store.Directory;
32  import org.apache.lucene.store.FSDirectory;
33  import org.apache.lucene.store.LockFactory;
34  import org.apache.lucene.store.RAMDirectory;
35  import org.apache.lucene.util.Version;
36  import org.modeshape.common.i18n.I18n;
37  import org.modeshape.common.text.NoOpEncoder;
38  import org.modeshape.common.text.TextEncoder;
39  import org.modeshape.common.util.CheckArg;
40  import org.modeshape.common.util.FileUtil;
41  import org.modeshape.common.util.HashCode;
42  import org.modeshape.common.util.Logger;
43  import org.modeshape.graph.search.SearchEngineException;
44  
45  /**
46   * A family of {@link LuceneConfiguration} implementations.
47   */
48  public class LuceneConfigurations {
49  
50      /**
51       * Return a new {@link LuceneConfiguration} that creates in-memory directories.
52       * 
53       * @return the new directory configuration; never null
54       */
55      public static final LuceneConfiguration inMemory() {
56          return new RamDirectoryFactory();
57      }
58  
59      /**
60       * Return a new {@link LuceneConfiguration} that creates {@link FSDirectory} instances mapped to folders under a parent
61       * folder, where the workspace name is used to create the workspace folder. Note that this has ramifications on the allowable
62       * workspace names.
63       * 
64       * @param parent the parent folder
65       * @return the new directory configuration; never null
66       * @throws IllegalArgumentException if the parent file is null
67       */
68      public static final LuceneConfiguration using( File parent ) {
69          CheckArg.isNotNull(parent, "parent");
70          return new FileSystemDirectoryFromNameFactory(parent);
71      }
72  
73      /**
74       * Return a new {@link LuceneConfiguration} that creates {@link FSDirectory} instances mapped to folders under a parent
75       * folder, where the workspace name is used to create the workspace folder. Note that this has ramifications on the allowable
76       * workspace names.
77       * 
78       * @param parent the parent folder
79       * @param lockFactory the lock factory; may be null
80       * @return the new directory configuration; never null
81       * @throws IllegalArgumentException if the parent file is null
82       */
83      public static final LuceneConfiguration using( File parent,
84                                                     LockFactory lockFactory ) {
85          CheckArg.isNotNull(parent, "parent");
86          return new FileSystemDirectoryFromNameFactory(parent, lockFactory);
87      }
88  
89      /**
90       * Return a new {@link LuceneConfiguration} that creates {@link FSDirectory} instances mapped to folders under a parent
91       * folder, where the workspace name is used to create the workspace folder. Note that this has ramifications on the allowable
92       * workspace names.
93       * 
94       * @param parent the parent folder
95       * @param workspaceNameEncoder the encoder that should be used for encoding the workspace name into a directory name
96       * @param indexNameEncoder the encoder that should be used for encoding the index name into a directory name
97       * @return the new directory configuration; never null
98       * @throws IllegalArgumentException if the parent file is null
99       */
100     public static final LuceneConfiguration using( File parent,
101                                                    TextEncoder workspaceNameEncoder,
102                                                    TextEncoder indexNameEncoder ) {
103         CheckArg.isNotNull(parent, "parent");
104         return new FileSystemDirectoryFromNameFactory(parent, workspaceNameEncoder, indexNameEncoder);
105     }
106 
107     /**
108      * Return a new {@link LuceneConfiguration} that creates {@link FSDirectory} instances mapped to folders under a parent
109      * folder, where the workspace name is used to create the workspace folder. Note that this has ramifications on the allowable
110      * workspace names.
111      * 
112      * @param parent the parent folder
113      * @param lockFactory the lock factory; may be null
114      * @param workspaceNameEncoder the encoder that should be used for encoding the workspace name into a directory name
115      * @param indexNameEncoder the encoder that should be used for encoding the index name into a directory name
116      * @return the new directory configuration; never null
117      * @throws IllegalArgumentException if the parent file is null
118      */
119     public static final LuceneConfiguration using( File parent,
120                                                    LockFactory lockFactory,
121                                                    TextEncoder workspaceNameEncoder,
122                                                    TextEncoder indexNameEncoder ) {
123         CheckArg.isNotNull(parent, "parent");
124         return new FileSystemDirectoryFromNameFactory(parent, lockFactory, workspaceNameEncoder, indexNameEncoder);
125     }
126 
127     /**
128      * A {@link LuceneConfiguration} implementation that creates {@link Directory} instances of the supplied type for each
129      * workspace and pools the results, ensuring that the same {@link Directory} instance is always returned for the same
130      * workspace name.
131      * 
132      * @param <DirectoryType> the concrete type of the directory
133      */
134     @ThreadSafe
135     protected static abstract class PoolingDirectoryFactory<DirectoryType extends Directory> implements LuceneConfiguration {
136         private final ConcurrentHashMap<IndexId, DirectoryType> directories = new ConcurrentHashMap<IndexId, DirectoryType>();
137 
138         /**
139          * {@inheritDoc}
140          * 
141          * @see LuceneConfiguration#getDirectory(java.lang.String, java.lang.String)
142          */
143         public Directory getDirectory( String workspaceName,
144                                        String indexName ) throws SearchEngineException {
145             CheckArg.isNotNull(workspaceName, "workspaceName");
146             IndexId id = new IndexId(workspaceName, indexName);
147             DirectoryType result = directories.get(id);
148             if (result == null) {
149                 DirectoryType newDirectory = createDirectory(workspaceName, indexName);
150                 result = directories.putIfAbsent(id, newDirectory);
151                 if (result == null) result = newDirectory;
152             }
153             return result;
154         }
155 
156         /**
157          * {@inheritDoc}
158          * 
159          * @see LuceneConfiguration#destroyDirectory(java.lang.String, java.lang.String)
160          */
161         public boolean destroyDirectory( String workspaceName,
162                                          String indexName ) throws SearchEngineException {
163             CheckArg.isNotNull(workspaceName, "workspaceName");
164             IndexId id = new IndexId(workspaceName, indexName);
165             DirectoryType result = directories.remove(id);
166             return result != null ? doDestroy(result) : false;
167         }
168 
169         /**
170          * Method implemented by subclasses to create a new Directory implementation.
171          * 
172          * @param workspaceName the name of the workspace for which the {@link Directory} is to be created; never null
173          * @param indexName the name of the index to be created
174          * @return the new directory; may not be null
175          * @throws SearchEngineException if there is a problem creating the directory
176          */
177         protected abstract DirectoryType createDirectory( String workspaceName,
178                                                           String indexName ) throws SearchEngineException;
179 
180         protected abstract boolean doDestroy( DirectoryType directory ) throws SearchEngineException;
181     }
182 
183     /**
184      * A {@link LuceneConfiguration} implementation that creates {@link RAMDirectory} instances for each workspace and index name.
185      * Each factory instance maintains a pool of {@link RAMDirectory} instances, ensuring that the same {@link RAMDirectory} is
186      * always returned for the same workspace name.
187      */
188     @ThreadSafe
189     public static class RamDirectoryFactory extends PoolingDirectoryFactory<RAMDirectory> {
190         protected RamDirectoryFactory() {
191         }
192 
193         /**
194          * {@inheritDoc}
195          * 
196          * @see org.modeshape.search.lucene.LuceneConfiguration#getVersion()
197          */
198         @Override
199         public Version getVersion() {
200             return Version.LUCENE_30;
201         }
202 
203         @Override
204         protected RAMDirectory createDirectory( String workspaceName,
205                                                 String indexName ) {
206             return new RAMDirectory();
207         }
208 
209         /**
210          * {@inheritDoc}
211          * 
212          * @see LuceneConfigurations.PoolingDirectoryFactory#doDestroy(org.apache.lucene.store.Directory)
213          */
214         @Override
215         protected boolean doDestroy( RAMDirectory directory ) throws SearchEngineException {
216             return directory != null;
217         }
218     }
219 
220     /**
221      * A {@link LuceneConfiguration} implementation that creates {@link FSDirectory} instances for each workspace and index name.
222      * This factory is created with a parent directory under which all workspace and index directories are created.
223      * <p>
224      * This uses the supplied encoders to translate the workspace and index names into valid directory names. By default, no
225      * encoding is performed, meaning that the workspace and index names are used explicitly as directory names. This default
226      * behavior, then, means that not all values of workspace names or index names will work. If you want to be sure that all
227      * workspace names work, supply an encoder for the workspace names. (Index names are currently such that they will always be
228      * valid directory names, but you can always supply an encoder if you'd like.)
229      * </p>
230      */
231     public static class FileSystemDirectoryFromNameFactory extends PoolingDirectoryFactory<FSDirectory> {
232         private final File parentFile;
233         private final LockFactory lockFactory;
234         private final TextEncoder workspaceNameEncoder;
235         private final TextEncoder indexNameEncoder;
236 
237         /**
238          * Create a new {@link LuceneConfiguration} that creates {@link FSDirectory} instances mapped to folders under a parent
239          * folder, where the workspace name is used to create the workspace folder. Note that this has ramifications on the
240          * allowable workspace names.
241          * 
242          * @param parent the parent folder
243          * @throws IllegalArgumentException if the parent file is null
244          */
245         protected FileSystemDirectoryFromNameFactory( File parent ) {
246             this(parent, null, null, null);
247         }
248 
249         /**
250          * Create a new {@link LuceneConfiguration} that creates {@link FSDirectory} instances mapped to folders under a parent
251          * folder, where the workspace name is used to create the workspace folder. Note that this has ramifications on the
252          * allowable workspace names.
253          * 
254          * @param parent the parent folder
255          * @param lockFactory the lock factory; may be null
256          * @throws IllegalArgumentException if the parent file is null
257          */
258         protected FileSystemDirectoryFromNameFactory( File parent,
259                                                       LockFactory lockFactory ) {
260             this(parent, lockFactory, null, null);
261         }
262 
263         /**
264          * Create a new {@link LuceneConfiguration} that creates {@link FSDirectory} instances mapped to folders under a parent
265          * folder, where the workspace name is used to create the workspace folder. Note that this has ramifications on the
266          * allowable workspace names.
267          * 
268          * @param parent the parent folder
269          * @param workspaceNameEncoder the encoder that should be used for encoding the workspace name into a directory name
270          * @param indexNameEncoder the encoder that should be used for encoding the index name into a directory name
271          * @throws IllegalArgumentException if the parent file is null
272          */
273         protected FileSystemDirectoryFromNameFactory( File parent,
274                                                       TextEncoder workspaceNameEncoder,
275                                                       TextEncoder indexNameEncoder ) {
276             this(parent, null, workspaceNameEncoder, indexNameEncoder);
277         }
278 
279         /**
280          * Create a new {@link LuceneConfiguration} that creates {@link FSDirectory} instances mapped to folders under a parent
281          * folder, where the workspace name is used to create the workspace folder. Note that this has ramifications on the
282          * allowable workspace names.
283          * 
284          * @param parent the parent folder
285          * @param lockFactory the lock factory; may be null
286          * @param workspaceNameEncoder the encoder that should be used for encoding the workspace name into a directory name
287          * @param indexNameEncoder the encoder that should be used for encoding the index name into a directory name
288          * @throws IllegalArgumentException if the parent file is null
289          */
290         protected FileSystemDirectoryFromNameFactory( File parent,
291                                                       LockFactory lockFactory,
292                                                       TextEncoder workspaceNameEncoder,
293                                                       TextEncoder indexNameEncoder ) {
294             CheckArg.isNotNull(parent, "parent");
295             this.parentFile = parent;
296             this.lockFactory = lockFactory;
297             this.workspaceNameEncoder = workspaceNameEncoder != null ? workspaceNameEncoder : new NoOpEncoder();
298             this.indexNameEncoder = indexNameEncoder != null ? indexNameEncoder : new NoOpEncoder();
299         }
300 
301         /**
302          * {@inheritDoc}
303          * 
304          * @see org.modeshape.search.lucene.LuceneConfiguration#getVersion()
305          */
306         @Override
307         public Version getVersion() {
308             return Version.LUCENE_30;
309         }
310 
311         @Override
312         protected FSDirectory createDirectory( String workspaceName,
313                                                String indexName ) {
314             File workspaceFile = new File(parentFile, workspaceNameEncoder.encode(workspaceName));
315             if (!workspaceFile.exists()) {
316                 workspaceFile.mkdirs();
317             } else {
318                 if (!workspaceFile.isDirectory()) {
319                     I18n msg = LuceneI18n.locationForIndexesIsNotDirectory;
320                     throw new SearchEngineException(msg.text(workspaceFile.getAbsolutePath(), workspaceName));
321                 }
322                 if (!workspaceFile.canRead()) {
323                     I18n msg = LuceneI18n.locationForIndexesCannotBeRead;
324                     throw new SearchEngineException(msg.text(workspaceFile.getAbsolutePath(), workspaceName));
325                 }
326                 if (!workspaceFile.canWrite()) {
327                     I18n msg = LuceneI18n.locationForIndexesCannotBeWritten;
328                     throw new SearchEngineException(msg.text(workspaceFile.getAbsolutePath(), workspaceName));
329                 }
330             }
331             File directory = workspaceFile;
332             if (indexName != null) {
333                 File indexFile = new File(workspaceFile, indexNameEncoder.encode(indexName));
334                 if (!indexFile.exists()) {
335                     Logger.getLogger(LuceneConfigurations.class).debug("Creating index folders for the '{0}' workspace at '{1}'",
336                                                                        workspaceName,
337                                                                        workspaceFile);
338                     indexFile.mkdirs();
339                 } else {
340                     if (!indexFile.isDirectory()) {
341                         I18n msg = LuceneI18n.locationForIndexesIsNotDirectory;
342                         throw new SearchEngineException(msg.text(indexFile.getAbsolutePath(), workspaceName));
343                     }
344                     if (!indexFile.canRead()) {
345                         I18n msg = LuceneI18n.locationForIndexesCannotBeRead;
346                         throw new SearchEngineException(msg.text(indexFile.getAbsolutePath(), workspaceName));
347                     }
348                     if (!indexFile.canWrite()) {
349                         I18n msg = LuceneI18n.locationForIndexesCannotBeWritten;
350                         throw new SearchEngineException(msg.text(indexFile.getAbsolutePath(), workspaceName));
351                     }
352                 }
353                 directory = indexFile;
354             }
355             try {
356                 Logger.getLogger(LuceneConfigurations.class)
357                       .debug("Initializing index files for the '{0}' workspace indexes under '{1}'", workspaceName, workspaceFile);
358                 return create(directory, lockFactory);
359             } catch (IOException e) {
360                 throw new SearchEngineException(e);
361             }
362         }
363 
364         /**
365          * {@inheritDoc}
366          * 
367          * @see LuceneConfigurations.PoolingDirectoryFactory#doDestroy(org.apache.lucene.store.Directory)
368          */
369         @Override
370         protected boolean doDestroy( FSDirectory directory ) throws SearchEngineException {
371             File file = directory.getFile();
372             if (file.exists()) {
373                 return FileUtil.delete(file);
374             }
375             return false;
376         }
377 
378         /**
379          * Override this method to define which subclass of {@link FSDirectory} should be created.
380          * 
381          * @param directory the file system directory; never null
382          * @param lockFactory the lock factory; may be null
383          * @return the {@link FSDirectory} instance
384          * @throws IOException if there is a problem creating the FSDirectory instance
385          */
386         protected FSDirectory create( File directory,
387                                       LockFactory lockFactory ) throws IOException {
388             return FSDirectory.open(directory, lockFactory);
389         }
390     }
391 
392     @Immutable
393     protected static final class IndexId {
394         private final String workspaceName;
395         private final String indexName;
396         private final int hc;
397 
398         protected IndexId( String workspaceName,
399                            String indexName ) {
400             assert workspaceName != null;
401             this.workspaceName = workspaceName;
402             this.indexName = indexName;
403             this.hc = HashCode.compute(this.workspaceName, this.indexName);
404         }
405 
406         /**
407          * @return indexName
408          */
409         public String getIndexName() {
410             return indexName;
411         }
412 
413         /**
414          * @return workspaceName
415          */
416         public String getWorkspaceName() {
417             return workspaceName;
418         }
419 
420         /**
421          * {@inheritDoc}
422          * 
423          * @see java.lang.Object#hashCode()
424          */
425         @Override
426         public int hashCode() {
427             return hc;
428         }
429 
430         /**
431          * {@inheritDoc}
432          * 
433          * @see java.lang.Object#equals(java.lang.Object)
434          */
435         @Override
436         public boolean equals( Object obj ) {
437             if (obj == this) return true;
438             if (obj instanceof IndexId) {
439                 IndexId that = (IndexId)obj;
440                 if (this.hashCode() != that.hashCode()) return false;
441                 if (!this.workspaceName.equals(that.workspaceName)) return false;
442                 if (!this.indexName.equals(that.indexName)) return false;
443                 return true;
444             }
445             return false;
446         }
447 
448         /**
449          * {@inheritDoc}
450          * 
451          * @see java.lang.Object#toString()
452          */
453         @Override
454         public String toString() {
455             return indexName != null ? workspaceName + "/" + this.indexName : this.workspaceName;
456         }
457     }
458 }