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
160 public File getStorageDirectory() {
161 return mStorageDirectory;
162 }
163
164 public void setStorageDirectory(File storageDirectory) {
165 mStorageDirectory = storageDirectory;
166 }
167
168
169 private File getRepoStorageDirectory(AbstractRepository repo) {
170 File file = new File(mStorageDirectory, repo.getName());
171 file.mkdirs();
172 return file;
173 }
174 }