001: /**
002: * Copyright (c) 2000-2008 Liferay, Inc. All rights reserved.
003: *
004: * Permission is hereby granted, free of charge, to any person obtaining a copy
005: * of this software and associated documentation files (the "Software"), to deal
006: * in the Software without restriction, including without limitation the rights
007: * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
008: * copies of the Software, and to permit persons to whom the Software is
009: * furnished to do so, subject to the following conditions:
010: *
011: * The above copyright notice and this permission notice shall be included in
012: * all copies or substantial portions of the Software.
013: *
014: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
017: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
019: * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
020: * SOFTWARE.
021: */package com.liferay.portal.lucene;
022:
023: import com.liferay.portal.SystemException;
024: import com.liferay.portal.kernel.util.GetterUtil;
025: import com.liferay.portal.model.Company;
026: import com.liferay.portal.model.impl.CompanyImpl;
027: import com.liferay.portal.service.CompanyLocalServiceUtil;
028: import com.liferay.portal.util.PropsUtil;
029: import com.liferay.util.CollectionFactory;
030: import com.liferay.util.SystemProperties;
031:
032: import edu.emory.mathcs.backport.java.util.concurrent.Semaphore;
033:
034: import java.io.File;
035: import java.io.IOException;
036:
037: import java.util.Iterator;
038: import java.util.List;
039: import java.util.Map;
040:
041: import org.apache.commons.logging.Log;
042: import org.apache.commons.logging.LogFactory;
043: import org.apache.lucene.analysis.SimpleAnalyzer;
044: import org.apache.lucene.index.IndexReader;
045: import org.apache.lucene.index.IndexWriter;
046: import org.apache.lucene.index.Term;
047: import org.apache.lucene.store.Directory;
048: import org.apache.lucene.store.FSDirectory;
049:
050: /**
051: * <a href="IndexWriterFactory.java.html"><b><i>View Source</i></b></a>
052: *
053: * <p>
054: * Lucene only allows one IndexWriter to be open at a time. However, multiple
055: * threads can use this single IndexWriter. This class manages a global
056: * IndexWriter and uses reference counting to determine when it can be closed.
057: * </p>
058: *
059: * <p>
060: * To delete documents, IndexReaders are used but cannot delete while another
061: * IndexWriter or IndexReader has the write lock. A semaphore is used to
062: * serialize delete and add operations. If the shared IndexWriter is open,
063: * concurrent add operations are permitted.
064: * </p>
065: *
066: * @author Harry Mark
067: * @author Brian Wing Shun Chan
068: *
069: */
070: public class IndexWriterFactory {
071:
072: public IndexWriterFactory() {
073: if (LuceneUtil.INDEX_READ_ONLY) {
074: return;
075: }
076:
077: // Create semaphores for all companies
078:
079: try {
080: List companies = CompanyLocalServiceUtil.getCompanies();
081:
082: for (int i = 0; i < companies.size(); i++) {
083: Company company = (Company) companies.get(i);
084:
085: _lockLookup.put(new Long(company.getCompanyId()),
086: new Semaphore(1));
087: }
088:
089: _lockLookup.put(new Long(CompanyImpl.SYSTEM),
090: new Semaphore(1));
091: } catch (SystemException se) {
092: _log.error(se);
093: }
094: }
095:
096: public void acquireLock(long companyId, boolean needExclusive)
097: throws InterruptedException {
098:
099: if (LuceneUtil.INDEX_READ_ONLY) {
100: return;
101: }
102:
103: Semaphore lock = (Semaphore) _lockLookup
104: .get(new Long(companyId));
105:
106: if (lock != null) {
107:
108: // Exclusive checking is used to prevent greedy IndexWriter sharing.
109: // This registers a need for exclusive lock, and causes IndexWriters
110: // to wait in FIFO order.
111:
112: if (needExclusive) {
113: synchronized (_lockLookup) {
114: _needExclusiveLock++;
115: }
116: }
117:
118: try {
119: lock.acquire();
120: } finally {
121: if (needExclusive) {
122: synchronized (_lockLookup) {
123: _needExclusiveLock--;
124: }
125: }
126: }
127: } else {
128: if (_log.isWarnEnabled()) {
129: _log.warn("IndexWriterFactory lock not found for "
130: + companyId);
131: }
132: }
133: }
134:
135: public void deleteDocuments(long companyId, Term term)
136: throws InterruptedException, IOException {
137:
138: if (LuceneUtil.INDEX_READ_ONLY) {
139: return;
140: }
141:
142: try {
143: acquireLock(companyId, true);
144:
145: IndexReader reader = null;
146:
147: try {
148: reader = IndexReader.open(LuceneUtil
149: .getLuceneDir(companyId));
150:
151: reader.deleteDocuments(term);
152: } finally {
153: if (reader != null) {
154: reader.close();
155: }
156: }
157: } finally {
158: releaseLock(companyId);
159: }
160: }
161:
162: public IndexWriter getWriter(long companyId, boolean create)
163: throws IOException {
164:
165: if (LuceneUtil.INDEX_READ_ONLY) {
166: return getReadOnlyIndexWriter();
167: }
168:
169: Long companyIdObj = new Long(companyId);
170:
171: boolean hasError = false;
172: boolean newWriter = false;
173:
174: try {
175:
176: // If others need an exclusive lock, then wait to acquire lock
177: // before proceeding. This prevents starvation.
178:
179: if (_needExclusiveLock > 0) {
180: acquireLock(companyId, false);
181: releaseLock(companyId);
182: }
183:
184: synchronized (this ) {
185: IndexWriterData writerData = (IndexWriterData) _writerLookup
186: .get(companyIdObj);
187:
188: if (writerData == null) {
189: newWriter = true;
190:
191: acquireLock(companyId, false);
192:
193: IndexWriter writer = new IndexWriter(LuceneUtil
194: .getLuceneDir(companyId), LuceneUtil
195: .getAnalyzer(), create);
196:
197: writer.setMergeFactor(_MERGE_FACTOR);
198:
199: writerData = new IndexWriterData(companyId, writer,
200: 0);
201:
202: _writerLookup.put(companyIdObj, writerData);
203: }
204:
205: writerData.setCount(writerData.getCount() + 1);
206:
207: return writerData.getWriter();
208: }
209: } catch (Exception e) {
210: hasError = true;
211:
212: _log.error("Unable to create a new writer", e);
213:
214: throw new IOException("Unable to create a new writer");
215: } finally {
216: if (hasError && newWriter) {
217: try {
218: releaseLock(companyId);
219: } catch (Exception e) {
220: }
221: }
222: }
223: }
224:
225: public void releaseLock(long companyId) {
226: if (LuceneUtil.INDEX_READ_ONLY) {
227: return;
228: }
229:
230: Semaphore lock = (Semaphore) _lockLookup
231: .get(new Long(companyId));
232:
233: if (lock != null) {
234: lock.release();
235: }
236: }
237:
238: public void write(long companyId) throws IOException {
239: if (LuceneUtil.INDEX_READ_ONLY) {
240: return;
241: }
242:
243: IndexWriterData writerData = (IndexWriterData) _writerLookup
244: .get(new Long(companyId));
245:
246: if (writerData != null) {
247: decrement(writerData);
248: } else {
249: if (_log.isWarnEnabled()) {
250: _log.warn("IndexWriterData not found for " + companyId);
251: }
252: }
253: }
254:
255: public void write(IndexWriter writer) throws IOException {
256: if (LuceneUtil.INDEX_READ_ONLY) {
257: return;
258: }
259:
260: boolean writerFound = false;
261:
262: synchronized (this ) {
263: if (!_writerLookup.isEmpty()) {
264: Iterator itr = _writerLookup.values().iterator();
265:
266: while (itr.hasNext()) {
267: IndexWriterData writerData = (IndexWriterData) itr
268: .next();
269:
270: if (writerData.getWriter() == writer) {
271: writerFound = true;
272:
273: decrement(writerData);
274:
275: break;
276: }
277: }
278: }
279: }
280:
281: if (!writerFound) {
282: try {
283: _optimizeCount++;
284:
285: if ((_OPTIMIZE_INTERVAL == 0)
286: || (_optimizeCount >= _OPTIMIZE_INTERVAL)) {
287:
288: writer.optimize();
289:
290: _optimizeCount = 0;
291: }
292: } finally {
293: writer.close();
294: }
295: }
296: }
297:
298: protected void decrement(IndexWriterData writerData)
299: throws IOException {
300: if (writerData.getCount() > 0) {
301: writerData.setCount(writerData.getCount() - 1);
302:
303: if (writerData.getCount() == 0) {
304: _writerLookup
305: .remove(new Long(writerData.getCompanyId()));
306:
307: try {
308: IndexWriter writer = writerData.getWriter();
309:
310: try {
311: _optimizeCount++;
312:
313: if ((_OPTIMIZE_INTERVAL == 0)
314: || (_optimizeCount >= _OPTIMIZE_INTERVAL)) {
315:
316: writer.optimize();
317:
318: _optimizeCount = 0;
319: }
320: } finally {
321: writer.close();
322: }
323: } catch (Exception e) {
324: _log.error(e, e);
325: } finally {
326: releaseLock(writerData.getCompanyId());
327: }
328: }
329: }
330: }
331:
332: protected IndexWriter getReadOnlyIndexWriter() {
333: if (_readOnlyIndexWriter == null) {
334: try {
335: if (_log.isInfoEnabled()) {
336: _log
337: .info("Disabling writing to index for this process");
338: }
339:
340: _readOnlyIndexWriter = new ReadOnlyIndexWriter(
341: getReadOnlyLuceneDir(), new SimpleAnalyzer(),
342: true);
343: } catch (IOException ioe) {
344: throw new RuntimeException(ioe);
345: }
346: }
347:
348: return _readOnlyIndexWriter;
349: }
350:
351: protected Directory getReadOnlyLuceneDir() throws IOException {
352: if (_readOnlyLuceneDir == null) {
353: String tmpDir = SystemProperties
354: .get(SystemProperties.TMP_DIR);
355:
356: File dir = new File(tmpDir + "/liferay/lucene/empty");
357:
358: dir.mkdir();
359:
360: _readOnlyLuceneDir = FSDirectory.getDirectory(
361: dir.getPath(), false);
362: }
363:
364: return _readOnlyLuceneDir;
365: }
366:
367: private static final int _MERGE_FACTOR = GetterUtil
368: .getInteger(PropsUtil.get(PropsUtil.LUCENE_MERGE_FACTOR));
369:
370: private static final int _OPTIMIZE_INTERVAL = GetterUtil
371: .getInteger(PropsUtil
372: .get(PropsUtil.LUCENE_OPTIMIZE_INTERVAL));
373:
374: private static Log _log = LogFactory
375: .getLog(IndexWriterFactory.class);
376:
377: private FSDirectory _readOnlyLuceneDir = null;
378: private IndexWriter _readOnlyIndexWriter = null;
379: private Map _lockLookup = CollectionFactory.getHashMap();
380: private Map _writerLookup = CollectionFactory.getHashMap();
381: private int _needExclusiveLock = 0;
382: private int _optimizeCount = 0;
383:
384: }
|