001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. The ASF licenses this file to You
004: * under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License. For additional information regarding
015: * copyright in this work, please see the NOTICE file in the top level
016: * directory of this distribution.
017: */
018: /* Created on Jul 18, 2003 */
019: package org.apache.roller.business.search;
020:
021: import java.io.File;
022: import java.io.IOException;
023:
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026: import org.apache.lucene.analysis.Analyzer;
027: import org.apache.lucene.analysis.standard.StandardAnalyzer;
028: import org.apache.lucene.index.IndexReader;
029: import org.apache.lucene.index.IndexWriter;
030: import org.apache.lucene.store.Directory;
031: import org.apache.lucene.store.FSDirectory;
032: import org.apache.lucene.store.RAMDirectory;
033: import org.apache.roller.RollerException;
034: import org.apache.roller.business.*;
035: import org.apache.roller.business.search.operations.AddEntryOperation;
036: import org.apache.roller.business.search.operations.IndexOperation;
037: import org.apache.roller.business.search.operations.ReIndexEntryOperation;
038: import org.apache.roller.business.search.operations.RebuildWebsiteIndexOperation;
039: import org.apache.roller.business.search.operations.RemoveEntryOperation;
040: import org.apache.roller.business.search.operations.RemoveWebsiteIndexOperation;
041: import org.apache.roller.business.search.operations.WriteToIndexOperation;
042: import org.apache.roller.pojos.WeblogEntryData;
043: import org.apache.roller.pojos.WebsiteData;
044:
045: import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock;
046: import EDU.oswego.cs.dl.util.concurrent.WriterPreferenceReadWriteLock;
047: import org.apache.roller.config.RollerConfig;
048: import org.apache.roller.business.RollerFactory;
049: import org.apache.commons.lang.StringUtils;
050:
051: /**
052: * Lucene implementation of IndexManager. This is the central entry point
053: * into the Lucene searching API.
054: * @author Mindaugas Idzelis (min@idzelis.com)
055: * @author mraible (formatting and making indexDir configurable)
056: */
057: public class IndexManagerImpl implements IndexManager {
058: //~ Static fields/initializers
059: // =============================================
060:
061: private IndexReader reader;
062:
063: static Log mLogger = LogFactory.getFactory().getInstance(
064: IndexManagerImpl.class);
065:
066: //~ Instance fields
067: // ========================================================
068:
069: private boolean searchEnabled = true;
070:
071: File indexConsistencyMarker;
072:
073: private boolean useRAMIndex = false;
074:
075: private RAMDirectory fRAMindex;
076:
077: private String indexDir = null;
078:
079: private boolean inconsistentAtStartup = false;
080:
081: private ReadWriteLock rwl = new WriterPreferenceReadWriteLock();
082:
083: //~ Constructors
084: // ===========================================================
085:
086: /**
087: * Creates a new lucene index manager. This should only be created once.
088: * Creating the index manager more than once will definately result in
089: * errors. The preferred way of getting an index is through the
090: * RollerContext.
091: *
092: * @param indexDir -
093: * the path to the index directory
094: */
095: public IndexManagerImpl() {
096: // check config to see if the internal search is enabled
097: String enabled = RollerConfig.getProperty("search.enabled");
098: if ("false".equalsIgnoreCase(enabled))
099: this .searchEnabled = false;
100:
101: // we also need to know what our index directory is
102: // Note: system property expansion is now handled by RollerConfig
103: String searchIndexDir = RollerConfig
104: .getProperty("search.index.dir");
105:
106: this .indexDir = searchIndexDir.replace('/', File.separatorChar);
107:
108: // a little debugging
109: mLogger.info("search enabled: " + this .searchEnabled);
110: mLogger.info("index dir: " + this .indexDir);
111:
112: String test = indexDir + File.separator + ".index-inconsistent";
113: indexConsistencyMarker = new File(test);
114:
115: // only setup the index if search is enabled
116: if (this .searchEnabled) {
117:
118: // 1. If inconsistency marker exists.
119: // Delete index
120: // 2. if we're using RAM index
121: // load ram index wrapper around index
122: //
123: if (indexConsistencyMarker.exists()) {
124: getFSDirectory(true);
125: inconsistentAtStartup = true;
126: } else {
127: try {
128: File makeIndexDir = new File(indexDir);
129: if (!makeIndexDir.exists()) {
130: makeIndexDir.mkdirs();
131: inconsistentAtStartup = true;
132: }
133: indexConsistencyMarker.createNewFile();
134: } catch (IOException e) {
135: mLogger.error(e);
136: }
137: }
138:
139: if (indexExists()) {
140: if (useRAMIndex) {
141: Directory filesystem = getFSDirectory(false);
142:
143: try {
144: fRAMindex = new RAMDirectory(filesystem);
145: } catch (IOException e) {
146: mLogger.error("Error creating in-memory index",
147: e);
148: }
149: }
150: } else {
151: if (useRAMIndex) {
152: fRAMindex = new RAMDirectory();
153: createIndex(fRAMindex);
154: } else {
155: createIndex(getFSDirectory(true));
156: }
157: }
158:
159: if (isInconsistentAtStartup()) {
160: mLogger
161: .info("Index was inconsistent. Rebuilding index in the background...");
162: try {
163: rebuildWebsiteIndex();
164: } catch (RollerException e) {
165: mLogger
166: .error("ERROR: scheduling re-index operation");
167: }
168: }
169: }
170: }
171:
172: //~ Methods
173: // ================================================================
174:
175: public void rebuildWebsiteIndex() throws RollerException {
176: scheduleIndexOperation(new RebuildWebsiteIndexOperation(this ,
177: null));
178: }
179:
180: public void rebuildWebsiteIndex(WebsiteData website)
181: throws RollerException {
182: scheduleIndexOperation(new RebuildWebsiteIndexOperation(this ,
183: website));
184: }
185:
186: public void removeWebsiteIndex(WebsiteData website)
187: throws RollerException {
188: scheduleIndexOperation(new RemoveWebsiteIndexOperation(this ,
189: website));
190: }
191:
192: public void addEntryIndexOperation(WeblogEntryData entry)
193: throws RollerException {
194: AddEntryOperation addEntry = new AddEntryOperation(this , entry);
195: scheduleIndexOperation(addEntry);
196: }
197:
198: public void addEntryReIndexOperation(WeblogEntryData entry)
199: throws RollerException {
200: ReIndexEntryOperation reindex = new ReIndexEntryOperation(this ,
201: entry);
202: scheduleIndexOperation(reindex);
203: }
204:
205: public void removeEntryIndexOperation(WeblogEntryData entry)
206: throws RollerException {
207: RemoveEntryOperation removeOp = new RemoveEntryOperation(this ,
208: entry);
209: executeIndexOperationNow(removeOp);
210: }
211:
212: public ReadWriteLock getReadWriteLock() {
213: return rwl;
214: }
215:
216: public boolean isInconsistentAtStartup() {
217: return inconsistentAtStartup;
218: }
219:
220: /**
221: * This is the analyzer that will be used to tokenize comment text.
222: *
223: * @return Analyzer to be used in manipulating the database.
224: */
225: public static final Analyzer getAnalyzer() {
226: return new StandardAnalyzer();
227: }
228:
229: private void scheduleIndexOperation(final IndexOperation op) {
230: try {
231: // only if search is enabled
232: if (this .searchEnabled) {
233: mLogger.debug("Starting scheduled index operation: "
234: + op.getClass().getName());
235: RollerFactory.getRoller().getThreadManager()
236: .executeInBackground(op);
237: }
238: } catch (RollerException re) {
239: mLogger.error("Error getting thread manager", re);
240: } catch (InterruptedException e) {
241: mLogger.error("Error executing operation", e);
242: }
243: }
244:
245: /**
246: * @param search
247: */
248: public void executeIndexOperationNow(final IndexOperation op) {
249: try {
250: // only if search is enabled
251: if (this .searchEnabled) {
252: mLogger.debug("Executing index operation now: "
253: + op.getClass().getName());
254: RollerFactory.getRoller().getThreadManager()
255: .executeInForeground(op);
256: }
257: } catch (RollerException re) {
258: mLogger.error("Error getting thread manager", re);
259: } catch (InterruptedException e) {
260: mLogger.error("Error executing operation", e);
261: }
262: }
263:
264: public synchronized void resetSharedReader() {
265: reader = null;
266: }
267:
268: public synchronized IndexReader getSharedIndexReader() {
269: if (reader == null) {
270: try {
271: reader = IndexReader.open(getIndexDirectory());
272: } catch (IOException e) {
273: }
274: }
275: return reader;
276: }
277:
278: /**
279: * Get the directory that is used by the lucene index. This method will
280: * return null if there is no index at the directory location. If we are
281: * using a RAM index, the directory will be a ram directory.
282: *
283: * @return Directory The directory containing the index, or null if error.
284: */
285: public Directory getIndexDirectory() {
286: if (useRAMIndex) {
287: return fRAMindex;
288: } else {
289: return getFSDirectory(false);
290: }
291: }
292:
293: private boolean indexExists() {
294: return IndexReader.indexExists(indexDir);
295: }
296:
297: Directory getFSDirectory(boolean delete) {
298: Directory directory = null;
299:
300: try {
301: directory = FSDirectory.getDirectory(indexDir, delete);
302: } catch (IOException e) {
303: mLogger.error("Problem accessing index directory", e);
304: }
305:
306: return directory;
307: }
308:
309: private void createIndex(Directory dir) {
310: IndexWriter writer = null;
311:
312: try {
313: writer = new IndexWriter(dir, IndexManagerImpl
314: .getAnalyzer(), true);
315: } catch (IOException e) {
316: mLogger.error("Error creating index", e);
317: } finally {
318: try {
319: if (writer != null) {
320: writer.close();
321: }
322: } catch (IOException e) {
323: }
324: }
325: }
326:
327: private IndexOperation getSaveIndexOperation() {
328: return new WriteToIndexOperation(this ) {
329: public void doRun() {
330: Directory dir = getIndexDirectory();
331: Directory fsdir = getFSDirectory(true);
332:
333: IndexWriter writer = null;
334:
335: try {
336: writer = new IndexWriter(fsdir, IndexManagerImpl
337: .getAnalyzer(), true);
338:
339: writer.addIndexes(new Directory[] { dir });
340: indexConsistencyMarker.delete();
341: } catch (IOException e) {
342: mLogger.error("Problem saving index to disk", e);
343:
344: // Delete the directory, since there was a problem saving
345: // the RAM contents
346: getFSDirectory(true);
347: } finally {
348: try {
349: if (writer != null)
350: writer.close();
351: } catch (IOException e1) {
352: mLogger.warn("Unable to close IndexWriter.");
353: }
354: }
355:
356: }
357: };
358: }
359:
360: public void release() {
361: // no-op
362: }
363:
364: public void shutdown() {
365: if (useRAMIndex) {
366: scheduleIndexOperation(getSaveIndexOperation());
367: } else {
368: indexConsistencyMarker.delete();
369: }
370:
371: try {
372: if (reader != null)
373: reader.close();
374: } catch (IOException e) {
375: // won't happen, since it was
376: }
377: }
378: }
|