LuceneIndexer.java
001 /*
002  *  LuceneIndexer.java
003  *
004  *  Niraj Aswani, 19/March/07
005  *
006  *  $Id: LuceneIndexer.html,v 1.0 2007/03/19 16:22:01 niraj Exp $
007  */
008 package gate.creole.annic.lucene;
009 
010 import java.io.File;
011 import java.io.IOException;
012 import java.net.URISyntaxException;
013 import java.net.URL;
014 import java.util.ArrayList;
015 import java.util.HashMap;
016 import java.util.HashSet;
017 import java.util.Iterator;
018 import java.util.List;
019 import java.util.Map;
020 import java.util.Set;
021 
022 import gate.creole.annic.Constants;
023 import gate.creole.annic.IndexException;
024 import gate.creole.annic.Indexer;
025 import gate.creole.annic.apache.lucene.document.Document;
026 import gate.creole.annic.apache.lucene.index.IndexReader;
027 import gate.creole.annic.apache.lucene.index.IndexWriter;
028 import gate.creole.annic.apache.lucene.index.Term;
029 import gate.creole.annic.apache.lucene.search.Hits;
030 import gate.creole.annic.apache.lucene.search.IndexSearcher;
031 import gate.creole.annic.apache.lucene.search.TermQuery;
032 import gate.Corpus;
033 import gate.util.Files;
034 
035 /**
036  * This class provides a Lucene based implementation for the Indexer
037  * interface. It asks users to provide various required parameters and
038  * creates the Lucene Index.
039  
040  @author niraj
041  
042  */
043 public class LuceneIndexer implements Indexer {
044 
045   protected boolean DEBUG = false;
046 
047   /** An corpus for indexing */
048   protected Corpus corpus;
049 
050   /**
051    * Various parameters such as location of the Index etc.
052    */
053   protected Map<String,Object> parameters;
054 
055   /**
056    * Constructor
057    
058    @param indexLocationUrl
059    @throws IOException
060    */
061   public LuceneIndexer(URL indexLocationUrlthrows IOException {
062     if(indexLocationUrl != null) {
063       readParametersFromDisk(indexLocationUrl);
064     }
065   }
066 
067   /**
068    * Checks the Index Parameters to see if they are all compatible
069    */
070   protected void checkIndexParameters(Map<String,Object> parametersthrows IndexException {
071     this.parameters = parameters;
072 
073     if(parameters == null) {
074       throw new IndexException("No parameters provided!");
075     }
076 
077     URL indexLocation = (URL)parameters.get(Constants.INDEX_LOCATION_URL);
078     if(indexLocation == null)
079       throw new IndexException("You must provide a URL for INDEX_LOCATION");
080 
081     if(!indexLocation.getProtocol().equalsIgnoreCase("file")) {
082       throw new IndexException(
083               "Index Output Directory must be set to the empty directory on the file system");
084     }
085 
086     File file = null;
087     try {
088       file = new File(indexLocation.toURI());
089     catch(URISyntaxException use) {
090       file = Files.fileFromURL(indexLocation);
091     }
092       
093       if(file.exists()) {
094         if(!file.isDirectory()) {
095           throw new IndexException("Path doesn't exist");
096         }
097       }
098 
099     String baseTokenAnnotationType = (String)parameters
100             .get(Constants.BASE_TOKEN_ANNOTATION_TYPE);
101     if(baseTokenAnnotationType.indexOf("."> -|| baseTokenAnnotationType.indexOf("="> -1
102         || baseTokenAnnotationType.indexOf(";"> -|| baseTokenAnnotationType.indexOf(","> -1) {
103       throw new IndexException(
104       "Base token annotation type cannot have '.' , '=', ',' or ';; in it");
105     }
106     
107     String indexUnitAnnotationType = (String)parameters
108             .get(Constants.INDEX_UNIT_ANNOTATION_TYPE);
109     
110     if(DEBUG) {
111       System.out.println("BTAT : " + baseTokenAnnotationType);
112       System.out.println("IUAT : " + indexUnitAnnotationType);
113     }
114 
115     if(baseTokenAnnotationType == null
116             || baseTokenAnnotationType.trim().length() == 0) {
117       baseTokenAnnotationType = Constants.ANNIC_TOKEN;
118       parameters.put(Constants.BASE_TOKEN_ANNOTATION_TYPE,
119               Constants.ANNIC_TOKEN);
120     }
121   }
122 
123   /**
124    * Returns the indexing parameters
125    */
126   protected Map<String,Object> getIndexParameters() {
127     return this.parameters;
128   }
129 
130   /**
131    * Creates index directory and indexing all documents in the corpus.
132    
133    @param indexParameters This is a map containing various values
134    *          required to create an index In case of LuceneIndexManager
135    *          following are the values required
136    *          <P>
137    *          INDEX_LOCATION_URL - this is a URL where the Index be
138    *          created
139    *          <P>
140    *          BASE_TOKEN_ANNOTATION_TYPE
141    *          <P>
142    *          INDEX_UNIT_ANNOTATION_TYPE
143    *          <P>
144    *          FEATURES_TO_EXCLUDE
145    *          <P>
146    *          FEATURES_TO_INCLUDE
147    *          <P>
148    
149    */
150   @Override
151   public void createIndex(Map<String,Object> indexParametersthrows IndexException {
152     checkIndexParameters(indexParameters);
153     URL indexLocation = (URL)parameters.get(Constants.INDEX_LOCATION_URL);
154 
155     try {
156       File file = null;
157       try {
158         file = new File(indexLocation.toURI());
159       catch(URISyntaxException use) {
160         file = Files.fileFromURL(indexLocation);
161       }
162 
163 
164       // create an instance of Index Writer
165       IndexWriter writer = new IndexWriter(file.getAbsolutePath(),
166               new LuceneAnalyzer()true);
167 
168       try {
169         if(corpus != null) {
170           // load documents and add them one by one
171           for(int i = 0; i < corpus.size(); i++) {
172             gate.Document gateDoc = corpus.get(i);
173             String idToUse = gateDoc.getLRPersistenceId() == null ? gateDoc
174                     .getName() : gateDoc.getLRPersistenceId().toString();
175   
176             System.out.print("Indexing : " + idToUse + " ...");
177             String corpusName = corpus.getLRPersistenceId() == null ? corpus
178                     .getName() : corpus.getLRPersistenceId().toString();
179   
180             List<gate.creole.annic.apache.lucene.document.Document> luceneDocs = getLuceneDocuments(
181                     corpusName, gateDoc, indexLocation.toString());
182   
183             if(luceneDocs != null) {
184               for(int j = 0; j < luceneDocs.size(); j++) {
185                 if(luceneDocs.get(j!= null) {
186                   writer.addDocument(luceneDocs.get(j));
187                 }
188               }
189             }
190             if(gateDoc.getLRPersistenceId() != null) {
191               gate.Factory.deleteResource(gateDoc);
192             }
193             System.out.println("Done");
194           }
195         }// for (all documents)
196       }
197       finally {
198         writer.close();
199       }
200       writeParametersToDisk();
201     }
202     catch(java.io.IOException ioe) {
203       throw new IndexException(ioe);
204     }
205   }
206 
207   /** Optimize existing index. */
208   @Override
209   public void optimizeIndex() throws IndexException {
210     try {
211       String location = ((URL)parameters.get(Constants.INDEX_LOCATION_URL))
212               .toString();
213       IndexWriter writer = new IndexWriter(location,
214               new gate.creole.annic.lucene.LuceneAnalyzer()false);
215       try {
216         writer.optimize();
217       }
218       finally {
219         writer.close();
220       }
221     }
222     catch(java.io.IOException ioe) {
223       throw new IndexException(ioe);
224     }
225   }
226 
227   /** Deletes the index. */
228   @Override
229   public void deleteIndex() throws IndexException {
230     boolean isDeleted = true;
231     if(parameters == nullreturn;
232     File dir = null;
233     try {
234       dir = new File(((URL)parameters.get(Constants.INDEX_LOCATION_URL))
235               .toURI());
236     catch(URISyntaxException use) {
237       dir = new File(((URL)parameters.get(Constants.INDEX_LOCATION_URL))
238               .getFile());
239     }
240 
241     if(dir.exists() && dir.isDirectory()) {
242       File[] files = dir.listFiles();
243       for(int i = 0; i < files.length; i++) {
244         File f = files[i];
245         if(f.isDirectory()) {
246           File[] subFiles = f.listFiles();
247           for(int j = 0; j < subFiles.length; j++) {
248             File sf = subFiles[j];
249             sf.delete();
250           }
251         }
252         f.delete();
253       }
254     }
255     isDeleted = dir.delete();
256     if(!isDeleted) {
257       throw new IndexException("Can't delete directory" + dir.getAbsolutePath());
258     }
259   }
260 
261   /**
262    * Add new documents to Index
263    @throws IndexException
264    */
265   @Override
266   public void add(String corpusPersistenceID, List<gate.Document> added)
267           throws IndexException {
268 
269     String location = null;
270     try {
271       location = new File(((URL)parameters.get(Constants.INDEX_LOCATION_URL))
272               .toURI()).getAbsolutePath();
273     catch(URISyntaxException use) {
274       location = new File(((URL)parameters.get(Constants.INDEX_LOCATION_URL))
275               .getFile()).getAbsolutePath();
276     }
277 
278     try {
279       IndexWriter writer = new IndexWriter(location, new LuceneAnalyzer()false);
280 
281       try {
282         if(added != null) {
283           for(int i = 0; i < added.size(); i++) {
284 
285             gate.Document gateDoc = added.get(i);
286 
287             String idToUse = gateDoc.getLRPersistenceId() == null ? gateDoc
288                     .getName() : gateDoc.getLRPersistenceId().toString();
289             System.out.print("Indexing : " + idToUse + " ...");
290             List<gate.creole.annic.apache.lucene.document.Document> docs = getLuceneDocuments(
291                     corpusPersistenceID, gateDoc, location);
292             if(docs == null) {
293               System.out.println("Done");
294               continue;
295             }
296             for(int j = 0; j < docs.size(); j++) {
297               writer.addDocument(docs.get(j));
298             }
299             System.out.println("Done");
300           }// for (add all added documents)
301         }
302       }
303       finally {
304         // make sure we close the writer, whatever happens
305         writer.close();
306       }
307     }
308     catch(java.io.IOException ioe) {
309       throw new IndexException(ioe);
310     }
311   }
312 
313   private String getCompatibleName(String name) {
314     return name.replaceAll("[\\/:\\*\\?\"<>|]""_");
315   }
316   
317 
318   /**
319    * remove documents from the Index
320    
321    @param removedIDs - when documents are not
322    *          peristed, Persistence IDs will not be available In that
323    *          case provide the document Names instead of their IDs
324    @throws Exception
325    */
326   @Override
327   public void remove(List<Object> removedIDsthrows IndexException {
328 
329     String location = null;
330     try {
331       location = new File(((URL)parameters.get(Constants.INDEX_LOCATION_URL))
332               .toURI()).getAbsolutePath();
333     catch(URISyntaxException use) {
334       location = new File(((URL)parameters.get(Constants.INDEX_LOCATION_URL))
335               .getFile()).getAbsolutePath();
336     
337     }
338     
339     try {
340 
341       IndexReader reader = IndexReader.open(location);
342 
343       try {
344         // let us first remove the documents which need to be removed
345         if(removedIDs != null) {
346           for(int i = 0; i < removedIDs.size(); i++) {
347             String id = removedIDs.get(i).toString();
348             
349             Set<String> serializedFilesIDs = getNamesOfSerializedFiles(id);
350             
351             if(serializedFilesIDs.size() 0) {
352               System.out.print("Removing => " + id + "...");
353             
354             id = getCompatibleName(id);
355             File file = new File(location, Constants.SERIALIZED_FOLDER_NAME);
356             file = new File(file, id);
357             
358             for(String serializedFileID : serializedFilesIDs) {
359               gate.creole.annic.apache.lucene.index.Term term = new gate.creole.annic.apache.lucene.index.Term(
360                       Constants.DOCUMENT_ID_FOR_SERIALIZED_FILE, serializedFileID);
361               reader.delete(term);
362               serializedFileID = getCompatibleName(serializedFileID);
363               // deleting them from the disk as well
364               // we have a subfolder for each document
365               
366               File toDelete = new File(file, serializedFileID
367                       ".annic");
368               if(toDelete.exists()) toDelete.delete();
369             }
370             
371             if(file.exists() && file.isDirectory()) {
372               file.delete();
373             }
374             
375             System.out.println("Done ");
376             }
377           }// for (remove all removed documents)
378         }
379       }
380       finally {
381         reader.close();
382       }
383     }
384     catch(java.io.IOException ioe) {
385       throw new IndexException(ioe);
386     }
387 
388   }
389 
390   /**
391    * We create a separate Lucene document for each index unit available
392    * in the gate document. An array of Lucene document is returned as a
393    * call to this method. It uses various indexing parameters set
394    * earlier.
395    @throws IndexException
396    */
397   private List<gate.creole.annic.apache.lucene.document.Document> getLuceneDocuments(
398           String corpusPersistenceID, gate.Document gateDoc, String location)
399           throws IndexException {
400 
401     String baseTokenAnnotationType = (String)parameters
402             .get(Constants.BASE_TOKEN_ANNOTATION_TYPE);
403 
404     String indexUnitAnnotationType = (String)parameters
405             .get(Constants.INDEX_UNIT_ANNOTATION_TYPE);
406 
407     @SuppressWarnings("unchecked")
408     List<String> featuresToExclude = new ArrayList<String>((List<String>)parameters
409             .get(Constants.FEATURES_TO_EXCLUDE));
410 
411     @SuppressWarnings("unchecked")
412     List<String> featuresToInclude = new ArrayList<String>((List<String>)parameters
413             .get(Constants.FEATURES_TO_INCLUDE));
414 
415     @SuppressWarnings("unchecked")
416     List<String> annotationSetsToExclude = new ArrayList<String>((List<String>)parameters
417             .get(Constants.ANNOTATION_SETS_NAMES_TO_EXCLUDE));
418 
419     @SuppressWarnings("unchecked")
420     List<String> annotationSetsToInclude = new ArrayList<String>((List<String>)parameters
421             .get(Constants.ANNOTATION_SETS_NAMES_TO_INCLUDE));
422 
423     Boolean createTokensAutomatically = (Booleanparameters.get(Constants.CREATE_TOKENS_AUTOMATICALLY);
424     if(createTokensAutomatically == nullcreateTokensAutomatically = new Boolean(true);
425     
426     String idToUse = gateDoc.getLRPersistenceId() == null
427             ? gateDoc.getName()
428             : gateDoc.getLRPersistenceId().toString();
429 
430     return new gate.creole.annic.lucene.LuceneDocument().createDocuments(
431             corpusPersistenceID, gateDoc, idToUse, annotationSetsToInclude,
432             annotationSetsToExclude, featuresToInclude, featuresToExclude,
433             location, baseTokenAnnotationType, createTokensAutomatically, indexUnitAnnotationType);
434   }
435 
436   /**
437    * Returns the corpus.
438    */
439   @Override
440   public Corpus getCorpus() {
441     return corpus;
442   }
443 
444   /**
445    * Sets the corpus.
446    */
447   @Override
448   public void setCorpus(Corpus corpusthrows IndexException {
449     this.corpus = corpus;
450     if(corpus == null) {
451       throw new IndexException("Corpus is not initialized");
452     }
453 
454     // we would add a feature to the corpus
455     // which will tell us if this corpus was index by the ANNIC
456     corpus.getFeatures().put(Constants.CORPUS_INDEX_FEATURE,
457             Constants.CORPUS_INDEX_FEATURE_VALUE);
458   }
459 
460   /**
461    * This method, searchers for the LuceneIndexDefinition.xml file at
462    * the provided location. The file is supposed to contain all the
463    * required parameters which are used to create an index.
464    
465    @param indexLocationUrl
466    @throws IOException
467    */
468   @SuppressWarnings("unchecked")
469   private void readParametersFromDisk(URL indexLocationUrlthrows IOException {
470     // we create a hashmap to store index definition in the index
471     // directory
472 
473     File file = null;
474     try {
475       file = new File(new File(indexLocationUrl.toURI())"LuceneIndexDefinition.xml");
476     catch(URISyntaxException use) {
477       file = new File(indexLocationUrl.getFile()"LuceneIndexDefinition.xml");
478     }
479 
480     if(!file.exists()) return;
481 
482     java.io.FileReader fileReader = new java.io.FileReader(file);
483 
484     try {
485       // other wise read this and
486       com.thoughtworks.xstream.XStream xstream = new com.thoughtworks.xstream.XStream(
487               new com.thoughtworks.xstream.io.xml.StaxDriver());
488   
489       // Saving is accomplished just using XML serialization of the map.
490       this.parameters = (Map<String,Object>)xstream.fromXML(fileReader);
491       // setting the index location URL
492       this.parameters.put(Constants.INDEX_LOCATION_URL, indexLocationUrl);
493     }
494     finally {
495       fileReader.close();
496     }
497   }
498 
499   /**
500    * All Index parameters are stored on a disc at the
501    * index_location_url/LuceneIndexDefinition.xml file.
502    
503    @throws IOException
504    */
505   private void writeParametersToDisk() throws IOException {
506     // we create a hashmap to store index definition in the index
507     // directory
508     URL location = (URL)parameters.get(Constants.INDEX_LOCATION_URL);
509     File file = null;
510     try {
511       file = new File(new File(location.toURI())"LuceneIndexDefinition.xml");
512     catch(URISyntaxException use) {
513       file = new File(location.getFile()"LuceneIndexDefinition.xml");
514     }
515 
516     java.io.FileWriter fileWriter = new java.io.FileWriter(file);
517     Map<String,Object> indexInformation = new HashMap<String,Object>();
518     Iterator<String> iter = parameters.keySet().iterator();
519     while(iter.hasNext()) {
520       String key = iter.next();
521       if(key.equals(Constants.INDEX_LOCATION_URL)) continue;
522       indexInformation.put(key, parameters.get(key));
523     }
524 
525     indexInformation.put(Constants.CORPUS_INDEX_FEATURE,
526             Constants.CORPUS_INDEX_FEATURE_VALUE);
527     if(corpus != null)
528       indexInformation.put(Constants.CORPUS_SIZE, new Integer(corpus
529               .getDocumentNames().size()));
530 
531     // we would use XStream library to store annic patterns
532     com.thoughtworks.xstream.XStream xstream = new com.thoughtworks.xstream.XStream();
533 
534     // Saving is accomplished just using XML serialization of
535     // the map.
536     try {
537       xstream.toXML(indexInformation, fileWriter);
538     }
539     finally {
540       fileWriter.close();
541     }
542   }
543 
544   /**
545    * Returns the set parameters
546    */
547   @Override
548   public Map<String,Object> getParameters() {
549     return this.parameters;
550   }
551 
552   /**
553    * This method returns a set of annotation set names that are indexed.
554    */
555   public Set<String> getNamesOfSerializedFiles(String documentID)
556           throws IndexException {
557     String location = null;
558     try {
559       location = new File(((URL)parameters
560             .get(Constants.INDEX_LOCATION_URL)).toURI()).getAbsolutePath();
561     catch(URISyntaxException use) {
562       location = new File(((URL)parameters
563               .get(Constants.INDEX_LOCATION_URL)).getFile()).getAbsolutePath();
564     }
565     
566     Set<String> toReturn = new HashSet<String>();
567     try {
568       Term term = new Term(Constants.DOCUMENT_ID, documentID);
569       TermQuery tq = new TermQuery(term);
570       gate.creole.annic.apache.lucene.search.Searcher searcher = new IndexSearcher(location);
571       try {
572         // and now execute the query
573         // result of which will be stored in hits
574         Hits luceneHits = searcher.search(tq);
575         for(int i = 0; i < luceneHits.length(); i++) {
576           Document luceneDoc = luceneHits.doc(i);
577           String documentIdOfSerializedFile = luceneDoc
578                   .get(Constants.DOCUMENT_ID_FOR_SERIALIZED_FILE);
579           toReturn.add(documentIdOfSerializedFile);
580         }
581         return toReturn;
582       }
583       finally {
584         searcher.close();
585       }
586     }
587     catch(IOException ioe) {
588       throw new IndexException(ioe);
589     }
590   }
591 }