View Javadoc

1   /***
2    * Copyright (C) 2008 Jeremy Thomerson (jthomerson@users.sourceforge.net)
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package net.sf.vcaperture.services;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.text.DateFormat;
22  import java.text.SimpleDateFormat;
23  import java.util.Date;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import net.sf.vcaperture.model.AbstractRepository;
30  import net.sf.vcaperture.model.RepoFileRevision;
31  import net.sf.vcaperture.model.Revision;
32  import net.sf.vcaperture.model.SearchQuery;
33  import net.sf.vcaperture.model.SearchResults;
34  
35  import org.apache.commons.io.FileUtils;
36  import org.apache.lucene.analysis.standard.StandardAnalyzer;
37  import org.apache.lucene.document.Document;
38  import org.apache.lucene.document.Field;
39  import org.apache.lucene.document.Field.Index;
40  import org.apache.lucene.document.Field.Store;
41  import org.apache.lucene.index.IndexReader;
42  import org.apache.lucene.index.IndexWriter;
43  
44  public class LuceneSearchService implements ISearchService {
45  
46  	private static final String LATEST_VERSION_FILE = "latest-version.txt";
47  	private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");
48  	private File mStorageDirectory;
49  
50  	private Map<AbstractRepository, IndexReader> mReaders = new HashMap<AbstractRepository, IndexReader>();
51  	
52  	private Set<AbstractRepository> mLockedRepositoryIndexes = new HashSet<AbstractRepository>();
53  
54  	public void beginIndexing(AbstractRepository repo) {
55  		if (mLockedRepositoryIndexes.contains(repo)) {
56  			throw new IllegalStateException("You can not have two threads modifying an index concurrently.");
57  		}
58  		mLockedRepositoryIndexes.add(repo);
59  	}
60  
61  	public void indexRevision(AbstractRepository repo, Revision rev) {
62  		beginIndexing(repo);
63  		try {
64  	        File dir = getRepoStorageDirectory(repo);
65  	        dir.mkdirs();
66  			IndexWriter writer = new IndexWriter(dir.getAbsolutePath(), new StandardAnalyzer(), dir.listFiles().length == 0);
67          	String formattedDate = formatDate(rev.getCommitDate());
68  	        indexRevision(rev, formattedDate, writer);
69  	        for (RepoFileRevision rfr : rev.getFiles()) {
70  				indexFile(formattedDate, rev, rfr, writer);
71  	        }
72  	        writer.optimize();
73  	        writer.close();
74  	        updateLatestStoredRevision(repo, rev);
75          } catch (Exception ex) {
76  	        throw new SearchException("Error indexing revision: " + rev.getName() + ": " + ex.getMessage(), ex);
77          } finally {
78      		endIndexing(repo);
79          }
80  	}
81  
82  	private void indexFile(String formattedDate, Revision rev, RepoFileRevision rfr, IndexWriter writer) throws Exception {
83          Document doc = new Document();
84          doc.add(new Field(ISearchService.FIELD_TYPE, ISearchService.TYPE_FILE, Store.YES, Index.UN_TOKENIZED));
85          doc.add(new Field(ISearchService.FIELD_REVISION, rev.getName(), Store.YES, Index.UN_TOKENIZED));
86          doc.add(new Field(ISearchService.FIELD_NAME, rfr.getRelativePath(), Store.YES, Index.UN_TOKENIZED));
87          doc.add(new Field(ISearchService.FIELD_AUTHOR, rfr.getAuthor(), Store.YES, Index.UN_TOKENIZED));
88          doc.add(new Field(ISearchService.FIELD_DATE, formattedDate, Store.YES, Index.UN_TOKENIZED));
89          doc.add(new Field(ISearchService.FIELD_MESSAGE, nullSafe(rev.getCommitMessage()), Store.YES, Index.TOKENIZED));
90          doc.add(new Field(ISearchService.FIELD_CONTENTS, nullSafe(rfr.getContents()), Store.NO, Index.TOKENIZED));
91          writer.addDocument(doc);
92      }
93  
94  	private void indexRevision(Revision rev, String formattedDate, IndexWriter writer) throws Exception {
95          Document doc = new Document();
96          doc.add(new Field(ISearchService.FIELD_TYPE, ISearchService.TYPE_REVISION, Store.YES, Index.UN_TOKENIZED));
97          doc.add(new Field(ISearchService.FIELD_NAME, rev.getName(), Store.YES, Index.UN_TOKENIZED));
98          doc.add(new Field(ISearchService.FIELD_REVISION, rev.getName(), Store.YES, Index.UN_TOKENIZED));
99          doc.add(new Field(ISearchService.FIELD_AUTHOR, rev.getAuthor(), Store.YES, Index.UN_TOKENIZED));
100         doc.add(new Field(ISearchService.FIELD_DATE, formattedDate, Store.YES, Index.UN_TOKENIZED));
101         doc.add(new Field(ISearchService.FIELD_MESSAGE, nullSafe(rev.getCommitMessage()), Store.YES, Index.TOKENIZED));
102         doc.add(new Field(ISearchService.FIELD_CONTENTS, toString(rev.getFiles()), Store.YES, Index.TOKENIZED));
103         writer.addDocument(doc);
104     }
105 
106 	private String nullSafe(String data) {
107 	    return data == null ? "" : data;
108     }
109 
110 	private String toString(RepoFileRevision[] files) {
111 		StringBuffer sb = new StringBuffer();
112 		for (RepoFileRevision rfr : files) {
113 			sb.append(rfr.getRelativePath()).append("\n");
114 		}
115 	    return sb.toString();
116     }
117 
118 	private String formatDate(Date date) {
119 	    synchronized (DATE_FORMAT) {
120 	        return DATE_FORMAT.format(date);
121         }
122     }
123 
124 	public void endIndexing(AbstractRepository repo) {
125 		mLockedRepositoryIndexes.remove(repo);
126 	}
127 	
128 	public SearchResults search(SearchQuery query) {
129 		@SuppressWarnings("unused")
130         IndexReader reader = null;
131 		synchronized (query.getRepository()) {
132 	        reader = mReaders.get(query.getRepository());
133         }
134 		return null;
135 	}
136 
137 	public String getLastProcessedRevision(AbstractRepository repo) {
138 		File file = new File(getRepoStorageDirectory(repo), LATEST_VERSION_FILE);
139 		if (file.exists() && file.isFile()) {
140 			try {
141 				String rev = FileUtils.readFileToString(file);
142 				return rev == null ? repo.getNullRevisionDefault() : rev.trim();
143 			} catch (IOException ioe) {
144 				throw new StorageException("Error in storage mechanism: " + ioe.getMessage(), ioe);
145 			}
146 		}
147 		return repo.getNullRevisionDefault();
148 	}
149 
150 	public void updateLatestStoredRevision(AbstractRepository repo, Revision rev) {
151 		File file = new File(getRepoStorageDirectory(repo), LATEST_VERSION_FILE);
152 		try {
153 			FileUtils.writeStringToFile(file, rev.getName());
154 		} catch (IOException ioe) {
155 			throw new StorageException("Error in storage mechanism: " + ioe.getMessage(), ioe);
156 		}
157 	}
158 
159 	// IoC
160 	public File getStorageDirectory() {
161 		return mStorageDirectory;
162 	}
163 
164 	public void setStorageDirectory(File storageDirectory) {
165 		mStorageDirectory = storageDirectory;
166 	}
167 
168 	// HELPER METHODS
169 	private File getRepoStorageDirectory(AbstractRepository repo) {
170 		File file = new File(mStorageDirectory, repo.getName());
171 		file.mkdirs();
172 		return file;
173 	}
174 }